diff --git a/django/utils/timezone.py b/django/utils/timezone.py index 20d59f5fed..e2c6967607 100644 --- a/django/utils/timezone.py +++ b/django/utils/timezone.py @@ -299,13 +299,18 @@ def template_localtime(value, use_tz=None): # Utilities -def localtime(value, timezone=None): +def localtime(value=None, timezone=None): """ Converts an aware datetime.datetime to local time. + Only aware datetimes are allowed. When value is omitted, it defaults to + now(). + Local time is defined by the current time zone, unless another time zone is specified. """ + if value is None: + value = now() if timezone is None: timezone = get_current_timezone() # If `value` is naive, astimezone() will raise a ValueError, @@ -317,6 +322,19 @@ def localtime(value, timezone=None): return value +def localdate(value=None, timezone=None): + """ + Convert an aware datetime to local time and return the value's date. + + Only aware datetimes are allowed. When value is omitted, it defaults to + now(). + + Local time is defined by the current time zone, unless another time zone is + specified. + """ + return localtime(value, timezone).date() + + def now(): """ Returns an aware or naive datetime.datetime, depending on settings.USE_TZ. diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index 52b3b39062..938c538535 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -792,6 +792,6 @@ def timezone_today(): Return the current date in the current time zone. """ if settings.USE_TZ: - return timezone.localtime(timezone.now()).date() + return timezone.localdate() else: return datetime.date.today() diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 893ecb5792..00ac7970d7 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -974,14 +974,32 @@ appropriate entities. ``override`` is also usable as a function decorator. -.. function:: localtime(value, timezone=None) +.. function:: localtime(value=None, timezone=None) Converts an aware :class:`~datetime.datetime` to a different time zone, by default the :ref:`current time zone `. + When ``value`` is omitted, it defaults to :func:`now`. + This function doesn't work on naive datetimes; use :func:`make_aware` instead. + .. versionchanged:: 1.11 + + In older versions, ``value`` is a required argument. + +.. function:: localdate(value=None, timezone=None) + + .. versionadded:: 1.11 + + Uses :func:`localtime` to convert an aware :class:`~datetime.datetime` to a + :meth:`~datetime.datetime.date` in a different time zone, by default the + :ref:`current time zone `. + + When ``value`` is omitted, it defaults to :func:`now`. + + This function doesn't work on naive datetimes. + .. function:: now() Returns a :class:`~datetime.datetime` that represents the diff --git a/tests/utils_tests/test_timezone.py b/tests/utils_tests/test_timezone.py index 5f21c06bae..9dc7941b4b 100644 --- a/tests/utils_tests/test_timezone.py +++ b/tests/utils_tests/test_timezone.py @@ -4,7 +4,7 @@ import pickle import sys import unittest -from django.test import SimpleTestCase, override_settings +from django.test import SimpleTestCase, mock, override_settings from django.utils import timezone try: @@ -27,7 +27,10 @@ class TimezoneTests(SimpleTestCase): def test_localtime(self): now = datetime.datetime.utcnow().replace(tzinfo=timezone.utc) local_tz = timezone.LocalTimezone() - local_now = timezone.localtime(now, local_tz) + with timezone.override(local_tz): + local_now = timezone.localtime(now) + self.assertEqual(local_now.tzinfo, local_tz) + local_now = timezone.localtime(now, timezone=local_tz) self.assertEqual(local_now.tzinfo, local_tz) def test_localtime_naive(self): @@ -54,6 +57,27 @@ class TimezoneTests(SimpleTestCase): with override_settings(USE_TZ=False): self.assertTrue(timezone.is_naive(timezone.now())) + def test_localdate(self): + naive = datetime.datetime(2015, 1, 1, 0, 0, 1) + if PY36: + self.assertEqual(timezone.localdate(naive), datetime.date(2015, 1, 1)) + self.assertEqual(timezone.localdate(naive, timezone=EAT), datetime.date(2015, 1, 1)) + else: + with self.assertRaisesMessage(ValueError, 'astimezone() cannot be applied to a naive datetime'): + timezone.localdate(naive) + with self.assertRaisesMessage(ValueError, 'astimezone() cannot be applied to a naive datetime'): + timezone.localdate(naive, timezone=EAT) + + aware = datetime.datetime(2015, 1, 1, 0, 0, 1, tzinfo=ICT) + self.assertEqual(timezone.localdate(aware, timezone=EAT), datetime.date(2014, 12, 31)) + with timezone.override(EAT): + self.assertEqual(timezone.localdate(aware), datetime.date(2014, 12, 31)) + + with mock.patch('django.utils.timezone.now', return_value=aware): + self.assertEqual(timezone.localdate(timezone=EAT), datetime.date(2014, 12, 31)) + with timezone.override(EAT): + self.assertEqual(timezone.localdate(), datetime.date(2014, 12, 31)) + def test_override(self): default = timezone.get_default_timezone() try: