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

Thanks Bradley Ayers for the report.
This commit is contained in:
Aymeric Augustin 2012-04-29 15:37:23 +02:00
parent 5aa51fa999
commit 3e8b40f479
9 changed files with 65 additions and 35 deletions

View File

@ -290,19 +290,15 @@ class DateFieldListFilter(FieldListFilter):
now = timezone.now()
# When time zone support is enabled, convert "now" to the user's time
# zone so Django's definition of "Today" matches what the user expects.
if now.tzinfo is not None:
current_tz = timezone.get_current_timezone()
now = now.astimezone(current_tz)
if hasattr(current_tz, 'normalize'):
# available for pytz time zones
now = current_tz.normalize(now)
if timezone.is_aware(now):
now = timezone.localtime(now)
if isinstance(field, models.DateTimeField):
today = now.replace(hour=0, minute=0, second=0, microsecond=0)
else: # field is a models.DateField
today = now.date()
tomorrow = today + datetime.timedelta(days=1)
self.lookup_kwarg_since = '%s__gte' % field_path
self.lookup_kwarg_until = '%s__lt' % field_path
self.links = (

View File

@ -325,7 +325,7 @@ def display_for_field(value, field):
elif value is None:
return EMPTY_CHANGELIST_VALUE
elif isinstance(field, models.DateTimeField):
return formats.localize(timezone.localtime(value))
return formats.localize(timezone.template_localtime(value))
elif isinstance(field, (models.DateField, models.TimeField)):
return formats.localize(value)
elif isinstance(field, models.DecimalField):
@ -345,7 +345,7 @@ def display_for_value(value, boolean=False):
elif value is None:
return EMPTY_CHANGELIST_VALUE
elif isinstance(value, datetime.datetime):
return formats.localize(timezone.localtime(value))
return formats.localize(timezone.template_localtime(value))
elif isinstance(value, (datetime.date, datetime.time)):
return formats.localize(value)
elif isinstance(value, (decimal.Decimal, float, int, long)):

View File

@ -18,7 +18,7 @@ from django.utils.safestring import (SafeData, EscapeData, mark_safe,
from django.utils.formats import localize
from django.utils.html import escape
from django.utils.module_loading import module_has_submodule
from django.utils.timezone import localtime
from django.utils.timezone import template_localtime
TOKEN_TEXT = 0
@ -592,7 +592,7 @@ class FilterExpression(object):
else:
arg_vals.append(arg.resolve(context))
if getattr(func, 'expects_localtime', False):
obj = localtime(obj, context.use_tz)
obj = template_localtime(obj, context.use_tz)
if getattr(func, 'needs_autoescape', False):
new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
else:
@ -853,7 +853,7 @@ def _render_value_in_context(value, context):
means escaping, if required, and conversion to a unicode object. If value
is a string, it is expected to have already been translated.
"""
value = localtime(value, use_tz=context.use_tz)
value = template_localtime(value, use_tz=context.use_tz)
value = localize(value, use_l10n=context.use_l10n)
value = force_unicode(value)
if ((context.autoescape and not isinstance(value, SafeData)) or

View File

@ -3,7 +3,7 @@ from django.utils.encoding import force_unicode
from django.utils.html import escape
from django.utils.safestring import SafeData, EscapeData
from django.utils.formats import localize
from django.utils.timezone import localtime
from django.utils.timezone import template_localtime
class DebugLexer(Lexer):
@ -82,7 +82,7 @@ class DebugVariableNode(VariableNode):
def render(self, context):
try:
output = self.filter_expression.resolve(context)
output = localtime(output, use_tz=context.use_tz)
output = template_localtime(output, use_tz=context.use_tz)
output = localize(output, use_l10n=context.use_l10n)
output = force_unicode(output)
except UnicodeDecodeError:

View File

@ -72,11 +72,7 @@ def do_timezone(value, arg):
else:
return ''
# Convert and prevent further conversion
result = value.astimezone(tz)
if hasattr(tz, 'normalize'):
# available for pytz time zones
result = tz.normalize(result)
result = timezone.localtime(value, tz)
# HACK: the convert_to_local_time flag will prevent
# automatic conversion of the value to local time.

View File

@ -206,7 +206,7 @@ class override(object):
# Templates
def localtime(value, use_tz=None):
def template_localtime(value, use_tz=None):
"""
Checks if value is a datetime and converts it to local time if necessary.
@ -215,20 +215,30 @@ def localtime(value, use_tz=None):
This function is designed for use by the template engine.
"""
if (isinstance(value, datetime)
should_convert = (isinstance(value, datetime)
and (settings.USE_TZ if use_tz is None else use_tz)
and not is_naive(value)
and getattr(value, 'convert_to_local_time', True)):
timezone = get_current_timezone()
value = value.astimezone(timezone)
if hasattr(timezone, 'normalize'):
# available for pytz time zones
value = timezone.normalize(value)
return value
and getattr(value, 'convert_to_local_time', True))
return localtime(value) if should_convert else value
# Utilities
def localtime(value, timezone=None):
"""
Converts an aware datetime.datetime to local time.
Local time is defined by the current time zone, unless another time zone
is specified.
"""
if timezone is None:
timezone = get_current_timezone()
value = value.astimezone(timezone)
if hasattr(timezone, 'normalize'):
# available for pytz time zones
value = timezone.normalize(value)
return value
def now():
"""
Returns an aware or naive datetime.datetime, depending on settings.USE_TZ.

View File

@ -666,6 +666,16 @@ For a complete discussion on the usage of the following see the
``None``, the :ref:`current time zone <default-current-time-zone>` is unset
on entry with :func:`deactivate()` instead.
.. versionadded:: 1.5
.. function:: localtime(value, timezone=None)
Converts an aware :class:`~datetime.datetime` to a different time zone,
by default the :ref:`current time zone <default-current-time-zone>`.
This function doesn't work on naive datetimes; use :func:`make_aware`
instead.
.. function:: now()
Returns an aware or naive :class:`~datetime.datetime` that represents the

View File

@ -41,6 +41,9 @@ Django 1.5 also includes several smaller improvements worth noting:
* The template engine now interprets ``True``, ``False`` and ``None`` as the
corresponding Python objects.
* :mod:`django.utils.timezone` provides a helper for converting aware
datetimes between time zones, see :func:`~django.utils.timezone.localtime`.
Backwards incompatible changes in 1.5
=====================================

View File

@ -1,18 +1,33 @@
import copy
import datetime
import pickle
from django.utils.timezone import UTC, LocalTimezone
from django.test.utils import override_settings
from django.utils import timezone
from django.utils import unittest
class TimezoneTests(unittest.TestCase):
def test_localtime(self):
now = datetime.datetime.utcnow().replace(tzinfo=timezone.utc)
local_tz = timezone.LocalTimezone()
local_now = timezone.localtime(now, local_tz)
self.assertEqual(local_now.tzinfo, local_tz)
def test_now(self):
with override_settings(USE_TZ=True):
self.assertTrue(timezone.is_aware(timezone.now()))
with override_settings(USE_TZ=False):
self.assertTrue(timezone.is_naive(timezone.now()))
def test_copy(self):
self.assertIsInstance(copy.copy(UTC()), UTC)
self.assertIsInstance(copy.copy(LocalTimezone()), LocalTimezone)
self.assertIsInstance(copy.copy(timezone.UTC()), timezone.UTC)
self.assertIsInstance(copy.copy(timezone.LocalTimezone()), timezone.LocalTimezone)
def test_deepcopy(self):
self.assertIsInstance(copy.deepcopy(UTC()), UTC)
self.assertIsInstance(copy.deepcopy(LocalTimezone()), LocalTimezone)
self.assertIsInstance(copy.deepcopy(timezone.UTC()), timezone.UTC)
self.assertIsInstance(copy.deepcopy(timezone.LocalTimezone()), timezone.LocalTimezone)
def test_pickling_unpickling(self):
self.assertIsInstance(pickle.loads(pickle.dumps(UTC())), UTC)
self.assertIsInstance(pickle.loads(pickle.dumps(LocalTimezone())), LocalTimezone)
self.assertIsInstance(pickle.loads(pickle.dumps(timezone.UTC())), timezone.UTC)
self.assertIsInstance(pickle.loads(pickle.dumps(timezone.LocalTimezone())), timezone.LocalTimezone)