Skip to content

Commit

Permalink
Converts the dates on the dashboard, sidebar navigation, and importan…
Browse files Browse the repository at this point in the history
…t course dates to user specified time zone.
  • Loading branch information
kevinjkim committed Jul 5, 2016
1 parent 4bc81ac commit 0bf8fc4
Show file tree
Hide file tree
Showing 21 changed files with 443 additions and 134 deletions.
10 changes: 0 additions & 10 deletions common/djangoapps/util/date_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from datetime import datetime, timedelta
import re

from django.utils.timezone import now
from django.utils.translation import pgettext, ugettext
from pytz import timezone, utc, UnknownTimeZoneError

Expand Down Expand Up @@ -63,15 +62,6 @@ def get_time_display(dtime, format_string=None, coerce_tz=None):
return get_default_time_display(dtime)


def get_formatted_time_zone(time_zone):
"""
Returns a formatted time zone (e.g. 'Asia/Tokyo (JST +0900)') for user account settings time zone drop down
"""
abbr = get_time_display(now(), '%Z', time_zone)
offset = get_time_display(now(), '%z', time_zone)
return "{name} ({abbr}, UTC{offset})".format(name=time_zone, abbr=abbr, offset=offset).replace("_", " ")


def almost_same_datetime(dt1, dt2, allowed_delta=timedelta(minutes=1)):
"""
Returns true if these are w/in a minute of each other. (in case secs saved to db
Expand Down
15 changes: 7 additions & 8 deletions common/djangoapps/util/tests/test_date_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import ddt
from mock import patch
from nose.tools import assert_equals, assert_false # pylint: disable=no-name-in-module
from pytz import UTC

from pytz import utc
from util.date_utils import (
get_default_time_display, get_time_display, almost_same_datetime,
strftime_localized,
Expand All @@ -19,7 +18,7 @@

def test_get_default_time_display():
assert_equals("", get_default_time_display(None))
test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=UTC)
test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=utc)
assert_equals(
"Mar 12, 1992 at 15:03 UTC",
get_default_time_display(test_time))
Expand All @@ -34,28 +33,28 @@ def test_get_dflt_time_disp_notz():

def test_get_time_disp_ret_empty():
assert_equals("", get_time_display(None))
test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=UTC)
test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=utc)
assert_equals("", get_time_display(test_time, ""))


def test_get_time_display():
test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=UTC)
test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=utc)
assert_equals("dummy text", get_time_display(test_time, 'dummy text'))
assert_equals("Mar 12 1992", get_time_display(test_time, '%b %d %Y'))
assert_equals("Mar 12 1992 UTC", get_time_display(test_time, '%b %d %Y %Z'))
assert_equals("Mar 12 15:03", get_time_display(test_time, '%b %d %H:%M'))


def test_get_time_pass_through():
test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=UTC)
test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=utc)
assert_equals("Mar 12, 1992 at 15:03 UTC", get_time_display(test_time))
assert_equals("Mar 12, 1992 at 15:03 UTC", get_time_display(test_time, None))
assert_equals("Mar 12, 1992 at 15:03 UTC", get_time_display(test_time, "%"))


def test_get_time_display_coerce():
test_time_standard = datetime(1992, 1, 12, 15, 3, 30, tzinfo=UTC)
test_time_daylight = datetime(1992, 7, 12, 15, 3, 30, tzinfo=UTC)
test_time_standard = datetime(1992, 1, 12, 15, 3, 30, tzinfo=utc)
test_time_daylight = datetime(1992, 7, 12, 15, 3, 30, tzinfo=utc)
assert_equals("Jan 12, 1992 at 07:03 PST",
get_time_display(test_time_standard, None, coerce_tz="US/Pacific"))
assert_equals("Jan 12, 1992 at 15:03 UTC",
Expand Down
40 changes: 22 additions & 18 deletions common/lib/xmodule/xmodule/course_metadata_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
import dateutil.parser
from math import exp

from django.utils.timezone import UTC
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())
DEFAULT_START_DATE = datetime(2030, 1, 1, tzinfo=utc)


def clean_course_key(course_key, padding_char):
Expand Down Expand Up @@ -56,7 +57,7 @@ def has_course_started(start_date):
start_date (datetime): The start datetime of the course in question.
"""
# TODO: This will throw if start_date is None... consider changing this behavior?
return datetime.now(UTC()) > start_date
return datetime.now(utc) > start_date


def has_course_ended(end_date):
Expand All @@ -68,7 +69,7 @@ def has_course_ended(end_date):
Arguments:
end_date (datetime): The end datetime of the course in question.
"""
return datetime.now(UTC()) > end_date if end_date is not None else False
return datetime.now(utc) > end_date if end_date is not None else False


def course_starts_within(start_date, look_ahead_days):
Expand All @@ -80,7 +81,7 @@ def course_starts_within(start_date, look_ahead_days):
start_date (datetime): The start datetime of the course in question.
look_ahead_days (int): number of days to see in future for course start date.
"""
return datetime.now(UTC()) + timedelta(days=look_ahead_days) > start_date
return datetime.now(utc) + timedelta(days=look_ahead_days) > start_date


def course_start_date_is_default(start, advertised_start):
Expand All @@ -95,37 +96,39 @@ 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, strftime_localized):
def _datetime_to_string(date_time, format_string, time_zone, strftime_localized):
"""
Formats the given datetime with the given function and format string.
Adds UTC to the resulting string if the format is DATE_TIME or TIME.
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
"""
# TODO: Is manually appending UTC really the right thing to do here? What if date_time isn't UTC?
result = strftime_localized(date_time, format_string)
result = strftime_localized(date_time.astimezone(time_zone), format_string)
abbr = get_time_zone_abbr(time_zone, date_time)
return (
result + u" UTC" if format_string in ['DATE_TIME', 'TIME', 'DAY_AND_TIME']
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, ugettext, strftime_localized):
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 UTC.
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
Expand All @@ -138,20 +141,20 @@ def course_start_datetime_text(start_date, advertised_start, format_string, uget
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, strftime_localized)
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, strftime_localized)
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, strftime_localized):
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.
Expand All @@ -160,11 +163,12 @@ def course_end_datetime_text(end_date, format_string, strftime_localized):
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, strftime_localized) if end_date is not None
_datetime_to_string(end_date, format_string, time_zone, strftime_localized) if end_date is not None
else ''
)

Expand Down Expand Up @@ -220,10 +224,10 @@ def sorting_dates(start, advertised_start, announcement):
try:
start = dateutil.parser.parse(advertised_start)
if start.tzinfo is None:
start = start.replace(tzinfo=UTC())
start = start.replace(tzinfo=utc)
except (ValueError, AttributeError):
start = start

now = datetime.now(UTC())
now = datetime.now(utc)

return announcement, start, now
18 changes: 10 additions & 8 deletions common/lib/xmodule/xmodule/course_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
from datetime import datetime

import requests
from django.utils.timezone import UTC
from lazy import lazy
from lxml import etree
from path import Path as path
from pytz import utc
from xblock.fields import Scope, List, String, Dict, Boolean, Integer, Float

from xmodule import course_metadata_utils
Expand Down Expand Up @@ -106,7 +106,7 @@ def table_of_contents(self):
# see if we already fetched this
if toc_url in _cached_toc:
(table_of_contents, timestamp) = _cached_toc[toc_url]
age = datetime.now(UTC) - timestamp
age = datetime.now(utc) - timestamp
# expire every 10 minutes
if age.seconds < 600:
return table_of_contents
Expand Down Expand Up @@ -1190,16 +1190,17 @@ def id(self):
"""Return the course_id for this course"""
return self.location.course_key

def start_datetime_text(self, format_string="SHORT_DATE"):
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 UTC. Prefers .advertised_start,
then falls back to .start
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
)
Expand All @@ -1215,13 +1216,14 @@ def start_date_is_still_default(self):
self.advertised_start
)

def end_datetime_text(self, format_string="SHORT_DATE"):
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
)

Expand Down Expand Up @@ -1256,7 +1258,7 @@ def forum_posts_allowed(self):
setting
"""
blackouts = self.get_discussion_blackout_datetimes()
now = datetime.now(UTC())
now = datetime.now(utc)
for blackout in blackouts:
if blackout["start"] <= now <= blackout["end"]:
return False
Expand Down Expand Up @@ -1384,7 +1386,7 @@ def can_toggle_course_pacing(self):
Returns:
bool: False if the course has already started, True otherwise.
"""
return datetime.now(UTC()) <= self.start
return datetime.now(utc) <= self.start


class CourseSummary(object):
Expand Down
Loading

0 comments on commit 0bf8fc4

Please sign in to comment.