From b525f403775cea068d9b76995d3b548d8ab6bf08 Mon Sep 17 00:00:00 2001
From: Jason Bau <jbau@stanford.edu>
Date: Wed, 22 Jan 2014 17:11:29 -0800
Subject: [PATCH] Start datetime localization in LMS

Currently just coerces the time displayed for due dates
(accordion.html and progess.html) to settings.TIME_ZONE
---
 CHANGELOG.rst                                 |  3 +++
 .../xmodule/xmodule/tests/test_date_utils.py  | 19 ++++++++++++++++++-
 common/lib/xmodule/xmodule/util/date_utils.py | 13 +++++++++++--
 lms/envs/test.py                              |  1 +
 lms/templates/courseware/accordion.html       |  3 ++-
 lms/templates/courseware/progress.html        |  7 +++++--
 6 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 1e464d063a1..5006cb0cb22 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,9 @@ These are notable changes in edx-platform.  This is a rolling list of changes,
 in roughly chronological order, most recent first.  Add your entries at or near
 the top.  Include a label indicating the component affected.
 
+LMS: In left accordion and progress page, due dates are now displayed in time
+zone specified by settings.TIME_ZONE, instead of UTC always
+
 LMS:  If the course start date is kept at the default studio value (Jan 1, 2030)
 and advertised_start is not set, the start date is not displayed in the
 /courses tile view, the course about page, or the dashboard
diff --git a/common/lib/xmodule/xmodule/tests/test_date_utils.py b/common/lib/xmodule/xmodule/tests/test_date_utils.py
index c5f71dfb56e..ae6fd7f109d 100644
--- a/common/lib/xmodule/xmodule/tests/test_date_utils.py
+++ b/common/lib/xmodule/xmodule/tests/test_date_utils.py
@@ -3,7 +3,7 @@
 from nose.tools import assert_equals, assert_false  # pylint: disable=E0611
 from xmodule.util.date_utils import get_default_time_display, get_time_display, almost_same_datetime
 from datetime import datetime, timedelta, tzinfo
-from pytz import UTC
+from pytz import UTC, timezone
 
 
 def test_get_default_time_display():
@@ -42,6 +42,23 @@ def test_get_time_pass_through():
     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)
+    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",
+                  get_time_display(test_time_standard, None, coerce_tz="NONEXISTENTTZ"))
+    assert_equals("Jan 12 07:03",
+                  get_time_display(test_time_standard, '%b %d %H:%M', coerce_tz="US/Pacific"))
+    assert_equals("Jul 12, 1992 at 08:03 PDT",
+                  get_time_display(test_time_daylight, None, coerce_tz="US/Pacific"))
+    assert_equals("Jul 12, 1992 at 15:03 UTC",
+                  get_time_display(test_time_daylight, None, coerce_tz="NONEXISTENTTZ"))
+    assert_equals("Jul 12 08:03",
+                  get_time_display(test_time_daylight, '%b %d %H:%M', coerce_tz="US/Pacific"))
+
+
 # pylint: disable=W0232
 class NamelessTZ(tzinfo):
     """Static timezone for testing"""
diff --git a/common/lib/xmodule/xmodule/util/date_utils.py b/common/lib/xmodule/xmodule/util/date_utils.py
index aec4f20788a..a862bc7d310 100644
--- a/common/lib/xmodule/xmodule/util/date_utils.py
+++ b/common/lib/xmodule/xmodule/util/date_utils.py
@@ -2,7 +2,7 @@
 Convenience methods for working with datetime objects
 """
 from datetime import timedelta
-
+from pytz import timezone, UTC, UnknownTimeZoneError
 
 def get_default_time_display(dtime):
     """
@@ -25,7 +25,7 @@ def get_default_time_display(dtime):
         tz=timezone).strip()
 
 
-def get_time_display(dtime, format_string=None):
+def get_time_display(dtime, format_string=None, coerce_tz=None):
     """
     Converts a datetime to a string representation.
 
@@ -34,8 +34,17 @@ def get_time_display(dtime, format_string=None):
     If the format_string is None, or if format_string is improperly
     formatted, this method will return the value from `get_default_time_display`.
 
+    Coerces aware datetime to tz=coerce_tz if set. coerce_tz should be a pytz timezone string
+    like "US/Pacific", or None
+
     format_string should be a unicode string that is a valid argument for datetime's strftime method.
     """
+    if dtime is not None and dtime.tzinfo is not None and coerce_tz:
+        try:
+            to_tz = timezone(coerce_tz)
+        except UnknownTimeZoneError:
+            to_tz = UTC
+        dtime = to_tz.normalize(dtime.astimezone(to_tz))
     if dtime is None or format_string is None:
         return get_default_time_display(dtime)
     try:
diff --git a/lms/envs/test.py b/lms/envs/test.py
index 1c48238b316..b2065e87b21 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -85,6 +85,7 @@ XQUEUE_INTERFACE = {
 }
 XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5  # seconds
 
+TIME_ZONE = 'UTC'
 
 # Don't rely on a real staff grading backend
 MOCK_STAFF_GRADING = True
diff --git a/lms/templates/courseware/accordion.html b/lms/templates/courseware/accordion.html
index 8784b27d716..d857fc3ba0a 100644
--- a/lms/templates/courseware/accordion.html
+++ b/lms/templates/courseware/accordion.html
@@ -2,6 +2,7 @@
     from django.core.urlresolvers import reverse
     from xmodule.util.date_utils import get_time_display
     from django.utils.translation import ugettext as _
+    from django.conf import settings
 %>
 
 <%def name="make_chapter(chapter)">
@@ -29,7 +30,7 @@
                 if section.get('due') is None:
                     due_date = ''
                 else:
-                    formatted_string = get_time_display(section['due'], due_date_display_format)
+                    formatted_string = get_time_display(section['due'], due_date_display_format, coerce_tz=settings.TIME_ZONE)
                     due_date = '' if len(formatted_string)==0 else _('due {date}'.format(date=formatted_string))
               %>
               <p class="subtitle">${section['format']} ${due_date}</p>
diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html
index 1e3fe575ba2..7eeea8da596 100644
--- a/lms/templates/courseware/progress.html
+++ b/lms/templates/courseware/progress.html
@@ -16,7 +16,10 @@
     from django.core.urlresolvers import reverse
 %>
 
-<%! from xmodule.util.date_utils import get_time_display %>
+<%!
+from xmodule.util.date_utils import get_time_display
+from django.conf import settings
+%>
 
 <%block name="js_extra">
 <script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
@@ -74,7 +77,7 @@ ${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph",
 
                 %if section.get('due') is not None:
                   <%
-                      formatted_string = get_time_display(section['due'], course.due_date_display_format)
+                      formatted_string = get_time_display(section['due'], course.due_date_display_format, coerce_tz=settings.TIME_ZONE)
                       due_date = '' if len(formatted_string)==0 else _(u'due {date}').format(date=formatted_string)
                   %>
                   <em>
-- 
GitLab