Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #7201 -- Fixed the timeuntil filter to work correctly with time…

…zone-aware

times. Patch from Jeremy Carbaugh.

This is backwards incompatible in the sense that previously, if you tried to
compare timezone-aware and timezone-naive values, you got an incorrect result.
Now you get an empty string. So your previously incorrect code returns a
different incorrect result.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@8579 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 3111d7f60b67d5aefa4d891d1d9a86ba56d7607c 1 parent 61957df
Malcolm Tredinnick authored August 26, 2008
1  AUTHORS
@@ -88,6 +88,7 @@ answer newbie questions, and generally made Django that much better:
88 88
     Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com>
89 89
     Trevor Caira <trevor@caira.com>
90 90
     Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com>
  91
+    Jeremy Carbaugh <jcarbaugh@gmail.com>
91 92
     Graham Carlyle <graham.carlyle@maplecroft.net>
92 93
     Antonio Cavedoni <http://cavedoni.com/>
93 94
     C8E
18  django/template/defaultfilters.py
@@ -646,20 +646,24 @@ def timesince(value, arg=None):
646 646
     from django.utils.timesince import timesince
647 647
     if not value:
648 648
         return u''
649  
-    if arg:
650  
-        return timesince(value, arg)
651  
-    return timesince(value)
  649
+    try:
  650
+        if arg:
  651
+            return timesince(value, arg)
  652
+        return timesince(value)
  653
+    except (ValueError, TypeError):
  654
+        return u''
652 655
 timesince.is_safe = False
653 656
 
654 657
 def timeuntil(value, arg=None):
655 658
     """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
656  
-    from django.utils.timesince import timesince
  659
+    from django.utils.timesince import timeuntil
657 660
     from datetime import datetime
658 661
     if not value:
659 662
         return u''
660  
-    if arg:
661  
-        return timesince(arg, value)
662  
-    return timesince(datetime.now(), value)
  663
+    try:
  664
+        return timeuntil(value, arg)
  665
+    except (ValueError, TypeError):
  666
+        return u''
663 667
 timeuntil.is_safe = False
664 668
 
665 669
 ###################
22  django/utils/timesince.py
@@ -28,15 +28,12 @@ def timesince(d, now=None):
28 28
     # Convert datetime.date to datetime.datetime for comparison
29 29
     if d.__class__ is not datetime.datetime:
30 30
         d = datetime.datetime(d.year, d.month, d.day)
31  
-    if now:
32  
-        t = now.timetuple()
33  
-    else:
34  
-        t = time.localtime()
35  
-    if d.tzinfo:
36  
-        tz = LocalTimezone(d)
37  
-    else:
38  
-        tz = None
39  
-    now = datetime.datetime(t[0], t[1], t[2], t[3], t[4], t[5], tzinfo=tz)
  31
+
  32
+    if not now:
  33
+        if d.tzinfo:
  34
+            now = datetime.datetime.now(LocalTimezone(d))
  35
+        else:
  36
+            now = datetime.datetime.now()
40 37
 
41 38
     # ignore microsecond part of 'd' since we removed it from 'now'
42 39
     delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
@@ -62,6 +59,9 @@ def timeuntil(d, now=None):
62 59
     Like timesince, but returns a string measuring the time until
63 60
     the given time.
64 61
     """
65  
-    if now == None:
66  
-        now = datetime.datetime.now()
  62
+    if not now:
  63
+        if d.tzinfo:
  64
+            now = datetime.datetime.now(LocalTimezone(d))
  65
+        else:
  66
+            now = datetime.datetime.now()
67 67
     return timesince(now, d)
4  docs/ref/templates/builtins.txt
@@ -1332,6 +1332,8 @@ For example, if ``blog_date`` is a date instance representing midnight on 1
1332 1332
 June 2006, and ``comment_date`` is a date instance for 08:00 on 1 June 2006,
1333 1333
 then ``{{ blog_date|timesince:comment_date }}`` would return "8 hours".
1334 1334
 
  1335
+Comparing offset-naive and offset-aware datetimes will return an empty string.
  1336
+
1335 1337
 Minutes is the smallest unit used, and "0 minutes" will be returned for any
1336 1338
 date that is in the future relative to the comparison point.
1337 1339
 
@@ -1349,6 +1351,8 @@ Takes an optional argument that is a variable containing the date to use as
1349 1351
 the comparison point (instead of *now*). If ``from_date`` contains 22 June
1350 1352
 2006, then ``{{ conference_date|timeuntil:from_date }}`` will return "1 week".
1351 1353
 
  1354
+Comparing offset-naive and offset-aware datetimes will return an empty string.
  1355
+
1352 1356
 Minutes is the smallest unit used, and "0 minutes" will be returned for any
1353 1357
 date that is in the past relative to the comparison point.
1354 1358
 
14  tests/regressiontests/templates/filters.py
@@ -9,7 +9,7 @@
9 9
 
10 10
 from datetime import datetime, timedelta
11 11
 
12  
-from django.utils.tzinfo import LocalTimezone
  12
+from django.utils.tzinfo import LocalTimezone, FixedOffset
13 13
 from django.utils.safestring import mark_safe
14 14
 
15 15
 # These two classes are used to test auto-escaping of __unicode__ output.
@@ -27,6 +27,7 @@ def __unicode__(self):
27 27
 def get_filter_tests():
28 28
     now = datetime.now()
29 29
     now_tz = datetime.now(LocalTimezone(now))
  30
+    now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone
30 31
     return {
31 32
         # Default compare with datetime.now()
32 33
         'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
@@ -46,6 +47,14 @@ def get_filter_tests():
46 47
         'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0 minutes'),
47 48
         'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0 minutes'),
48 49
 
  50
+        # Ensures that differing timezones are calculated correctly
  51
+        'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0 minutes'),
  52
+        'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0 minutes'),
  53
+        'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0 minutes'),
  54
+        'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0 minutes'),
  55
+        'filter-timesince15' : ('{{ a|timesince:b }}', {'a': now, 'b': now_tz_i}, ''),
  56
+        'filter-timesince16' : ('{{ a|timesince:b }}', {'a': now_tz_i, 'b': now}, ''),
  57
+
49 58
         # Default compare with datetime.now()
50 59
         'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
51 60
         'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
@@ -61,6 +70,9 @@ def get_filter_tests():
61 70
         'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1 week'),
62 71
         'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1 week'),
63 72
 
  73
+        # Ensures that differing timezones are calculated correctly
  74
+        'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0 minutes'),
  75
+        'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0 minutes'),
64 76
 
65 77
         'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"<a>\' <a>\'"),
66 78
         'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"&lt;a&gt;\&#39; <a>\'"),
14  tests/regressiontests/utils/timesince.py
... ...
@@ -1,6 +1,7 @@
1 1
 """
2 2
 >>> from datetime import datetime, timedelta
3  
->>> from django.utils.timesince import timesince
  3
+>>> from django.utils.timesince import timesince, timeuntil
  4
+>>> from django.utils.tzinfo import LocalTimezone, FixedOffset
4 5
 
5 6
 >>> t = datetime(2007, 8, 14, 13, 46, 0)
6 7
 
@@ -74,4 +75,15 @@
74 75
 u'0 minutes'
75 76
 >>> timesince(t, t-4*oneday-5*oneminute)
76 77
 u'0 minutes'
  78
+
  79
+# When using two different timezones.
  80
+>>> now = datetime.now()
  81
+>>> now_tz = datetime.now(LocalTimezone(now))
  82
+>>> now_tz_i = datetime.now(FixedOffset((3 * 60) + 15))
  83
+>>> timesince(now)
  84
+u'0 minutes'
  85
+>>> timesince(now_tz)
  86
+u'0 minutes'
  87
+>>> timeuntil(now_tz, now_tz_i)
  88
+u'0 minutes'
77 89
 """

0 notes on commit 3111d7f

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