Browse Source

Refs #29724 -- Added is_dst parameter to QuerySet.datetimes().

Thanks Simon Charette for the review and Mariusz Felisiak for tests.
Hasan Ramezani 5 years ago
parent
commit
53b6a466d8
4 changed files with 56 additions and 3 deletions
  1. 8 2
      django/db/models/query.py
  2. 9 1
      docs/ref/models/querysets.txt
  3. 3 0
      docs/releases/3.1.txt
  4. 36 0
      tests/datetimes/tests.py

+ 8 - 2
django/db/models/query.py

@@ -875,7 +875,7 @@ class QuerySet:
             'datefield', flat=True
         ).distinct().filter(plain_field__isnull=False).order_by(('-' if order == 'DESC' else '') + 'datefield')
 
-    def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
+    def datetimes(self, field_name, kind, order='ASC', tzinfo=None, is_dst=None):
         """
         Return a list of datetime objects representing all available
         datetimes for the given field_name, scoped to 'kind'.
@@ -890,7 +890,13 @@ class QuerySet:
         else:
             tzinfo = None
         return self.annotate(
-            datetimefield=Trunc(field_name, kind, output_field=DateTimeField(), tzinfo=tzinfo),
+            datetimefield=Trunc(
+                field_name,
+                kind,
+                output_field=DateTimeField(),
+                tzinfo=tzinfo,
+                is_dst=is_dst,
+            ),
             plain_field=F(field_name)
         ).values_list(
             'datetimefield', flat=True

+ 9 - 1
docs/ref/models/querysets.txt

@@ -759,7 +759,7 @@ Examples::
 ``datetimes()``
 ~~~~~~~~~~~~~~~
 
-.. method:: datetimes(field_name, kind, order='ASC', tzinfo=None)
+.. method:: datetimes(field_name, kind, order='ASC', tzinfo=None, is_dst=None)
 
 Returns a ``QuerySet`` that evaluates to a list of :class:`datetime.datetime`
 objects representing all available dates of a particular kind within the
@@ -781,6 +781,14 @@ object. If it's ``None``, Django uses the :ref:`current time zone
 <default-current-time-zone>`. It has no effect when :setting:`USE_TZ` is
 ``False``.
 
+``is_dst`` indicates whether or not ``pytz`` should interpret nonexistent and
+ambiguous datetimes in daylight saving time. By default (when ``is_dst=None``),
+``pytz`` raises an exception for such datetimes.
+
+.. versionadded:: 3.1
+
+    The ``is_dst`` parameter was added.
+
 .. _database-time-zone-definitions:
 
 .. note::

+ 3 - 0
docs/releases/3.1.txt

@@ -324,6 +324,9 @@ Models
   :meth:`~.RelatedManager.set` methods now accept callables as values in the
   ``through_defaults`` argument.
 
+* The new ``is_dst``  parameter of the :meth:`.QuerySet.datetimes` determines
+  the treatment of nonexistent and ambiguous datetimes.
+
 Pagination
 ~~~~~~~~~~
 

+ 36 - 0
tests/datetimes/tests.py

@@ -1,5 +1,7 @@
 import datetime
 
+import pytz
+
 from django.test import TestCase, override_settings
 from django.utils import timezone
 
@@ -89,6 +91,40 @@ class DateTimesTests(TestCase):
         qs = Article.objects.datetimes('pub_date', 'second')
         self.assertEqual(qs[0], now)
 
+    @override_settings(USE_TZ=True, TIME_ZONE='UTC')
+    def test_datetimes_ambiguous_and_invalid_times(self):
+        sao = pytz.timezone('America/Sao_Paulo')
+        utc = pytz.UTC
+        article = Article.objects.create(
+            title='Article 1',
+            pub_date=utc.localize(datetime.datetime(2016, 2, 21, 1)),
+        )
+        Comment.objects.create(
+            article=article,
+            pub_date=utc.localize(datetime.datetime(2016, 10, 16, 13)),
+        )
+        with timezone.override(sao):
+            with self.assertRaisesMessage(pytz.AmbiguousTimeError, '2016-02-20 23:00:00'):
+                Article.objects.datetimes('pub_date', 'hour').get()
+            with self.assertRaisesMessage(pytz.NonExistentTimeError, '2016-10-16 00:00:00'):
+                Comment.objects.datetimes('pub_date', 'day').get()
+            self.assertEqual(
+                Article.objects.datetimes('pub_date', 'hour', is_dst=False).get().dst(),
+                datetime.timedelta(0),
+            )
+            self.assertEqual(
+                Comment.objects.datetimes('pub_date', 'day', is_dst=False).get().dst(),
+                datetime.timedelta(0),
+            )
+            self.assertEqual(
+                Article.objects.datetimes('pub_date', 'hour', is_dst=True).get().dst(),
+                datetime.timedelta(0, 3600),
+            )
+            self.assertEqual(
+                Comment.objects.datetimes('pub_date', 'hour', is_dst=True).get().dst(),
+                datetime.timedelta(0, 3600),
+            )
+
     def test_datetimes_returns_available_dates_for_given_scope_and_given_field(self):
         pub_dates = [
             datetime.datetime(2005, 7, 28, 12, 15),