Revert "Merge pull request #14078 from edx/yro_remove-datetimetext-functions"
This reverts commit8c0098812d, reversing changes made to5b6e2dd5ee.
This commit is contained in:
@@ -10,8 +10,11 @@ from datetime import datetime, timedelta
|
||||
import dateutil.parser
|
||||
from math import exp
|
||||
|
||||
from openedx.core.lib.time_zone_utils import get_time_zone_abbr
|
||||
from pytz import utc
|
||||
|
||||
from .fields import Date
|
||||
|
||||
DEFAULT_START_DATE = datetime(2030, 1, 1, tzinfo=utc)
|
||||
|
||||
|
||||
@@ -93,6 +96,83 @@ def course_start_date_is_default(start, advertised_start):
|
||||
return advertised_start is None and start == DEFAULT_START_DATE
|
||||
|
||||
|
||||
def _datetime_to_string(date_time, format_string, time_zone, strftime_localized):
|
||||
"""
|
||||
Formats the given datetime with the given function and format string.
|
||||
|
||||
Adds time zone abbreviation to the resulting string if the format is DATE_TIME or TIME.
|
||||
|
||||
Arguments:
|
||||
date_time (datetime): the datetime to be formatted
|
||||
format_string (str): the date format type, as passed to strftime
|
||||
time_zone (pytz time zone): the time zone to convert to
|
||||
strftime_localized ((datetime, str) -> str): a nm localized string
|
||||
formatting function
|
||||
"""
|
||||
result = strftime_localized(date_time.astimezone(time_zone), format_string)
|
||||
abbr = get_time_zone_abbr(time_zone, date_time)
|
||||
return (
|
||||
result + ' ' + abbr if format_string in ['DATE_TIME', 'TIME', 'DAY_AND_TIME']
|
||||
else result
|
||||
)
|
||||
|
||||
|
||||
def course_start_datetime_text(start_date, advertised_start, format_string, time_zone, ugettext, strftime_localized):
|
||||
"""
|
||||
Calculates text to be shown to user regarding a course's start
|
||||
datetime in specified time zone.
|
||||
|
||||
Prefers .advertised_start, then falls back to .start.
|
||||
|
||||
Arguments:
|
||||
start_date (datetime): the course's start datetime
|
||||
advertised_start (str): the course's advertised start date
|
||||
format_string (str): the date format type, as passed to strftime
|
||||
time_zone (pytz time zone): the time zone to convert to
|
||||
ugettext ((str) -> str): a text localization function
|
||||
strftime_localized ((datetime, str) -> str): a localized string
|
||||
formatting function
|
||||
"""
|
||||
if advertised_start is not None:
|
||||
# TODO: This will return an empty string if advertised_start == ""... consider changing this behavior?
|
||||
try:
|
||||
# from_json either returns a Date, returns None, or raises a ValueError
|
||||
parsed_advertised_start = Date().from_json(advertised_start)
|
||||
if parsed_advertised_start is not None:
|
||||
# In the Django implementation of strftime_localized, if
|
||||
# the year is <1900, _datetime_to_string will raise a ValueError.
|
||||
return _datetime_to_string(parsed_advertised_start, format_string, time_zone, strftime_localized)
|
||||
except ValueError:
|
||||
pass
|
||||
return advertised_start.title()
|
||||
elif start_date != DEFAULT_START_DATE:
|
||||
return _datetime_to_string(start_date, format_string, time_zone, strftime_localized)
|
||||
else:
|
||||
_ = ugettext
|
||||
# Translators: TBD stands for 'To Be Determined' and is used when a course
|
||||
# does not yet have an announced start date.
|
||||
return _('TBD')
|
||||
|
||||
|
||||
def course_end_datetime_text(end_date, format_string, time_zone, strftime_localized):
|
||||
"""
|
||||
Returns a formatted string for a course's end date or datetime.
|
||||
|
||||
If end_date is None, an empty string will be returned.
|
||||
|
||||
Arguments:
|
||||
end_date (datetime): the end datetime of a course
|
||||
format_string (str): the date format type, as passed to strftime
|
||||
time_zone (pytz time zone): the time zone to convert to
|
||||
strftime_localized ((datetime, str) -> str): a localized string
|
||||
formatting function
|
||||
"""
|
||||
return (
|
||||
_datetime_to_string(end_date, format_string, time_zone, strftime_localized) if end_date is not None
|
||||
else ''
|
||||
)
|
||||
|
||||
|
||||
def may_certify_for_course(certificates_display_behavior, certificates_show_before_end, has_ended):
|
||||
"""
|
||||
Returns whether it is acceptable to show the student a certificate download
|
||||
|
||||
@@ -197,11 +197,10 @@ class CourseFields(object):
|
||||
scope=Scope.settings,
|
||||
)
|
||||
advertised_start = String(
|
||||
display_name=_("Course Advertised Start"),
|
||||
display_name=_("Course Advertised Start Date"),
|
||||
help=_(
|
||||
"Enter the text that you want to use as the advertised starting time frame for the course, "
|
||||
"such as \"Winter 2018\". If you enter null for this value, the start date that you have set "
|
||||
"for this course is used."
|
||||
"Enter the date you want to advertise as the course start date, if this date is different from the set "
|
||||
"start date. To advertise the set start date, enter null."
|
||||
),
|
||||
scope=Scope.settings
|
||||
)
|
||||
@@ -1212,6 +1211,21 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
|
||||
"""Return the course_id for this course"""
|
||||
return self.location.course_key
|
||||
|
||||
def start_datetime_text(self, format_string="SHORT_DATE", time_zone=utc):
|
||||
"""
|
||||
Returns the desired text corresponding the course's start date and time in specified time zone, defaulted
|
||||
to UTC. Prefers .advertised_start, then falls back to .start
|
||||
"""
|
||||
i18n = self.runtime.service(self, "i18n")
|
||||
return course_metadata_utils.course_start_datetime_text(
|
||||
self.start,
|
||||
self.advertised_start,
|
||||
format_string,
|
||||
time_zone,
|
||||
i18n.ugettext,
|
||||
i18n.strftime
|
||||
)
|
||||
|
||||
@property
|
||||
def start_date_is_still_default(self):
|
||||
"""
|
||||
@@ -1223,6 +1237,17 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
|
||||
self.advertised_start
|
||||
)
|
||||
|
||||
def end_datetime_text(self, format_string="SHORT_DATE", time_zone=utc):
|
||||
"""
|
||||
Returns the end date or date_time for the course formatted as a string.
|
||||
"""
|
||||
return course_metadata_utils.course_end_datetime_text(
|
||||
self.end,
|
||||
format_string,
|
||||
time_zone,
|
||||
self.runtime.service(self, "i18n").strftime
|
||||
)
|
||||
|
||||
def get_discussion_blackout_datetimes(self):
|
||||
"""
|
||||
Get a list of dicts with start and end fields with datetime values from
|
||||
|
||||
@@ -5,7 +5,7 @@ from collections import namedtuple
|
||||
from datetime import timedelta, datetime
|
||||
from unittest import TestCase
|
||||
|
||||
from pytz import utc
|
||||
from pytz import timezone, utc
|
||||
from xmodule.block_metadata_utils import (
|
||||
url_name_for_block,
|
||||
display_name_with_default,
|
||||
@@ -18,8 +18,11 @@ from xmodule.course_metadata_utils import (
|
||||
has_course_ended,
|
||||
DEFAULT_START_DATE,
|
||||
course_start_date_is_default,
|
||||
course_start_datetime_text,
|
||||
course_end_datetime_text,
|
||||
may_certify_for_course,
|
||||
)
|
||||
from xmodule.fields import Date
|
||||
from xmodule.modulestore.tests.utils import (
|
||||
MongoModulestoreBuilder,
|
||||
VersioningModulestoreBuilder,
|
||||
@@ -109,6 +112,12 @@ class CourseMetadataUtilsTestCase(TestCase):
|
||||
|
||||
test_datetime = datetime(1945, 2, 6, 4, 20, 00, tzinfo=utc)
|
||||
advertised_start_parsable = "2038-01-19 03:14:07"
|
||||
advertised_start_bad_date = "215-01-01 10:10:10"
|
||||
advertised_start_unparsable = "This coming fall"
|
||||
time_zone_normal_parsable = "2016-03-27 00:59:00"
|
||||
time_zone_normal_datetime = datetime(2016, 3, 27, 00, 59, 00, tzinfo=utc)
|
||||
time_zone_daylight_parsable = "2016-03-27 01:00:00"
|
||||
time_zone_daylight_datetime = datetime(2016, 3, 27, 1, 00, 00, tzinfo=utc)
|
||||
|
||||
FunctionTest = namedtuple('FunctionTest', 'function scenarios') # pylint: disable=invalid-name
|
||||
TestScenario = namedtuple('TestScenario', 'arguments expected_return') # pylint: disable=invalid-name
|
||||
@@ -160,6 +169,79 @@ class CourseMetadataUtilsTestCase(TestCase):
|
||||
TestScenario((DEFAULT_START_DATE, advertised_start_parsable), False),
|
||||
TestScenario((DEFAULT_START_DATE, None), True),
|
||||
]),
|
||||
FunctionTest(course_start_datetime_text, [
|
||||
# Test parsable advertised start date.
|
||||
# Expect start datetime to be parsed and formatted back into a string.
|
||||
TestScenario(
|
||||
(DEFAULT_START_DATE, advertised_start_parsable, 'DATE_TIME',
|
||||
utc, noop_gettext, mock_strftime_localized),
|
||||
mock_strftime_localized(Date().from_json(advertised_start_parsable), 'DATE_TIME') + " UTC"
|
||||
),
|
||||
# Test un-parsable advertised start date.
|
||||
# Expect date parsing to throw a ValueError, and the advertised
|
||||
# start to be returned in Title Case.
|
||||
TestScenario(
|
||||
(test_datetime, advertised_start_unparsable, 'DATE_TIME',
|
||||
utc, noop_gettext, mock_strftime_localized),
|
||||
advertised_start_unparsable.title()
|
||||
),
|
||||
# Test parsable advertised start date from before January 1, 1900.
|
||||
# Expect mock_strftime_localized to throw a ValueError, and the
|
||||
# advertised start to be returned in Title Case.
|
||||
TestScenario(
|
||||
(test_datetime, advertised_start_bad_date, 'DATE_TIME',
|
||||
utc, noop_gettext, mock_strftime_localized),
|
||||
advertised_start_bad_date.title()
|
||||
),
|
||||
# Test without advertised start date, but with a set start datetime.
|
||||
# Expect formatted datetime to be returned.
|
||||
TestScenario(
|
||||
(test_datetime, None, 'SHORT_DATE', utc, noop_gettext, mock_strftime_localized),
|
||||
mock_strftime_localized(test_datetime, 'SHORT_DATE')
|
||||
),
|
||||
# Test without advertised start date and with default start datetime.
|
||||
# Expect TBD to be returned.
|
||||
TestScenario(
|
||||
(DEFAULT_START_DATE, None, 'SHORT_DATE', utc, noop_gettext, mock_strftime_localized),
|
||||
'TBD'
|
||||
),
|
||||
# Test correctly formatted start datetime is returned during normal daylight hours
|
||||
TestScenario(
|
||||
(DEFAULT_START_DATE, time_zone_normal_parsable, 'DATE_TIME',
|
||||
timezone('Europe/Paris'), noop_gettext, mock_strftime_localized),
|
||||
"DATE_TIME " + "2016-03-27 01:59:00 CET"
|
||||
),
|
||||
# Test correctly formatted start datetime is returned during daylight savings hours
|
||||
TestScenario(
|
||||
(DEFAULT_START_DATE, time_zone_daylight_parsable, 'DATE_TIME',
|
||||
timezone('Europe/Paris'), noop_gettext, mock_strftime_localized),
|
||||
"DATE_TIME " + "2016-03-27 03:00:00 CEST"
|
||||
)
|
||||
]),
|
||||
FunctionTest(course_end_datetime_text, [
|
||||
# Test with a set end datetime.
|
||||
# Expect formatted datetime to be returned.
|
||||
TestScenario(
|
||||
(test_datetime, 'TIME', utc, mock_strftime_localized),
|
||||
mock_strftime_localized(test_datetime, 'TIME') + " UTC"
|
||||
),
|
||||
# Test with default end datetime.
|
||||
# Expect empty string to be returned.
|
||||
TestScenario(
|
||||
(None, 'TIME', utc, mock_strftime_localized),
|
||||
""
|
||||
),
|
||||
# Test correctly formatted end datetime is returned during normal daylight hours
|
||||
TestScenario(
|
||||
(time_zone_normal_datetime, 'TIME', timezone('Europe/Paris'), mock_strftime_localized),
|
||||
"TIME " + "2016-03-27 01:59:00 CET"
|
||||
),
|
||||
# Test correctly formatted end datetime is returned during daylight savings hours
|
||||
TestScenario(
|
||||
(time_zone_daylight_datetime, 'TIME', timezone('Europe/Paris'), mock_strftime_localized),
|
||||
"TIME " + "2016-03-27 03:00:00 CEST"
|
||||
)
|
||||
]),
|
||||
FunctionTest(may_certify_for_course, [
|
||||
TestScenario(('early_with_info', True, True), True),
|
||||
TestScenario(('early_no_info', False, False), True),
|
||||
|
||||
@@ -6,7 +6,7 @@ from datetime import datetime, timedelta
|
||||
import itertools
|
||||
from fs.memoryfs import MemoryFS
|
||||
from mock import Mock, patch
|
||||
from pytz import utc
|
||||
from pytz import timezone, utc
|
||||
from xblock.runtime import KvsFieldData, DictKeyValueStore
|
||||
|
||||
import xmodule.course_module
|
||||
@@ -209,6 +209,36 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
(xmodule.course_module.CourseFields.start.default, 'January 2014', 'January 2014', False, 'January 2014'),
|
||||
]
|
||||
|
||||
@patch('xmodule.course_metadata_utils.datetime.now')
|
||||
def test_start_date_text(self, gmtime_mock):
|
||||
gmtime_mock.return_value = NOW
|
||||
for s in self.start_advertised_settings:
|
||||
d = get_dummy_course(start=s[0], advertised_start=s[1])
|
||||
print "Checking start=%s advertised=%s" % (s[0], s[1])
|
||||
self.assertEqual(d.start_datetime_text(), s[2])
|
||||
|
||||
@patch('xmodule.course_metadata_utils.datetime.now')
|
||||
def test_start_date_time_text(self, gmtime_mock):
|
||||
gmtime_mock.return_value = NOW
|
||||
for setting in self.start_advertised_settings:
|
||||
course = get_dummy_course(start=setting[0], advertised_start=setting[1])
|
||||
print "Checking start=%s advertised=%s" % (setting[0], setting[1])
|
||||
self.assertEqual(course.start_datetime_text("DATE_TIME"), setting[4])
|
||||
|
||||
@ddt.data(("2015-11-01T08:59", 'Nov 01, 2015', u'Nov 01, 2015 at 01:59 PDT'),
|
||||
("2015-11-01T09:00", 'Nov 01, 2015', u'Nov 01, 2015 at 01:00 PST'))
|
||||
@ddt.unpack
|
||||
def test_start_date_time_zone(self, course_date, expected_short_date, expected_date_time):
|
||||
"""
|
||||
Test that start datetime text correctly formats datetimes
|
||||
for normal daylight hours and daylight savings hours
|
||||
"""
|
||||
time_zone = timezone('America/Los_Angeles')
|
||||
|
||||
course = get_dummy_course(start=course_date, advertised_start=course_date)
|
||||
self.assertEqual(course.start_datetime_text(time_zone=time_zone), expected_short_date)
|
||||
self.assertEqual(course.start_datetime_text("DATE_TIME", time_zone), expected_date_time)
|
||||
|
||||
def test_start_date_is_default(self):
|
||||
for s in self.start_advertised_settings:
|
||||
d = get_dummy_course(start=s[0], advertised_start=s[1])
|
||||
@@ -246,6 +276,36 @@ class IsNewCourseTestCase(unittest.TestCase):
|
||||
descriptor = get_dummy_course(start='2012-12-31T12:00')
|
||||
assert descriptor.is_newish is True
|
||||
|
||||
def test_end_date_text(self):
|
||||
# No end date set, returns empty string.
|
||||
d = get_dummy_course('2012-12-02T12:00')
|
||||
self.assertEqual('', d.end_datetime_text())
|
||||
|
||||
d = get_dummy_course('2012-12-02T12:00', end='2014-9-04T12:00')
|
||||
self.assertEqual('Sep 04, 2014', d.end_datetime_text())
|
||||
|
||||
def test_end_date_time_text(self):
|
||||
# No end date set, returns empty string.
|
||||
course = get_dummy_course('2012-12-02T12:00')
|
||||
self.assertEqual('', course.end_datetime_text("DATE_TIME"))
|
||||
|
||||
course = get_dummy_course('2012-12-02T12:00', end='2014-9-04T12:00')
|
||||
self.assertEqual('Sep 04, 2014 at 12:00 UTC', course.end_datetime_text("DATE_TIME"))
|
||||
|
||||
@ddt.data(("2015-11-01T08:59", 'Nov 01, 2015', u'Nov 01, 2015 at 01:59 PDT'),
|
||||
("2015-11-01T09:00", 'Nov 01, 2015', u'Nov 01, 2015 at 01:00 PST'))
|
||||
@ddt.unpack
|
||||
def test_end_date_time_zone(self, course_date, expected_short_date, expected_date_time):
|
||||
"""
|
||||
Test that end datetime text correctly formats datetimes
|
||||
for normal daylight hours and daylight savings hours
|
||||
"""
|
||||
time_zone = timezone('America/Los_Angeles')
|
||||
course = get_dummy_course(course_date, end=course_date)
|
||||
|
||||
self.assertEqual(course.end_datetime_text(time_zone=time_zone), expected_short_date)
|
||||
self.assertEqual(course.end_datetime_text("DATE_TIME", time_zone), expected_date_time)
|
||||
|
||||
|
||||
class DiscussionTopicsTestCase(unittest.TestCase):
|
||||
def test_default_discussion_topics(self):
|
||||
|
||||
Reference in New Issue
Block a user