Skip to content

Commit 5d560dc

Browse files
committed
Fixed django#18504 -- Computed |naturalday in local time.
1 parent 123362d commit 5d560dc

File tree

2 files changed

+53
-27
lines changed

2 files changed

+53
-27
lines changed

django/contrib/humanize/templatetags/humanize.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import unicode_literals
22
import re
3-
from datetime import date, datetime, timedelta
3+
from datetime import date, datetime
44

55
from django import template
66
from django.conf import settings
@@ -143,7 +143,9 @@ def apnumber(value):
143143
return value
144144
return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1]
145145

146-
@register.filter
146+
# Perform the comparison in the default time zone when USE_TZ = True
147+
# (unless a specific time zone has been applied with the |timezone filter).
148+
@register.filter(expects_localtime=True)
147149
def naturalday(value, arg=None):
148150
"""
149151
For date values that are tomorrow, today or yesterday compared to
@@ -169,6 +171,8 @@ def naturalday(value, arg=None):
169171
return _('yesterday')
170172
return defaultfilters.date(value, arg)
171173

174+
# This filter doesn't require expects_localtime=True because it deals properly
175+
# with both naive and aware datetimes. Therefore avoid the cost of conversion.
172176
@register.filter
173177
def naturaltime(value):
174178
"""

django/contrib/humanize/tests.py

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
from __future__ import unicode_literals
22
import datetime
3-
import new
43

4+
from django.contrib.humanize.templatetags import humanize
55
from django.template import Template, Context, defaultfilters
66
from django.test import TestCase
7-
from django.utils import translation, tzinfo
8-
from django.utils.translation import ugettext as _
7+
from django.test.utils import override_settings
98
from django.utils.html import escape
109
from django.utils.timezone import utc
10+
from django.utils import translation
11+
from django.utils.translation import ugettext as _
12+
from django.utils import tzinfo
13+
14+
15+
# Mock out datetime in some tests so they don't fail occasionally when they
16+
# run too slow. Use a fixed datetime for datetime.now(). DST change in
17+
# America/Chicago (the default time zone) happened on March 11th in 2012.
18+
19+
now = datetime.datetime(2012, 3, 9, 22, 30)
20+
21+
class MockDateTime(datetime.datetime):
22+
@classmethod
23+
def now(self, tz=None):
24+
if tz is None or tz.utcoffset(now) is None:
25+
return now
26+
else:
27+
# equals now.replace(tzinfo=utc)
28+
return now.replace(tzinfo=tz) + tz.utcoffset(now)
1129

1230

1331
class HumanizeTests(TestCase):
@@ -109,28 +127,36 @@ def test_naturalday(self):
109127
self.humanize_tester(test_list, result_list, 'naturalday')
110128

111129
def test_naturalday_tz(self):
112-
from django.contrib.humanize.templatetags.humanize import naturalday
113-
114130
today = datetime.date.today()
115131
tz_one = tzinfo.FixedOffset(datetime.timedelta(hours=-12))
116132
tz_two = tzinfo.FixedOffset(datetime.timedelta(hours=12))
117133

118134
# Can be today or yesterday
119135
date_one = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_one)
120-
naturalday_one = naturalday(date_one)
136+
naturalday_one = humanize.naturalday(date_one)
121137
# Can be today or tomorrow
122138
date_two = datetime.datetime(today.year, today.month, today.day, tzinfo=tz_two)
123-
naturalday_two = naturalday(date_two)
139+
naturalday_two = humanize.naturalday(date_two)
124140

125141
# As 24h of difference they will never be the same
126142
self.assertNotEqual(naturalday_one, naturalday_two)
127143

144+
def test_naturalday_uses_localtime(self):
145+
# Regression for #18504
146+
# This is 2012-03-08HT19:30:00-06:00 in Ameria/Chicago
147+
dt = datetime.datetime(2012, 3, 9, 1, 30, tzinfo=utc)
148+
149+
orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime
150+
try:
151+
with override_settings(USE_TZ=True):
152+
self.humanize_tester([dt], ['yesterday'], 'naturalday')
153+
finally:
154+
humanize.datetime = orig_humanize_datetime
155+
128156
def test_naturaltime(self):
129157
class naive(datetime.tzinfo):
130158
def utcoffset(self, dt):
131159
return None
132-
# we're going to mock datetime.datetime, so use a fixed datetime
133-
now = datetime.datetime(2011, 8, 15, 1, 23)
134160
test_list = [
135161
now,
136162
now - datetime.timedelta(seconds=1),
@@ -148,6 +174,7 @@ def utcoffset(self, dt):
148174
now + datetime.timedelta(hours=1, minutes=30, seconds=30),
149175
now + datetime.timedelta(hours=23, minutes=50, seconds=50),
150176
now + datetime.timedelta(days=1),
177+
now + datetime.timedelta(days=2, hours=6),
151178
now + datetime.timedelta(days=500),
152179
now.replace(tzinfo=naive()),
153180
now.replace(tzinfo=utc),
@@ -169,27 +196,22 @@ def utcoffset(self, dt):
169196
'an hour from now',
170197
'23 hours from now',
171198
'1 day from now',
199+
'2 days, 6 hours from now',
172200
'1 year, 4 months from now',
173201
'now',
174202
'now',
175203
]
176-
177-
# mock out datetime so these tests don't fail occasionally when the
178-
# test runs too slow
179-
class MockDateTime(datetime.datetime):
180-
@classmethod
181-
def now(self, tz=None):
182-
if tz is None or tz.utcoffset(now) is None:
183-
return now
184-
else:
185-
# equals now.replace(tzinfo=utc)
186-
return now.replace(tzinfo=tz) + tz.utcoffset(now)
187-
188-
from django.contrib.humanize.templatetags import humanize
189-
orig_humanize_datetime = humanize.datetime
190-
humanize.datetime = MockDateTime
191-
204+
# Because of the DST change, 2 days and 6 hours after the chosen
205+
# date in naive arithmetic is only 2 days and 5 hours after in
206+
# aware arithmetic.
207+
result_list_with_tz_support = result_list[:]
208+
assert result_list_with_tz_support[-4] == '2 days, 6 hours from now'
209+
result_list_with_tz_support[-4] == '2 days, 5 hours from now'
210+
211+
orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime
192212
try:
193213
self.humanize_tester(test_list, result_list, 'naturaltime')
214+
with override_settings(USE_TZ=True):
215+
self.humanize_tester(test_list, result_list_with_tz_support, 'naturaltime')
194216
finally:
195217
humanize.datetime = orig_humanize_datetime

0 commit comments

Comments
 (0)