Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #17992 -- Added a public API for localtime.

Thanks Bradley Ayers for the report.
  • Loading branch information...
commit 3e8b40f479a02e0f8c40ef3c7dae740082478b89 1 parent 5aa51fa
Aymeric Augustin authored April 29, 2012
10  django/contrib/admin/filters.py
@@ -290,19 +290,15 @@ def __init__(self, field, request, params, model, model_admin, field_path):
290 290
         now = timezone.now()
291 291
         # When time zone support is enabled, convert "now" to the user's time
292 292
         # zone so Django's definition of "Today" matches what the user expects.
293  
-        if now.tzinfo is not None:
294  
-            current_tz = timezone.get_current_timezone()
295  
-            now = now.astimezone(current_tz)
296  
-            if hasattr(current_tz, 'normalize'):
297  
-                # available for pytz time zones
298  
-                now = current_tz.normalize(now)
  293
+        if timezone.is_aware(now):
  294
+            now = timezone.localtime(now)
299 295
 
300 296
         if isinstance(field, models.DateTimeField):
301 297
             today = now.replace(hour=0, minute=0, second=0, microsecond=0)
302 298
         else:       # field is a models.DateField
303 299
             today = now.date()
304 300
         tomorrow = today + datetime.timedelta(days=1)
305  
-    
  301
+
306 302
         self.lookup_kwarg_since = '%s__gte' % field_path
307 303
         self.lookup_kwarg_until = '%s__lt' % field_path
308 304
         self.links = (
4  django/contrib/admin/util.py
@@ -325,7 +325,7 @@ def display_for_field(value, field):
325 325
     elif value is None:
326 326
         return EMPTY_CHANGELIST_VALUE
327 327
     elif isinstance(field, models.DateTimeField):
328  
-        return formats.localize(timezone.localtime(value))
  328
+        return formats.localize(timezone.template_localtime(value))
329 329
     elif isinstance(field, (models.DateField, models.TimeField)):
330 330
         return formats.localize(value)
331 331
     elif isinstance(field, models.DecimalField):
@@ -345,7 +345,7 @@ def display_for_value(value, boolean=False):
345 345
     elif value is None:
346 346
         return EMPTY_CHANGELIST_VALUE
347 347
     elif isinstance(value, datetime.datetime):
348  
-        return formats.localize(timezone.localtime(value))
  348
+        return formats.localize(timezone.template_localtime(value))
349 349
     elif isinstance(value, (datetime.date, datetime.time)):
350 350
         return formats.localize(value)
351 351
     elif isinstance(value, (decimal.Decimal, float, int, long)):
6  django/template/base.py
@@ -18,7 +18,7 @@
18 18
 from django.utils.formats import localize
19 19
 from django.utils.html import escape
20 20
 from django.utils.module_loading import module_has_submodule
21  
-from django.utils.timezone import localtime
  21
+from django.utils.timezone import template_localtime
22 22
 
23 23
 
24 24
 TOKEN_TEXT = 0
@@ -592,7 +592,7 @@ def resolve(self, context, ignore_failures=False):
592 592
                 else:
593 593
                     arg_vals.append(arg.resolve(context))
594 594
             if getattr(func, 'expects_localtime', False):
595  
-                obj = localtime(obj, context.use_tz)
  595
+                obj = template_localtime(obj, context.use_tz)
596 596
             if getattr(func, 'needs_autoescape', False):
597 597
                 new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
598 598
             else:
@@ -853,7 +853,7 @@ def _render_value_in_context(value, context):
853 853
     means escaping, if required, and conversion to a unicode object. If value
854 854
     is a string, it is expected to have already been translated.
855 855
     """
856  
-    value = localtime(value, use_tz=context.use_tz)
  856
+    value = template_localtime(value, use_tz=context.use_tz)
857 857
     value = localize(value, use_l10n=context.use_l10n)
858 858
     value = force_unicode(value)
859 859
     if ((context.autoescape and not isinstance(value, SafeData)) or
4  django/template/debug.py
@@ -3,7 +3,7 @@
3 3
 from django.utils.html import escape
4 4
 from django.utils.safestring import SafeData, EscapeData
5 5
 from django.utils.formats import localize
6  
-from django.utils.timezone import localtime
  6
+from django.utils.timezone import template_localtime
7 7
 
8 8
 
9 9
 class DebugLexer(Lexer):
@@ -82,7 +82,7 @@ class DebugVariableNode(VariableNode):
82 82
     def render(self, context):
83 83
         try:
84 84
             output = self.filter_expression.resolve(context)
85  
-            output = localtime(output, use_tz=context.use_tz)
  85
+            output = template_localtime(output, use_tz=context.use_tz)
86 86
             output = localize(output, use_l10n=context.use_l10n)
87 87
             output = force_unicode(output)
88 88
         except UnicodeDecodeError:
6  django/templatetags/tz.py
@@ -72,11 +72,7 @@ def do_timezone(value, arg):
72 72
     else:
73 73
         return ''
74 74
 
75  
-    # Convert and prevent further conversion
76  
-    result = value.astimezone(tz)
77  
-    if hasattr(tz, 'normalize'):
78  
-        # available for pytz time zones
79  
-        result = tz.normalize(result)
  75
+    result = timezone.localtime(value, tz)
80 76
 
81 77
     # HACK: the convert_to_local_time flag will prevent
82 78
     #       automatic conversion of the value to local time.
28  django/utils/timezone.py
@@ -206,7 +206,7 @@ def __exit__(self, exc_type, exc_value, traceback):
206 206
 
207 207
 # Templates
208 208
 
209  
-def localtime(value, use_tz=None):
  209
+def template_localtime(value, use_tz=None):
210 210
     """
211 211
     Checks if value is a datetime and converts it to local time if necessary.
212 212
 
@@ -215,20 +215,30 @@ def localtime(value, use_tz=None):
215 215
 
216 216
     This function is designed for use by the template engine.
217 217
     """
218  
-    if (isinstance(value, datetime)
  218
+    should_convert = (isinstance(value, datetime)
219 219
         and (settings.USE_TZ if use_tz is None else use_tz)
220 220
         and not is_naive(value)
221  
-        and getattr(value, 'convert_to_local_time', True)):
222  
-        timezone = get_current_timezone()
223  
-        value = value.astimezone(timezone)
224  
-        if hasattr(timezone, 'normalize'):
225  
-            # available for pytz time zones
226  
-            value = timezone.normalize(value)
227  
-    return value
  221
+        and getattr(value, 'convert_to_local_time', True))
  222
+    return localtime(value) if should_convert else value
228 223
 
229 224
 
230 225
 # Utilities
231 226
 
  227
+def localtime(value, timezone=None):
  228
+    """
  229
+    Converts an aware datetime.datetime to local time.
  230
+
  231
+    Local time is defined by the current time zone, unless another time zone
  232
+    is specified.
  233
+    """
  234
+    if timezone is None:
  235
+        timezone = get_current_timezone()
  236
+    value = value.astimezone(timezone)
  237
+    if hasattr(timezone, 'normalize'):
  238
+        # available for pytz time zones
  239
+        value = timezone.normalize(value)
  240
+    return value
  241
+
232 242
 def now():
233 243
     """
234 244
     Returns an aware or naive datetime.datetime, depending on settings.USE_TZ.
10  docs/ref/utils.txt
@@ -666,6 +666,16 @@ For a complete discussion on the usage of the following see the
666 666
     ``None``, the :ref:`current time zone <default-current-time-zone>` is unset
667 667
     on entry with :func:`deactivate()` instead.
668 668
 
  669
+.. versionadded:: 1.5
  670
+
  671
+.. function:: localtime(value, timezone=None)
  672
+
  673
+    Converts an aware :class:`~datetime.datetime` to a different time zone,
  674
+    by default the :ref:`current time zone <default-current-time-zone>`.
  675
+
  676
+    This function doesn't work on naive datetimes; use :func:`make_aware`
  677
+    instead.
  678
+
669 679
 .. function:: now()
670 680
 
671 681
     Returns an aware or naive :class:`~datetime.datetime` that represents the
3  docs/releases/1.5.txt
@@ -41,6 +41,9 @@ Django 1.5 also includes several smaller improvements worth noting:
41 41
 * The template engine now interprets ``True``, ``False`` and ``None`` as the
42 42
   corresponding Python objects.
43 43
 
  44
+* :mod:`django.utils.timezone` provides a helper for converting aware
  45
+  datetimes between time zones, see :func:`~django.utils.timezone.localtime`.
  46
+
44 47
 Backwards incompatible changes in 1.5
45 48
 =====================================
46 49
 
29  tests/regressiontests/utils/timezone.py
... ...
@@ -1,18 +1,33 @@
1 1
 import copy
  2
+import datetime
2 3
 import pickle
3  
-from django.utils.timezone import UTC, LocalTimezone
  4
+from django.test.utils import override_settings
  5
+from django.utils import timezone
4 6
 from django.utils import unittest
5 7
 
  8
+
6 9
 class TimezoneTests(unittest.TestCase):
7 10
 
  11
+    def test_localtime(self):
  12
+        now = datetime.datetime.utcnow().replace(tzinfo=timezone.utc)
  13
+        local_tz = timezone.LocalTimezone()
  14
+        local_now = timezone.localtime(now, local_tz)
  15
+        self.assertEqual(local_now.tzinfo, local_tz)
  16
+
  17
+    def test_now(self):
  18
+        with override_settings(USE_TZ=True):
  19
+            self.assertTrue(timezone.is_aware(timezone.now()))
  20
+        with override_settings(USE_TZ=False):
  21
+            self.assertTrue(timezone.is_naive(timezone.now()))
  22
+
8 23
     def test_copy(self):
9  
-        self.assertIsInstance(copy.copy(UTC()), UTC)
10  
-        self.assertIsInstance(copy.copy(LocalTimezone()), LocalTimezone)
  24
+        self.assertIsInstance(copy.copy(timezone.UTC()), timezone.UTC)
  25
+        self.assertIsInstance(copy.copy(timezone.LocalTimezone()), timezone.LocalTimezone)
11 26
 
12 27
     def test_deepcopy(self):
13  
-        self.assertIsInstance(copy.deepcopy(UTC()), UTC)
14  
-        self.assertIsInstance(copy.deepcopy(LocalTimezone()), LocalTimezone)
  28
+        self.assertIsInstance(copy.deepcopy(timezone.UTC()), timezone.UTC)
  29
+        self.assertIsInstance(copy.deepcopy(timezone.LocalTimezone()), timezone.LocalTimezone)
15 30
 
16 31
     def test_pickling_unpickling(self):
17  
-        self.assertIsInstance(pickle.loads(pickle.dumps(UTC())), UTC)
18  
-        self.assertIsInstance(pickle.loads(pickle.dumps(LocalTimezone())), LocalTimezone)
  32
+        self.assertIsInstance(pickle.loads(pickle.dumps(timezone.UTC())), timezone.UTC)
  33
+        self.assertIsInstance(pickle.loads(pickle.dumps(timezone.LocalTimezone())), timezone.LocalTimezone)

0 notes on commit 3e8b40f

Please sign in to comment.
Something went wrong with that request. Please try again.