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

View File

@ -325,7 +325,7 @@ def display_for_field(value, field):
elif value is None: elif value is None:
return EMPTY_CHANGELIST_VALUE return EMPTY_CHANGELIST_VALUE
elif isinstance(field, models.DateTimeField): 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)): elif isinstance(field, (models.DateField, models.TimeField)):
return formats.localize(value) return formats.localize(value)
elif isinstance(field, models.DecimalField): elif isinstance(field, models.DecimalField):
@ -345,7 +345,7 @@ def display_for_value(value, boolean=False):
elif value is None: elif value is None:
return EMPTY_CHANGELIST_VALUE return EMPTY_CHANGELIST_VALUE
elif isinstance(value, datetime.datetime): 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)): elif isinstance(value, (datetime.date, datetime.time)):
return formats.localize(value) return formats.localize(value)
elif isinstance(value, (decimal.Decimal, float, int, long)): 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.formats import localize
from django.utils.html import escape from django.utils.html import escape
from django.utils.module_loading import module_has_submodule 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 TOKEN_TEXT = 0
@ -592,7 +592,7 @@ class FilterExpression(object):
else: else:
arg_vals.append(arg.resolve(context)) arg_vals.append(arg.resolve(context))
if getattr(func, 'expects_localtime', False): 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): if getattr(func, 'needs_autoescape', False):
new_obj = func(obj, autoescape=context.autoescape, *arg_vals) new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
else: else:
@ -853,7 +853,7 @@ def _render_value_in_context(value, context):
means escaping, if required, and conversion to a unicode object. If value means escaping, if required, and conversion to a unicode object. If value
is a string, it is expected to have already been translated. 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 = localize(value, use_l10n=context.use_l10n)
value = force_unicode(value) value = force_unicode(value)
if ((context.autoescape and not isinstance(value, SafeData)) or 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.html import escape
from django.utils.safestring import SafeData, EscapeData from django.utils.safestring import SafeData, EscapeData
from django.utils.formats import localize from django.utils.formats import localize
from django.utils.timezone import localtime from django.utils.timezone import template_localtime
class DebugLexer(Lexer): class DebugLexer(Lexer):
@ -82,7 +82,7 @@ class DebugVariableNode(VariableNode):
def render(self, context): def render(self, context):
try: try:
output = self.filter_expression.resolve(context) 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 = localize(output, use_l10n=context.use_l10n)
output = force_unicode(output) output = force_unicode(output)
except UnicodeDecodeError: except UnicodeDecodeError:

View File

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

View File

@ -206,7 +206,7 @@ class override(object):
# Templates # 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. 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. 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 (settings.USE_TZ if use_tz is None else use_tz)
and not is_naive(value) and not is_naive(value)
and getattr(value, 'convert_to_local_time', True)): and getattr(value, 'convert_to_local_time', True))
timezone = get_current_timezone() return localtime(value) if should_convert else value
value = value.astimezone(timezone)
if hasattr(timezone, 'normalize'):
# available for pytz time zones
value = timezone.normalize(value)
return value
# Utilities # 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(): def now():
""" """
Returns an aware or naive datetime.datetime, depending on settings.USE_TZ. 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 ``None``, the :ref:`current time zone <default-current-time-zone>` is unset
on entry with :func:`deactivate()` instead. 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() .. function:: now()
Returns an aware or naive :class:`~datetime.datetime` that represents the 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 * The template engine now interprets ``True``, ``False`` and ``None`` as the
corresponding Python objects. 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 Backwards incompatible changes in 1.5
===================================== =====================================

View File

@ -1,18 +1,33 @@
import copy import copy
import datetime
import pickle 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 from django.utils import unittest
class TimezoneTests(unittest.TestCase): 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): def test_copy(self):
self.assertIsInstance(copy.copy(UTC()), UTC) self.assertIsInstance(copy.copy(timezone.UTC()), timezone.UTC)
self.assertIsInstance(copy.copy(LocalTimezone()), LocalTimezone) self.assertIsInstance(copy.copy(timezone.LocalTimezone()), timezone.LocalTimezone)
def test_deepcopy(self): def test_deepcopy(self):
self.assertIsInstance(copy.deepcopy(UTC()), UTC) self.assertIsInstance(copy.deepcopy(timezone.UTC()), timezone.UTC)
self.assertIsInstance(copy.deepcopy(LocalTimezone()), LocalTimezone) self.assertIsInstance(copy.deepcopy(timezone.LocalTimezone()), timezone.LocalTimezone)
def test_pickling_unpickling(self): def test_pickling_unpickling(self):
self.assertIsInstance(pickle.loads(pickle.dumps(UTC())), UTC) self.assertIsInstance(pickle.loads(pickle.dumps(timezone.UTC())), timezone.UTC)
self.assertIsInstance(pickle.loads(pickle.dumps(LocalTimezone())), LocalTimezone) self.assertIsInstance(pickle.loads(pickle.dumps(timezone.LocalTimezone())), timezone.LocalTimezone)