diff --git a/cms/djangoapps/contentstore/tests/test_request_event.py b/cms/djangoapps/contentstore/tests/test_request_event.py
index 7ac72f25a06971b3a4f7a519043bc0a3bac04f3f..9c5bebe58d42319998e14314b2e7b33129fd95c1 100644
--- a/cms/djangoapps/contentstore/tests/test_request_event.py
+++ b/cms/djangoapps/contentstore/tests/test_request_event.py
@@ -3,7 +3,7 @@
 
 from django.test import TestCase
 from django.urls import reverse
-from six import unichr  # pylint: disable=W0622
+from six import unichr
 
 from contentstore.views.helpers import event as cms_user_track
 
@@ -20,7 +20,7 @@ class CMSLogTest(TestCase):
         """
         requests = [
             {"event": "my_event", "event_type": "my_event_type", "page": "my_page"},
-            {"event": "{'json': 'object'}", "event_type": unichr(512), "page": "my_page"}  # pylint: disable=unicode-format-string
+            {"event": "{'json': 'object'}", "event_type": unichr(512), "page": "my_page"}
         ]
         for request_params in requests:
             response = self.client.post(reverse(cms_user_track), request_params)
diff --git a/lms/djangoapps/courseware/course_tools.py b/lms/djangoapps/courseware/course_tools.py
index ecad90aee08efe580b33dbc8665b091260e8ff38..b13116cd6166e8f6552864c7f944189dd04a3088 100644
--- a/lms/djangoapps/courseware/course_tools.py
+++ b/lms/djangoapps/courseware/course_tools.py
@@ -10,7 +10,7 @@ from crum import get_current_request
 from django.utils.translation import ugettext as _
 
 from course_modes.models import CourseMode
-from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
 from openedx.features.course_experience.course_tools import CourseTool
 from student.models import CourseEnrollment
 
diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py
index a7477fe27986310f5cf8df24d87e8aefb14a00be..5fa7d181d13b19609200ac6d8afef84728cc05c7 100644
--- a/lms/djangoapps/courseware/courses.py
+++ b/lms/djangoapps/courseware/courses.py
@@ -15,7 +15,9 @@ from django.conf import settings
 from django.db.models import Prefetch
 from django.http import Http404, QueryDict
 from django.urls import reverse
+from django.utils.translation import ugettext as _
 from edx_django_utils.monitoring import function_trace
+from edx_when.api import get_dates_for_course
 from fs.errors import ResourceNotFound
 from opaque_keys.edx.keys import UsageKey
 from path import Path as path
@@ -27,7 +29,9 @@ from lms.djangoapps.courseware.access import has_access
 from lms.djangoapps.courseware.access_response import MilestoneAccessError, StartDateError
 from lms.djangoapps.courseware.date_summary import (
     CertificateAvailableDate,
+    CourseAssignmentDate,
     CourseEndDate,
+    CourseExpiredDate,
     CourseStartDate,
     TodaysDate,
     VerificationDeadlineDate,
@@ -45,7 +49,7 @@ from openedx.core.djangoapps.enrollments.api import get_course_enrollment_detail
 from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
 from openedx.core.lib.api.view_utils import LazySequence
 from openedx.features.course_duration_limits.access import AuditExpiredError
-from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG
+from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, DATE_WIDGET_V2_FLAG
 from static_replace import replace_static_urls
 from student.models import CourseEnrollment
 from survey.utils import is_survey_required_and_unanswered
@@ -390,7 +394,7 @@ def get_course_info_section(request, user, course, section_key):
     return html
 
 
-def get_course_date_blocks(course, user):
+def get_course_date_blocks(course, user, request=None, include_past_dates=False, num_assignments=None):
     """
     Return the list of blocks to display on the course info page,
     sorted by date.
@@ -405,17 +409,53 @@ def get_course_date_blocks(course, user):
     if certs_api.get_active_web_certificate(course):
         block_classes.insert(0, CertificateAvailableDate)
 
-    blocks = (cls(course, user) for cls in block_classes)
+    blocks = [cls(course, user) for cls in block_classes]
+    if DATE_WIDGET_V2_FLAG.is_enabled(course.id):
+        blocks.append(CourseExpiredDate(course, user))
+        blocks.extend(get_course_assignment_due_dates(
+            course, user, request, num_return=num_assignments, include_past_dates=include_past_dates))
 
-    def block_key_fn(block):
-        """
-        If the block's date is None, return the maximum datetime in order
-        to force it to the end of the list of displayed blocks.
-        """
-        if block.date is None:
-            return datetime.max.replace(tzinfo=pytz.UTC)
-        return block.date
-    return sorted((b for b in blocks if b.is_enabled), key=block_key_fn)
+    return sorted((b for b in blocks if b.date and (b.is_enabled or include_past_dates)), key=date_block_key_fn)
+
+
+def date_block_key_fn(block):
+    """
+    If the block's date is None, return the maximum datetime in order
+    to force it to the end of the list of displayed blocks.
+    """
+    return block.date or datetime.max.replace(tzinfo=pytz.UTC)
+
+
+def get_course_assignment_due_dates(course, user, request, num_return=None, include_past_dates=False):
+    """
+    Returns a list of assignment (at the subsection/sequential level) due date
+    blocks for the given course. Will return num_return results or all results
+    if num_return is None in date increasing order.
+    """
+    store = modulestore()
+    all_course_dates = get_dates_for_course(course.id, user)
+    date_blocks = []
+    for (block_key, date_type), date in all_course_dates.items():
+        if date_type == 'due' and block_key.block_type == 'sequential':
+            item = store.get_item(block_key)
+            if item.graded:
+                date_block = CourseAssignmentDate(course, user)
+                date_block.date = date
+
+                block_url = None
+                now = datetime.now().replace(tzinfo=pytz.UTC)
+                assignment_released = item.start < now if item.start else None
+                if assignment_released:
+                    block_url = reverse('jump_to', args=[course.id, block_key])
+                    block_url = request.build_absolute_uri(block_url) if request else None
+                assignment_title = item.display_name if item.display_name else _('Assignment')
+                date_block.set_title(assignment_title, link=block_url)
+
+                date_blocks.append(date_block)
+    date_blocks = sorted((b for b in date_blocks if b.is_enabled or include_past_dates), key=date_block_key_fn)
+    if num_return:
+        return date_blocks[:num_return]
+    return date_blocks
 
 
 # TODO: Fix this such that these are pulled in as extra course-specific tabs.
diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py
index ffcb774209a38db6626058478a14a46eff1e86ef..5746495de7eea273fcaf1e0812d42b8a4dcdcd6a 100644
--- a/lms/djangoapps/courseware/date_summary.py
+++ b/lms/djangoapps/courseware/date_summary.py
@@ -20,12 +20,15 @@ from lazy import lazy
 from pytz import utc
 
 from course_modes.models import CourseMode, get_cosmetic_verified_display_price
-from lms.djangoapps.commerce.utils import EcommerceService
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
 from lms.djangoapps.verify_student.models import VerificationDeadline
 from lms.djangoapps.verify_student.services import IDVerificationService
+from openedx.core.djangoapps.catalog.utils import get_course_run_details
 from openedx.core.djangoapps.certificates.api import can_show_certificate_available_date_field
 from openedx.core.djangolib.markup import HTML, Text
-from openedx.features.course_experience import UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages
+from openedx.features.course_duration_limits.access import get_user_course_expiration_date
+from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
+from openedx.features.course_experience import DATE_WIDGET_V2_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages
 from student.models import CourseEnrollment
 
 from .context_processor import user_timezone_locale_prefs
@@ -59,6 +62,11 @@ class DateSummary(object):
         """The title of this summary."""
         return ''
 
+    @property
+    def title_html(self):
+        """The title as html for this summary."""
+        return ''
+
     @property
     def description(self):
         """The detail text displayed by this summary."""
@@ -287,6 +295,14 @@ class CourseEndDate(DateSummary):
 
     @property
     def date(self):
+        if DATE_WIDGET_V2_FLAG.is_enabled(self.course_id) and self.course.self_paced:
+            weeks_to_complete = get_course_run_details(self.course.id, ['weeks_to_complete']).get('weeks_to_complete')
+            if weeks_to_complete:
+                course_duration = datetime.timedelta(weeks=weeks_to_complete)
+                if self.course.end < (self.current_time + course_duration):
+                    return self.course.end
+                return None
+
         return self.course.end
 
     def register_alerts(self, request, course):
@@ -318,6 +334,65 @@ class CourseEndDate(DateSummary):
                 )
 
 
+class CourseAssignmentDate(DateSummary):
+    """
+    Displays due dates for homework assignments with a link to the homework
+    assignment if the link is provided.
+    """
+    css_class = 'assignment'
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.assignment_date = None
+        self.assignment_title = None
+        self.assignment_title_html = None
+
+    @property
+    def date(self):
+        return self.assignment_date
+
+    @date.setter
+    def date(self, date):
+        self.assignment_date = date
+
+    @property
+    def title(self):
+        return self.assignment_title
+
+    @property
+    def title_html(self):
+        return self.assignment_title_html
+
+    def set_title(self, title, link=None):
+        """ Used to set the title_html and title properties for the assignment date block """
+        if link:
+            self.assignment_title_html = HTML(
+                '<a href="{assignment_link}">{assignment_title}</a>'
+            ).format(assignment_link=link, assignment_title=title)
+        self.assignment_title = title
+
+
+class CourseExpiredDate(DateSummary):
+    """
+    Displays the course expiration date for Audit learners (if enabled)
+    """
+    css_class = 'course-expired'
+
+    @property
+    def date(self):
+        if not CourseDurationLimitConfig.enabled_for_enrollment(user=self.user, course_key=self.course_id):
+            return
+        return get_user_course_expiration_date(self.user, self.course)
+
+    @property
+    def description(self):
+        return _('You lose all access to this course, including your progress.')
+
+    @property
+    def title(self):
+        return _('Audit Access Expires')
+
+
 class CertificateAvailableDate(DateSummary):
     """
     Displays the certificate available date of the course.
@@ -384,53 +459,6 @@ class CertificateAvailableDate(DateSummary):
             )
 
 
-def verified_upgrade_deadline_link(user, course=None, course_id=None):
-    """
-    Format the correct verified upgrade link for the specified ``user``
-    in a course.
-
-    One of ``course`` or ``course_id`` must be supplied. If both are specified,
-    ``course`` will take priority.
-
-    Arguments:
-        user (:class:`~django.contrib.auth.models.User`): The user to display
-            the link for.
-        course (:class:`.CourseOverview`): The course to render a link for.
-        course_id (:class:`.CourseKey`): The course_id of the course to render for.
-
-    Returns:
-        The formatted link that will allow the user to upgrade to verified
-        in this course.
-    """
-    if course is not None:
-        course_id = course.id
-    return EcommerceService().upgrade_url(user, course_id)
-
-
-def verified_upgrade_link_is_valid(enrollment=None):
-    """
-    Return whether this enrollment can be upgraded.
-
-    Arguments:
-        enrollment (:class:`.CourseEnrollment`): The enrollment under consideration.
-            If None, then the enrollment is considered to be upgradeable.
-    """
-    # Return `true` if user is not enrolled in course
-    if enrollment is None:
-        return False
-
-    upgrade_deadline = enrollment.upgrade_deadline
-
-    if upgrade_deadline is None:
-        return False
-
-    if datetime.datetime.now(utc).date() > upgrade_deadline.date():
-        return False
-
-    # Show the summary if user enrollment is in which allow user to upsell
-    return enrollment.is_active and enrollment.mode in CourseMode.UPSELL_TO_VERIFIED_MODES
-
-
 class VerifiedUpgradeDeadlineDate(DateSummary):
     """
     Displays the date before which learners must upgrade to the
diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py
index 3918d305765089884a0cbe56f99f527abd2c1314..4a7d86e86587eaf54226e543fc5a56d90322caa3 100644
--- a/lms/djangoapps/courseware/tests/helpers.py
+++ b/lms/djangoapps/courseware/tests/helpers.py
@@ -21,7 +21,7 @@ from xblock.field_data import DictFieldData
 
 from edxmako.shortcuts import render_to_string
 from lms.djangoapps.courseware.access import has_access
-from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
 from lms.djangoapps.courseware.masquerade import handle_ajax, setup_masquerade
 from lms.djangoapps.lms_xblock.field_data import LmsFieldData
 from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py
index 7f39f211dcc667bf6941a5ccc63c33010d24595e..48774aac3c3e3d34db496c91db66f6611f36c399 100644
--- a/lms/djangoapps/courseware/tests/test_date_summary.py
+++ b/lms/djangoapps/courseware/tests/test_date_summary.py
@@ -18,7 +18,9 @@ from course_modes.tests.factories import CourseModeFactory
 from lms.djangoapps.courseware.courses import get_course_date_blocks
 from lms.djangoapps.courseware.date_summary import (
     CertificateAvailableDate,
+    CourseAssignmentDate,
     CourseEndDate,
+    CourseExpiredDate,
     CourseStartDate,
     TodaysDate,
     VerificationDeadlineDate,
@@ -38,10 +40,13 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
 from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
 from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
 from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
-from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages
+from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
+from openedx.features.course_experience import (
+    DATE_WIDGET_V2_FLAG, UNIFIED_COURSE_TAB_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages
+)
 from student.tests.factories import TEST_PASSWORD, CourseEnrollmentFactory, UserFactory
 from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory
+from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 
 
 @ddt.ddt
@@ -129,6 +134,174 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
         CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED)
         self.assert_block_types(course, user, expected_blocks)
 
+    @override_waffle_flag(DATE_WIDGET_V2_FLAG, active=True)
+    def test_enabled_block_types_with_assignments(self):  # pylint: disable=too-many-statements
+        """
+        Creates a course with multiple subsections to test all of the different
+        cases for assignment dates showing up. Mocks out calling the edx-when
+        service and then validates the correct data is set and returned.
+        """
+        course = create_course_run(days_till_start=-100)
+        user = create_user()
+        request = RequestFactory().request()
+        request.user = user
+        CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED)
+        now = datetime.now(utc)
+        assignment_title_html = ['<a href=', '</a>']
+        with self.store.bulk_operations(course.id):
+            section = ItemFactory.create(category='chapter', parent_location=course.location)
+            subsection_1 = ItemFactory.create(
+                category='sequential',
+                display_name='Released',
+                parent_location=section.location,
+                start=now - timedelta(days=1),
+                due=now + timedelta(days=6),
+                graded=True,
+            )
+            subsection_2 = ItemFactory.create(
+                category='sequential',
+                display_name='Not released',
+                parent_location=section.location,
+                start=now + timedelta(days=1),
+                due=now + timedelta(days=7),
+                graded=True,
+            )
+            subsection_3 = ItemFactory.create(
+                category='sequential',
+                display_name='Third nearest assignment',
+                parent_location=section.location,
+                start=now + timedelta(days=1),
+                due=now + timedelta(days=8),
+                graded=True,
+            )
+            subsection_4 = ItemFactory.create(
+                category='sequential',
+                display_name='Past due date',
+                parent_location=section.location,
+                start=now - timedelta(days=14),
+                due=now - timedelta(days=7),
+                graded=True,
+            )
+            subsection_5 = ItemFactory.create(
+                category='sequential',
+                display_name='Not returned since we do not get non-graded subsections',
+                parent_location=section.location,
+                start=now + timedelta(days=1),
+                due=now - timedelta(days=7),
+                graded=False,
+            )
+            subsection_6 = ItemFactory.create(
+                category='sequential',
+                display_name='No start date',
+                parent_location=section.location,
+                start=None,
+                due=now + timedelta(days=9),
+                graded=True,
+            )
+            subsection_7 = ItemFactory.create(
+                category='sequential',
+                # Setting display name to None should set the assignment title to 'Assignment'
+                display_name=None,
+                parent_location=section.location,
+                start=now - timedelta(days=14),
+                due=now + timedelta(days=10),
+                graded=True,
+            )
+            dummy_subsection = ItemFactory.create(category='sequential')
+
+        with patch('lms.djangoapps.courseware.courses.get_dates_for_course') as mock_get_dates:
+            mock_get_dates.return_value = {
+                (subsection_1.location, 'due'): subsection_1.due,
+                (subsection_1.location, 'start'): subsection_1.start,
+                (subsection_2.location, 'due'): subsection_2.due,
+                (subsection_2.location, 'start'): subsection_2.start,
+                (subsection_3.location, 'due'): subsection_3.due,
+                (subsection_3.location, 'start'): subsection_3.start,
+                (subsection_4.location, 'due'): subsection_4.due,
+                (subsection_4.location, 'start'): subsection_4.start,
+                (subsection_5.location, 'due'): subsection_5.due,
+                (subsection_5.location, 'start'): subsection_5.start,
+                (subsection_6.location, 'due'): subsection_6.due,
+                (subsection_7.location, 'due'): subsection_7.due,
+                (subsection_7.location, 'start'): subsection_7.start,
+                # Adding this in for the case where we return a block that
+                # doesn't actually exist as part of the course. Should just be ignored.
+                (dummy_subsection.location, 'due'): dummy_subsection.due,
+            }
+            # Standard widget case where we restrict the number of assignments.
+            expected_blocks = (
+                TodaysDate, CourseAssignmentDate, CourseAssignmentDate, CourseEndDate, VerificationDeadlineDate
+            )
+            blocks = get_course_date_blocks(course, user, request, num_assignments=2)
+            self.assertEqual(len(blocks), len(expected_blocks))
+            self.assertEqual(set(type(b) for b in blocks), set(expected_blocks))
+            assignment_blocks = filter(lambda b: isinstance(b, CourseAssignmentDate), blocks)
+            for assignment in assignment_blocks:
+                assignment_title = str(assignment.title_html) or str(assignment.title)
+                self.assertNotEqual(assignment_title, 'Third nearest assignment')
+                self.assertNotEqual(assignment_title, 'Past due date')
+                self.assertNotEqual(assignment_title, 'Not returned since we do not get non-graded subsections')
+                # checking if it is _in_ the title instead of being the title since released assignments
+                # are actually links. Unreleased assignments are just the string of the title.
+                if 'Released' in assignment_title:
+                    for html_tag in assignment_title_html:
+                        self.assertIn(html_tag, assignment_title)
+                elif assignment_title == 'Not released':
+                    for html_tag in assignment_title_html:
+                        self.assertNotIn(html_tag, assignment_title)
+
+            # No restrictions on number of assignments to return
+            expected_blocks = (
+                CourseStartDate, TodaysDate, CourseAssignmentDate, CourseAssignmentDate, CourseAssignmentDate,
+                CourseAssignmentDate, CourseAssignmentDate, CourseAssignmentDate, CourseEndDate,
+                VerificationDeadlineDate
+            )
+            blocks = get_course_date_blocks(course, user, request, include_past_dates=True)
+            self.assertEqual(len(blocks), len(expected_blocks))
+            self.assertEqual(set(type(b) for b in blocks), set(expected_blocks))
+            assignment_blocks = filter(lambda b: isinstance(b, CourseAssignmentDate), blocks)
+            for assignment in assignment_blocks:
+                assignment_title = str(assignment.title_html) or str(assignment.title)
+                self.assertNotEqual(assignment_title, 'Not returned since we do not get non-graded subsections')
+                # checking if it is _in_ the title instead of being the title since released assignments
+                # are actually links. Unreleased assignments are just the string of the title.
+                if 'Released' in assignment_title:
+                    for html_tag in assignment_title_html:
+                        self.assertIn(html_tag, assignment_title)
+                elif assignment_title == 'Not released':
+                    for html_tag in assignment_title_html:
+                        self.assertNotIn(html_tag, assignment_title)
+                elif assignment_title == 'Third nearest assignment':
+                    # It's still not released
+                    for html_tag in assignment_title_html:
+                        self.assertNotIn(html_tag, assignment_title)
+                elif 'Past due date' in assignment_title:
+                    self.assertGreater(now, assignment.date)
+                    for html_tag in assignment_title_html:
+                        self.assertIn(html_tag, assignment_title)
+                elif 'No start date' == assignment_title:
+                    # Can't determine if it is released so it does not get a link
+                    for html_tag in assignment_title_html:
+                        self.assertNotIn(html_tag, assignment_title)
+                # This is the item with no display name where we set one ourselves.
+                elif 'Assignment' in assignment_title:
+                    # Can't determine if it is released so it does not get a link
+                    for html_tag in assignment_title_html:
+                        self.assertIn(html_tag, assignment_title)
+
+    @override_waffle_flag(DATE_WIDGET_V2_FLAG, active=True)
+    def test_enabled_block_types_with_expired_course(self):
+        course = create_course_run(days_till_start=-100)
+        user = create_user()
+        # These two lines are to trigger the course expired block to be rendered
+        CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.AUDIT)
+        CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=utc))
+
+        expected_blocks = (
+            TodaysDate, CourseEndDate, CourseExpiredDate, VerifiedUpgradeDeadlineDate
+        )
+        self.assert_block_types(course, user, expected_blocks)
+
     @ddt.data(
         # Course not started
         ({}, (CourseStartDate, TodaysDate, CourseEndDate)),
@@ -177,7 +350,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
 
             html_elements = [
                 '<h3 class="hd hd-6 handouts-header">Upcoming Dates</h3>',
-                '<div class="date-summary-container">',
+                '<div class="date-summary',
                 '<p class="hd hd-6 date localized-datetime"',
                 'data-timezone="None"'
             ]
@@ -202,7 +375,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
 
             html_elements = [
                 '<h3 class="hd hd-6 handouts-header">Upcoming Dates</h3>',
-                '<div class="date-summary-container">',
+                '<div class="date-summary',
                 '<p class="hd hd-6 date localized-datetime"',
                 'data-timezone="America/Los_Angeles"'
             ]
@@ -287,6 +460,33 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
         )
         self.assertEqual(block.title, 'Course End')
 
+    @ddt.data(
+        {'weeks_to_complete': 7},  # Weeks to complete > time til end (end date shown)
+        {'weeks_to_complete': 4},  # Weeks to complete < time til end (end date not shown)
+    )
+    @override_waffle_flag(DATE_WIDGET_V2_FLAG, active=True)
+    def test_course_end_date_self_paced(self, cr_details):
+        """
+        In self-paced courses, the end date will now only show up if the learner
+        views the course within the course's weeks to complete (as defined in
+        the course-discovery service). E.g. if the weeks to complete is 5 weeks
+        and the course doesn't end for 10 weeks, there will be no end date, but
+        if the course ends in 3 weeks, the end date will appear.
+        """
+        now = datetime.now(utc)
+        end_timedelta_number = 5
+        course = CourseFactory.create(
+            start=now + timedelta(days=-7), end=now + timedelta(weeks=end_timedelta_number), self_paced=True)
+        user = create_user()
+        with patch('lms.djangoapps.courseware.date_summary.get_course_run_details') as mock_get_cr_details:
+            mock_get_cr_details.return_value = cr_details
+            block = CourseEndDate(course, user)
+            self.assertEqual(block.title, 'Course End')
+            if cr_details['weeks_to_complete'] > end_timedelta_number:
+                self.assertEqual(block.date, course.end)
+            else:
+                self.assertIsNone(block.date)
+
     def test_ecommerce_checkout_redirect(self):
         """Verify the block link redirects to ecommerce checkout if it's enabled."""
         sku = 'TESTSKU'
diff --git a/lms/djangoapps/courseware/utils.py b/lms/djangoapps/courseware/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..a378ace954fb2af9f46e4ce168ebeb672802d4e7
--- /dev/null
+++ b/lms/djangoapps/courseware/utils.py
@@ -0,0 +1,56 @@
+"""Utility functions that have to do with the courseware."""
+
+
+import datetime
+
+from lms.djangoapps.commerce.utils import EcommerceService
+from pytz import utc
+
+from course_modes.models import CourseMode
+
+
+def verified_upgrade_deadline_link(user, course=None, course_id=None):
+    """
+    Format the correct verified upgrade link for the specified ``user``
+    in a course.
+
+    One of ``course`` or ``course_id`` must be supplied. If both are specified,
+    ``course`` will take priority.
+
+    Arguments:
+        user (:class:`~django.contrib.auth.models.User`): The user to display
+            the link for.
+        course (:class:`.CourseOverview`): The course to render a link for.
+        course_id (:class:`.CourseKey`): The course_id of the course to render for.
+
+    Returns:
+        The formatted link that will allow the user to upgrade to verified
+        in this course.
+    """
+    if course is not None:
+        course_id = course.id
+    return EcommerceService().upgrade_url(user, course_id)
+
+
+def verified_upgrade_link_is_valid(enrollment=None):
+    """
+    Return whether this enrollment can be upgraded.
+
+    Arguments:
+        enrollment (:class:`.CourseEnrollment`): The enrollment under consideration.
+            If None, then the enrollment is considered to be upgradeable.
+    """
+    # Return `true` if user is not enrolled in course
+    if enrollment is None:
+        return False
+
+    upgrade_deadline = enrollment.upgrade_deadline
+
+    if upgrade_deadline is None:
+        return False
+
+    if datetime.datetime.now(utc).date() > upgrade_deadline.date():
+        return False
+
+    # Show the summary if user enrollment is in which allow user to upsell
+    return enrollment.is_active and enrollment.mode in CourseMode.UPSELL_TO_VERIFIED_MODES
diff --git a/lms/djangoapps/experiments/utils.py b/lms/djangoapps/experiments/utils.py
index c0a524f75fae09d06bedba9a3b70ec461123773f..3e4420e33fd89917a7c893650243b03190ea6de3 100644
--- a/lms/djangoapps/experiments/utils.py
+++ b/lms/djangoapps/experiments/utils.py
@@ -13,7 +13,7 @@ from opaque_keys.edx.keys import CourseKey
 
 from course_modes.models import format_course_price, get_cosmetic_verified_display_price, CourseMode
 from lms.djangoapps.courseware.access import has_staff_access_to_preview_mode
-from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
 from entitlements.models import CourseEntitlement
 from lms.djangoapps.commerce.utils import EcommerceService
 from openedx.core.djangoapps.catalog.utils import get_programs
diff --git a/lms/djangoapps/experiments/views_custom.py b/lms/djangoapps/experiments/views_custom.py
index 99d20d8fac89576456fe66f9c541af3e2479ff6b..9e10d9d9cdbdc2b5e19bfc561946c6e318705361 100644
--- a/lms/djangoapps/experiments/views_custom.py
+++ b/lms/djangoapps/experiments/views_custom.py
@@ -23,7 +23,7 @@ from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiv
 from openedx.core.lib.api.permissions import ApiKeyHeaderPermissionIsAuthenticated
 from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
 
-from lms.djangoapps.courseware.date_summary import verified_upgrade_link_is_valid
+from lms.djangoapps.courseware.utils import verified_upgrade_link_is_valid
 from course_modes.models import get_cosmetic_verified_display_price
 from lms.djangoapps.commerce.utils import EcommerceService
 from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group
diff --git a/lms/static/sass/features/_course-experience.scss b/lms/static/sass/features/_course-experience.scss
index 3f91244b4f951c95492a41c4c63cb7c9ff04b7a7..b1fe70780ba39768e3f1d6ec1cfe79a2f090ce26 100644
--- a/lms/static/sass/features/_course-experience.scss
+++ b/lms/static/sass/features/_course-experience.scss
@@ -14,8 +14,8 @@
       text-align: center;
     }
 
-    &:not(:first-child) {
-      margin-top: $baseline;
+    &:not(:last-child) {
+      margin-bottom: 32px;
     }
   }
 }
@@ -412,64 +412,69 @@
 }
 
 // date summary
-.date-summary-container {
-  .date-summary {
-    @include clearfix;
+.date-summary {
+  @include clearfix;
 
-    display: flex;
-    justify-content: space-between;
-    padding: $baseline/2 $baseline/2 $baseline/2 0;
-
-    .left-column {
-      flex: 5%;
-
-      .calendar-icon {
-        margin-top: 3px;
-        height: 1em;
-        width: auto;
-        background: url('#{$static-path}/images/calendar-alt-regular.svg');
-        background-repeat: no-repeat;
-      }
+  display: flex;
+  justify-content: space-between;
+  padding: 12px 0;
+  &:last-of-type {
+    padding-bottom: 0;
+  }
+
+  .left-column {
+    flex: 0 0 24px;
+
+    .calendar-icon {
+      margin-top: 4px;
+      height: 1em;
+      width: 16px;
+      background: url('#{$static-path}/images/calendar-alt-regular.svg');
+      background-repeat: no-repeat;
     }
+  }
 
-    .right-column {
-      flex: 85%;
+  .right-column {
+    flex: auto;
 
-      .localized-datetime {
-        font-weight: $font-weight-bold;
-        margin-bottom: 8px;
-      }
+    .localized-datetime {
+      font-weight: $font-weight-bold;
+      margin-bottom: 8px;
+    }
 
-      .heading {
-        font: -apple-system-body;
-        line-height: 1;
-        font-weight: $font-bold;
-        color: theme-color("dark");
-      }
+    .heading {
+      font: -apple-system-body;
+      line-height: 1.5;
+      font-weight: $font-bold;
+      color: theme-color("dark");
 
-      .description {
-        margin-bottom: $baseline/2;
-        display: inline-block;
+      a {
+        font-weight: $font-semibold;
       }
+    }
 
-      .heading, .description {
-          font-size: 0.9rem;
-      }
+    .description {
+      margin-bottom: 0;
+      display: inline-block;
+    }
 
-      .date-summary-link {
-        font-weight: $font-semibold;
+    .heading, .description {
+      font-size: 0.9rem;
+    }
 
-        a {
-          color: $link-color;
-          font-weight: $font-regular;
-        }
+    .date-summary-link {
+      font-weight: $font-semibold;
+
+      a {
+        color: $link-color;
+        font-size: 0.9rem;
       }
     }
+  }
 
-    .date {
-      color: theme-color("dark");
-      font: -apple-system-body;
-    }
+  .date {
+    color: theme-color("dark");
+    font: -apple-system-body;
   }
 }
 
diff --git a/openedx/core/djangoapps/schedules/resolvers.py b/openedx/core/djangoapps/schedules/resolvers.py
index c7b22a07d0eb208215978ba70e363908784e8859..6437e282385703f3abfd6d11c8a743f368b0b04b 100644
--- a/openedx/core/djangoapps/schedules/resolvers.py
+++ b/openedx/core/djangoapps/schedules/resolvers.py
@@ -14,7 +14,7 @@ from edx_ace.recipient import Recipient
 from edx_ace.recipient_resolver import RecipientResolver
 from edx_django_utils.monitoring import function_trace, set_custom_metric
 
-from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
 from lms.djangoapps.discussion.notification_prefs.views import UsernameCipher
 from openedx.core.djangoapps.ace_common.template_context import get_base_template_context
 from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH
diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py
index a528416fb4a6a49a1ed9dd83e353f6d7123c2972..8f69656b5a1c30e4e94276d1a6a0cab9fe4fc96d 100644
--- a/openedx/features/course_duration_limits/access.py
+++ b/openedx/features/course_duration_limits/access.py
@@ -17,7 +17,7 @@ from web_fragments.fragment import Fragment
 from course_modes.models import CourseMode
 from lms.djangoapps.courseware.access_response import AccessError
 from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED
-from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
 from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_specific_student
 from openedx.core.djangoapps.catalog.utils import get_course_run_details
 from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py
index a419ca520e3748dfce6865ff1b679c9a2797cb55..034541e53e092c4ebf8292b484941a385b8f51e8 100644
--- a/openedx/features/course_experience/__init__.py
+++ b/openedx/features/course_experience/__init__.py
@@ -81,6 +81,10 @@ COURSE_ENABLE_UNENROLLED_ACCESS_FLAG = CourseWaffleFlag(SEO_WAFFLE_FLAG_NAMESPAC
 # Waffle flag to enable relative dates for course content
 RELATIVE_DATES_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'relative_dates')
 
+# Waffle flag to enable showing FBE messaging, assignment due dates, and modified
+# end date logic (for self-paced courses) in the date widget
+DATE_WIDGET_V2_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'date_widget_v2')
+
 
 def course_home_page_title(course):  # pylint: disable=unused-argument
     """
diff --git a/openedx/features/course_experience/templates/course_experience/dates-summary.html b/openedx/features/course_experience/templates/course_experience/dates-summary.html
index 36952923c8186fec08f888a0f41923dfcd1b1574..0cfa204bccdda6c9de2654eba248e97e7c7007b1 100644
--- a/openedx/features/course_experience/templates/course_experience/dates-summary.html
+++ b/openedx/features/course_experience/templates/course_experience/dates-summary.html
@@ -2,26 +2,26 @@
 from django.utils.translation import ugettext as _
 %>
 <%page args="course_date" expression_filter="h"/>
-<div class="date-summary-container">
-    <div class="date-summary date-summary-${course_date.css_class}">
-        <div class="left-column">
-            <div class="calendar-icon"></div>
-        </div>
-        <div class="right-column">
-            % if course_date.date:
-                <p class="hd hd-6 date localized-datetime" data-format="shortDate" data-datetime="${course_date.date}" data-timezone="${user_timezone}" data-language="${user_language}"></p>
-            % endif
-            % if course_date.title:
-                <span class="hd hd-6 heading">${course_date.title}</span>
-            % endif
-            % if course_date.description:
-                <p class="description">${course_date.description}</p>
-            % endif
-            % if course_date.link and course_date.link_text:
-                <span class="date-summary-link">
-                    <a href="${course_date.link}">${course_date.link_text}</a>
-                </span>
-            % endif
-        </div>
+<div class="date-summary date-summary-${course_date.css_class}">
+    <div class="left-column">
+        <div class="calendar-icon"></div>
+    </div>
+    <div class="right-column">
+        % if course_date.date:
+            <p class="hd hd-6 date localized-datetime" data-format="shortDate" data-datetime="${course_date.date}" data-timezone="${user_timezone}" data-language="${user_language}"></p>
+        % endif
+        % if course_date.title_html:
+            <div class="hd hd-6 heading">${course_date.title_html}</div>
+        % elif course_date.title:
+            <div class="hd hd-6 heading">${course_date.title}</div>
+        % endif
+        % if course_date.description:
+            <p class="description">${course_date.description}</p>
+        % endif
+        % if course_date.link and course_date.link_text:
+            <div class="date-summary-link">
+                <a href="${course_date.link}">${course_date.link_text}</a>
+            </div>
+        % endif
     </div>
 </div>
diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py
index ea0e6e7a4bb4b636ccf4e0a2efcb334f585b4ba3..8b88c99381e2f2bcffa8d166b695aedef8d3ab66 100644
--- a/openedx/features/course_experience/tests/views/test_course_home.py
+++ b/openedx/features/course_experience/tests/views/test_course_home.py
@@ -25,7 +25,7 @@ from experiments.models import ExperimentData
 from lms.djangoapps.commerce.models import CommerceConfiguration
 from lms.djangoapps.commerce.utils import EcommerceService
 from lms.djangoapps.course_goals.api import add_course_goal, remove_course_goal
-from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
 from lms.djangoapps.courseware.tests.factories import (
     BetaTesterFactory,
     GlobalStaffFactory,
diff --git a/openedx/features/course_experience/views/course_dates.py b/openedx/features/course_experience/views/course_dates.py
index 204a273494579137079fcbfe6a1c37d938213e41..91c2bbfd21a8660c66cf60988e2c2b0b2111f98b 100644
--- a/openedx/features/course_experience/views/course_dates.py
+++ b/openedx/features/course_experience/views/course_dates.py
@@ -25,7 +25,7 @@ class CourseDatesFragmentView(EdxFragmentView):
         """
         course_key = CourseKey.from_string(course_id)
         course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False)
-        course_date_blocks = get_course_date_blocks(course, request.user)
+        course_date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=2)
 
         context = {
             'course_date_blocks': [block for block in course_date_blocks if block.title != 'current_datetime']
diff --git a/openedx/features/course_experience/views/course_sock.py b/openedx/features/course_experience/views/course_sock.py
index 695aef426b896e5d6e3f1ac0f67c80771f88a96f..a41d9b8f819e59b4100a96c9ce6a841f66e6a6bd 100644
--- a/openedx/features/course_experience/views/course_sock.py
+++ b/openedx/features/course_experience/views/course_sock.py
@@ -6,7 +6,7 @@ Fragment for rendering the course's sock and associated toggle button.
 from django.template.loader import render_to_string
 from web_fragments.fragment import Fragment
 
-from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid
 from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
 from openedx.features.discounts.utils import format_strikeout_price
 from student.models import CourseEnrollment
diff --git a/openedx/features/discounts/utils.py b/openedx/features/discounts/utils.py
index 8b9840e00e935a8caf4bb804db1b53bfa31f4529..53ac8a898e7f70d317c4db19b3d1891c116793d7 100644
--- a/openedx/features/discounts/utils.py
+++ b/openedx/features/discounts/utils.py
@@ -11,7 +11,7 @@ from edx_django_utils.cache import RequestCache
 import pytz
 
 from course_modes.models import get_course_prices, format_course_price
-from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
+from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link
 from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
 from experiments.models import ExperimentData
 
diff --git a/scripts/thresholds.sh b/scripts/thresholds.sh
index 6f8bfb89d4b2d18c9173b041c3e9946bdaf54b70..31d9d2fb008cb6a2ab84c6747b250c61cbeb101a 100755
--- a/scripts/thresholds.sh
+++ b/scripts/thresholds.sh
@@ -2,6 +2,6 @@
 set -e
 
 export LOWER_PYLINT_THRESHOLD=1000
-export UPPER_PYLINT_THRESHOLD=2005
+export UPPER_PYLINT_THRESHOLD=1985
 export ESLINT_THRESHOLD=5530
 export STYLELINT_THRESHOLD=880