1. import unittest
    
  2. from datetime import datetime, timedelta
    
  3. from datetime import timezone as datetime_timezone
    
  4. 
    
  5. try:
    
  6.     import zoneinfo
    
  7. except ImportError:
    
  8.     from backports import zoneinfo
    
  9. 
    
  10. try:
    
  11.     import pytz
    
  12. except ImportError:
    
  13.     pytz = None
    
  14. 
    
  15. from django.conf import settings
    
  16. from django.db import DataError, OperationalError
    
  17. from django.db.models import (
    
  18.     DateField,
    
  19.     DateTimeField,
    
  20.     F,
    
  21.     IntegerField,
    
  22.     Max,
    
  23.     OuterRef,
    
  24.     Subquery,
    
  25.     TimeField,
    
  26. )
    
  27. from django.db.models.functions import (
    
  28.     Extract,
    
  29.     ExtractDay,
    
  30.     ExtractHour,
    
  31.     ExtractIsoWeekDay,
    
  32.     ExtractIsoYear,
    
  33.     ExtractMinute,
    
  34.     ExtractMonth,
    
  35.     ExtractQuarter,
    
  36.     ExtractSecond,
    
  37.     ExtractWeek,
    
  38.     ExtractWeekDay,
    
  39.     ExtractYear,
    
  40.     Trunc,
    
  41.     TruncDate,
    
  42.     TruncDay,
    
  43.     TruncHour,
    
  44.     TruncMinute,
    
  45.     TruncMonth,
    
  46.     TruncQuarter,
    
  47.     TruncSecond,
    
  48.     TruncTime,
    
  49.     TruncWeek,
    
  50.     TruncYear,
    
  51. )
    
  52. from django.test import (
    
  53.     TestCase,
    
  54.     ignore_warnings,
    
  55.     override_settings,
    
  56.     skipIfDBFeature,
    
  57.     skipUnlessDBFeature,
    
  58. )
    
  59. from django.utils import timezone
    
  60. from django.utils.deprecation import RemovedInDjango50Warning
    
  61. 
    
  62. from ..models import Author, DTModel, Fan
    
  63. 
    
  64. HAS_PYTZ = pytz is not None
    
  65. if not HAS_PYTZ:
    
  66.     needs_pytz = unittest.skip("Test requires pytz")
    
  67. else:
    
  68. 
    
  69.     def needs_pytz(f):
    
  70.         return f
    
  71. 
    
  72. 
    
  73. ZONE_CONSTRUCTORS = (zoneinfo.ZoneInfo,)
    
  74. if HAS_PYTZ:
    
  75.     ZONE_CONSTRUCTORS += (pytz.timezone,)
    
  76. 
    
  77. 
    
  78. def truncate_to(value, kind, tzinfo=None):
    
  79.     # Convert to target timezone before truncation
    
  80.     if tzinfo is not None:
    
  81.         value = value.astimezone(tzinfo)
    
  82. 
    
  83.     def truncate(value, kind):
    
  84.         if kind == "second":
    
  85.             return value.replace(microsecond=0)
    
  86.         if kind == "minute":
    
  87.             return value.replace(second=0, microsecond=0)
    
  88.         if kind == "hour":
    
  89.             return value.replace(minute=0, second=0, microsecond=0)
    
  90.         if kind == "day":
    
  91.             if isinstance(value, datetime):
    
  92.                 return value.replace(hour=0, minute=0, second=0, microsecond=0)
    
  93.             return value
    
  94.         if kind == "week":
    
  95.             if isinstance(value, datetime):
    
  96.                 return (value - timedelta(days=value.weekday())).replace(
    
  97.                     hour=0, minute=0, second=0, microsecond=0
    
  98.                 )
    
  99.             return value - timedelta(days=value.weekday())
    
  100.         if kind == "month":
    
  101.             if isinstance(value, datetime):
    
  102.                 return value.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
    
  103.             return value.replace(day=1)
    
  104.         if kind == "quarter":
    
  105.             month_in_quarter = value.month - (value.month - 1) % 3
    
  106.             if isinstance(value, datetime):
    
  107.                 return value.replace(
    
  108.                     month=month_in_quarter,
    
  109.                     day=1,
    
  110.                     hour=0,
    
  111.                     minute=0,
    
  112.                     second=0,
    
  113.                     microsecond=0,
    
  114.                 )
    
  115.             return value.replace(month=month_in_quarter, day=1)
    
  116.         # otherwise, truncate to year
    
  117.         if isinstance(value, datetime):
    
  118.             return value.replace(
    
  119.                 month=1, day=1, hour=0, minute=0, second=0, microsecond=0
    
  120.             )
    
  121.         return value.replace(month=1, day=1)
    
  122. 
    
  123.     value = truncate(value, kind)
    
  124.     if tzinfo is not None:
    
  125.         # If there was a daylight saving transition, then reset the timezone.
    
  126.         value = timezone.make_aware(value.replace(tzinfo=None), tzinfo)
    
  127.     return value
    
  128. 
    
  129. 
    
  130. @override_settings(USE_TZ=False)
    
  131. class DateFunctionTests(TestCase):
    
  132.     def create_model(self, start_datetime, end_datetime):
    
  133.         return DTModel.objects.create(
    
  134.             name=start_datetime.isoformat() if start_datetime else "None",
    
  135.             start_datetime=start_datetime,
    
  136.             end_datetime=end_datetime,
    
  137.             start_date=start_datetime.date() if start_datetime else None,
    
  138.             end_date=end_datetime.date() if end_datetime else None,
    
  139.             start_time=start_datetime.time() if start_datetime else None,
    
  140.             end_time=end_datetime.time() if end_datetime else None,
    
  141.             duration=(end_datetime - start_datetime)
    
  142.             if start_datetime and end_datetime
    
  143.             else None,
    
  144.         )
    
  145. 
    
  146.     def test_extract_year_exact_lookup(self):
    
  147.         """
    
  148.         Extract year uses a BETWEEN filter to compare the year to allow indexes
    
  149.         to be used.
    
  150.         """
    
  151.         start_datetime = datetime(2015, 6, 15, 14, 10)
    
  152.         end_datetime = datetime(2016, 6, 15, 14, 10)
    
  153.         if settings.USE_TZ:
    
  154.             start_datetime = timezone.make_aware(start_datetime)
    
  155.             end_datetime = timezone.make_aware(end_datetime)
    
  156.         self.create_model(start_datetime, end_datetime)
    
  157.         self.create_model(end_datetime, start_datetime)
    
  158. 
    
  159.         for lookup in ("year", "iso_year"):
    
  160.             with self.subTest(lookup):
    
  161.                 qs = DTModel.objects.filter(
    
  162.                     **{"start_datetime__%s__exact" % lookup: 2015}
    
  163.                 )
    
  164.                 self.assertEqual(qs.count(), 1)
    
  165.                 query_string = str(qs.query).lower()
    
  166.                 self.assertEqual(query_string.count(" between "), 1)
    
  167.                 self.assertEqual(query_string.count("extract"), 0)
    
  168.                 # exact is implied and should be the same
    
  169.                 qs = DTModel.objects.filter(**{"start_datetime__%s" % lookup: 2015})
    
  170.                 self.assertEqual(qs.count(), 1)
    
  171.                 query_string = str(qs.query).lower()
    
  172.                 self.assertEqual(query_string.count(" between "), 1)
    
  173.                 self.assertEqual(query_string.count("extract"), 0)
    
  174.                 # date and datetime fields should behave the same
    
  175.                 qs = DTModel.objects.filter(**{"start_date__%s" % lookup: 2015})
    
  176.                 self.assertEqual(qs.count(), 1)
    
  177.                 query_string = str(qs.query).lower()
    
  178.                 self.assertEqual(query_string.count(" between "), 1)
    
  179.                 self.assertEqual(query_string.count("extract"), 0)
    
  180.                 # an expression rhs cannot use the between optimization.
    
  181.                 qs = DTModel.objects.annotate(
    
  182.                     start_year=ExtractYear("start_datetime"),
    
  183.                 ).filter(end_datetime__year=F("start_year") + 1)
    
  184.                 self.assertEqual(qs.count(), 1)
    
  185.                 query_string = str(qs.query).lower()
    
  186.                 self.assertEqual(query_string.count(" between "), 0)
    
  187.                 self.assertEqual(query_string.count("extract"), 3)
    
  188. 
    
  189.     def test_extract_year_greaterthan_lookup(self):
    
  190.         start_datetime = datetime(2015, 6, 15, 14, 10)
    
  191.         end_datetime = datetime(2016, 6, 15, 14, 10)
    
  192.         if settings.USE_TZ:
    
  193.             start_datetime = timezone.make_aware(start_datetime)
    
  194.             end_datetime = timezone.make_aware(end_datetime)
    
  195.         self.create_model(start_datetime, end_datetime)
    
  196.         self.create_model(end_datetime, start_datetime)
    
  197. 
    
  198.         for lookup in ("year", "iso_year"):
    
  199.             with self.subTest(lookup):
    
  200.                 qs = DTModel.objects.filter(**{"start_datetime__%s__gt" % lookup: 2015})
    
  201.                 self.assertEqual(qs.count(), 1)
    
  202.                 self.assertEqual(str(qs.query).lower().count("extract"), 0)
    
  203.                 qs = DTModel.objects.filter(
    
  204.                     **{"start_datetime__%s__gte" % lookup: 2015}
    
  205.                 )
    
  206.                 self.assertEqual(qs.count(), 2)
    
  207.                 self.assertEqual(str(qs.query).lower().count("extract"), 0)
    
  208.                 qs = DTModel.objects.annotate(
    
  209.                     start_year=ExtractYear("start_datetime"),
    
  210.                 ).filter(**{"end_datetime__%s__gte" % lookup: F("start_year")})
    
  211.                 self.assertEqual(qs.count(), 1)
    
  212.                 self.assertGreaterEqual(str(qs.query).lower().count("extract"), 2)
    
  213. 
    
  214.     def test_extract_year_lessthan_lookup(self):
    
  215.         start_datetime = datetime(2015, 6, 15, 14, 10)
    
  216.         end_datetime = datetime(2016, 6, 15, 14, 10)
    
  217.         if settings.USE_TZ:
    
  218.             start_datetime = timezone.make_aware(start_datetime)
    
  219.             end_datetime = timezone.make_aware(end_datetime)
    
  220.         self.create_model(start_datetime, end_datetime)
    
  221.         self.create_model(end_datetime, start_datetime)
    
  222. 
    
  223.         for lookup in ("year", "iso_year"):
    
  224.             with self.subTest(lookup):
    
  225.                 qs = DTModel.objects.filter(**{"start_datetime__%s__lt" % lookup: 2016})
    
  226.                 self.assertEqual(qs.count(), 1)
    
  227.                 self.assertEqual(str(qs.query).count("extract"), 0)
    
  228.                 qs = DTModel.objects.filter(
    
  229.                     **{"start_datetime__%s__lte" % lookup: 2016}
    
  230.                 )
    
  231.                 self.assertEqual(qs.count(), 2)
    
  232.                 self.assertEqual(str(qs.query).count("extract"), 0)
    
  233.                 qs = DTModel.objects.annotate(
    
  234.                     end_year=ExtractYear("end_datetime"),
    
  235.                 ).filter(**{"start_datetime__%s__lte" % lookup: F("end_year")})
    
  236.                 self.assertEqual(qs.count(), 1)
    
  237.                 self.assertGreaterEqual(str(qs.query).lower().count("extract"), 2)
    
  238. 
    
  239.     def test_extract_lookup_name_sql_injection(self):
    
  240.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  241.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  242.         if settings.USE_TZ:
    
  243.             start_datetime = timezone.make_aware(start_datetime)
    
  244.             end_datetime = timezone.make_aware(end_datetime)
    
  245.         self.create_model(start_datetime, end_datetime)
    
  246.         self.create_model(end_datetime, start_datetime)
    
  247. 
    
  248.         with self.assertRaises((DataError, OperationalError, ValueError)):
    
  249.             DTModel.objects.filter(
    
  250.                 start_datetime__year=Extract(
    
  251.                     "start_datetime", "day' FROM start_datetime)) OR 1=1;--"
    
  252.                 )
    
  253.             ).exists()
    
  254. 
    
  255.     def test_extract_func(self):
    
  256.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  257.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  258.         if settings.USE_TZ:
    
  259.             start_datetime = timezone.make_aware(start_datetime)
    
  260.             end_datetime = timezone.make_aware(end_datetime)
    
  261.         self.create_model(start_datetime, end_datetime)
    
  262.         self.create_model(end_datetime, start_datetime)
    
  263. 
    
  264.         with self.assertRaisesMessage(ValueError, "lookup_name must be provided"):
    
  265.             Extract("start_datetime")
    
  266. 
    
  267.         msg = (
    
  268.             "Extract input expression must be DateField, DateTimeField, TimeField, or "
    
  269.             "DurationField."
    
  270.         )
    
  271.         with self.assertRaisesMessage(ValueError, msg):
    
  272.             list(DTModel.objects.annotate(extracted=Extract("name", "hour")))
    
  273. 
    
  274.         with self.assertRaisesMessage(
    
  275.             ValueError,
    
  276.             "Cannot extract time component 'second' from DateField 'start_date'.",
    
  277.         ):
    
  278.             list(DTModel.objects.annotate(extracted=Extract("start_date", "second")))
    
  279. 
    
  280.         self.assertQuerysetEqual(
    
  281.             DTModel.objects.annotate(
    
  282.                 extracted=Extract("start_datetime", "year")
    
  283.             ).order_by("start_datetime"),
    
  284.             [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
    
  285.             lambda m: (m.start_datetime, m.extracted),
    
  286.         )
    
  287.         self.assertQuerysetEqual(
    
  288.             DTModel.objects.annotate(
    
  289.                 extracted=Extract("start_datetime", "quarter")
    
  290.             ).order_by("start_datetime"),
    
  291.             [(start_datetime, 2), (end_datetime, 2)],
    
  292.             lambda m: (m.start_datetime, m.extracted),
    
  293.         )
    
  294.         self.assertQuerysetEqual(
    
  295.             DTModel.objects.annotate(
    
  296.                 extracted=Extract("start_datetime", "month")
    
  297.             ).order_by("start_datetime"),
    
  298.             [
    
  299.                 (start_datetime, start_datetime.month),
    
  300.                 (end_datetime, end_datetime.month),
    
  301.             ],
    
  302.             lambda m: (m.start_datetime, m.extracted),
    
  303.         )
    
  304.         self.assertQuerysetEqual(
    
  305.             DTModel.objects.annotate(
    
  306.                 extracted=Extract("start_datetime", "day")
    
  307.             ).order_by("start_datetime"),
    
  308.             [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
    
  309.             lambda m: (m.start_datetime, m.extracted),
    
  310.         )
    
  311.         self.assertQuerysetEqual(
    
  312.             DTModel.objects.annotate(
    
  313.                 extracted=Extract("start_datetime", "week")
    
  314.             ).order_by("start_datetime"),
    
  315.             [(start_datetime, 25), (end_datetime, 24)],
    
  316.             lambda m: (m.start_datetime, m.extracted),
    
  317.         )
    
  318.         self.assertQuerysetEqual(
    
  319.             DTModel.objects.annotate(
    
  320.                 extracted=Extract("start_datetime", "week_day")
    
  321.             ).order_by("start_datetime"),
    
  322.             [
    
  323.                 (start_datetime, (start_datetime.isoweekday() % 7) + 1),
    
  324.                 (end_datetime, (end_datetime.isoweekday() % 7) + 1),
    
  325.             ],
    
  326.             lambda m: (m.start_datetime, m.extracted),
    
  327.         )
    
  328.         self.assertQuerysetEqual(
    
  329.             DTModel.objects.annotate(
    
  330.                 extracted=Extract("start_datetime", "iso_week_day"),
    
  331.             ).order_by("start_datetime"),
    
  332.             [
    
  333.                 (start_datetime, start_datetime.isoweekday()),
    
  334.                 (end_datetime, end_datetime.isoweekday()),
    
  335.             ],
    
  336.             lambda m: (m.start_datetime, m.extracted),
    
  337.         )
    
  338.         self.assertQuerysetEqual(
    
  339.             DTModel.objects.annotate(
    
  340.                 extracted=Extract("start_datetime", "hour")
    
  341.             ).order_by("start_datetime"),
    
  342.             [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
    
  343.             lambda m: (m.start_datetime, m.extracted),
    
  344.         )
    
  345.         self.assertQuerysetEqual(
    
  346.             DTModel.objects.annotate(
    
  347.                 extracted=Extract("start_datetime", "minute")
    
  348.             ).order_by("start_datetime"),
    
  349.             [
    
  350.                 (start_datetime, start_datetime.minute),
    
  351.                 (end_datetime, end_datetime.minute),
    
  352.             ],
    
  353.             lambda m: (m.start_datetime, m.extracted),
    
  354.         )
    
  355.         self.assertQuerysetEqual(
    
  356.             DTModel.objects.annotate(
    
  357.                 extracted=Extract("start_datetime", "second")
    
  358.             ).order_by("start_datetime"),
    
  359.             [
    
  360.                 (start_datetime, start_datetime.second),
    
  361.                 (end_datetime, end_datetime.second),
    
  362.             ],
    
  363.             lambda m: (m.start_datetime, m.extracted),
    
  364.         )
    
  365.         self.assertEqual(
    
  366.             DTModel.objects.filter(
    
  367.                 start_datetime__year=Extract("start_datetime", "year")
    
  368.             ).count(),
    
  369.             2,
    
  370.         )
    
  371.         self.assertEqual(
    
  372.             DTModel.objects.filter(
    
  373.                 start_datetime__hour=Extract("start_datetime", "hour")
    
  374.             ).count(),
    
  375.             2,
    
  376.         )
    
  377.         self.assertEqual(
    
  378.             DTModel.objects.filter(
    
  379.                 start_date__month=Extract("start_date", "month")
    
  380.             ).count(),
    
  381.             2,
    
  382.         )
    
  383.         self.assertEqual(
    
  384.             DTModel.objects.filter(
    
  385.                 start_time__hour=Extract("start_time", "hour")
    
  386.             ).count(),
    
  387.             2,
    
  388.         )
    
  389. 
    
  390.     def test_extract_none(self):
    
  391.         self.create_model(None, None)
    
  392.         for t in (
    
  393.             Extract("start_datetime", "year"),
    
  394.             Extract("start_date", "year"),
    
  395.             Extract("start_time", "hour"),
    
  396.         ):
    
  397.             with self.subTest(t):
    
  398.                 self.assertIsNone(
    
  399.                     DTModel.objects.annotate(extracted=t).first().extracted
    
  400.                 )
    
  401. 
    
  402.     def test_extract_outerref_validation(self):
    
  403.         inner_qs = DTModel.objects.filter(name=ExtractMonth(OuterRef("name")))
    
  404.         msg = (
    
  405.             "Extract input expression must be DateField, DateTimeField, "
    
  406.             "TimeField, or DurationField."
    
  407.         )
    
  408.         with self.assertRaisesMessage(ValueError, msg):
    
  409.             DTModel.objects.annotate(related_name=Subquery(inner_qs.values("name")[:1]))
    
  410. 
    
  411.     @skipUnlessDBFeature("has_native_duration_field")
    
  412.     def test_extract_duration(self):
    
  413.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  414.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  415.         if settings.USE_TZ:
    
  416.             start_datetime = timezone.make_aware(start_datetime)
    
  417.             end_datetime = timezone.make_aware(end_datetime)
    
  418.         self.create_model(start_datetime, end_datetime)
    
  419.         self.create_model(end_datetime, start_datetime)
    
  420.         self.assertQuerysetEqual(
    
  421.             DTModel.objects.annotate(extracted=Extract("duration", "second")).order_by(
    
  422.                 "start_datetime"
    
  423.             ),
    
  424.             [
    
  425.                 (start_datetime, (end_datetime - start_datetime).seconds % 60),
    
  426.                 (end_datetime, (start_datetime - end_datetime).seconds % 60),
    
  427.             ],
    
  428.             lambda m: (m.start_datetime, m.extracted),
    
  429.         )
    
  430.         self.assertEqual(
    
  431.             DTModel.objects.annotate(
    
  432.                 duration_days=Extract("duration", "day"),
    
  433.             )
    
  434.             .filter(duration_days__gt=200)
    
  435.             .count(),
    
  436.             1,
    
  437.         )
    
  438. 
    
  439.     @skipIfDBFeature("has_native_duration_field")
    
  440.     def test_extract_duration_without_native_duration_field(self):
    
  441.         msg = "Extract requires native DurationField database support."
    
  442.         with self.assertRaisesMessage(ValueError, msg):
    
  443.             list(DTModel.objects.annotate(extracted=Extract("duration", "second")))
    
  444. 
    
  445.     def test_extract_duration_unsupported_lookups(self):
    
  446.         msg = "Cannot extract component '%s' from DurationField 'duration'."
    
  447.         for lookup in (
    
  448.             "year",
    
  449.             "iso_year",
    
  450.             "month",
    
  451.             "week",
    
  452.             "week_day",
    
  453.             "iso_week_day",
    
  454.             "quarter",
    
  455.         ):
    
  456.             with self.subTest(lookup):
    
  457.                 with self.assertRaisesMessage(ValueError, msg % lookup):
    
  458.                     DTModel.objects.annotate(extracted=Extract("duration", lookup))
    
  459. 
    
  460.     def test_extract_year_func(self):
    
  461.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  462.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  463.         if settings.USE_TZ:
    
  464.             start_datetime = timezone.make_aware(start_datetime)
    
  465.             end_datetime = timezone.make_aware(end_datetime)
    
  466.         self.create_model(start_datetime, end_datetime)
    
  467.         self.create_model(end_datetime, start_datetime)
    
  468.         self.assertQuerysetEqual(
    
  469.             DTModel.objects.annotate(extracted=ExtractYear("start_datetime")).order_by(
    
  470.                 "start_datetime"
    
  471.             ),
    
  472.             [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
    
  473.             lambda m: (m.start_datetime, m.extracted),
    
  474.         )
    
  475.         self.assertQuerysetEqual(
    
  476.             DTModel.objects.annotate(extracted=ExtractYear("start_date")).order_by(
    
  477.                 "start_datetime"
    
  478.             ),
    
  479.             [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
    
  480.             lambda m: (m.start_datetime, m.extracted),
    
  481.         )
    
  482.         self.assertEqual(
    
  483.             DTModel.objects.filter(
    
  484.                 start_datetime__year=ExtractYear("start_datetime")
    
  485.             ).count(),
    
  486.             2,
    
  487.         )
    
  488. 
    
  489.     def test_extract_iso_year_func(self):
    
  490.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  491.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  492.         if settings.USE_TZ:
    
  493.             start_datetime = timezone.make_aware(start_datetime)
    
  494.             end_datetime = timezone.make_aware(end_datetime)
    
  495.         self.create_model(start_datetime, end_datetime)
    
  496.         self.create_model(end_datetime, start_datetime)
    
  497.         self.assertQuerysetEqual(
    
  498.             DTModel.objects.annotate(
    
  499.                 extracted=ExtractIsoYear("start_datetime")
    
  500.             ).order_by("start_datetime"),
    
  501.             [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
    
  502.             lambda m: (m.start_datetime, m.extracted),
    
  503.         )
    
  504.         self.assertQuerysetEqual(
    
  505.             DTModel.objects.annotate(extracted=ExtractIsoYear("start_date")).order_by(
    
  506.                 "start_datetime"
    
  507.             ),
    
  508.             [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
    
  509.             lambda m: (m.start_datetime, m.extracted),
    
  510.         )
    
  511.         # Both dates are from the same week year.
    
  512.         self.assertEqual(
    
  513.             DTModel.objects.filter(
    
  514.                 start_datetime__iso_year=ExtractIsoYear("start_datetime")
    
  515.             ).count(),
    
  516.             2,
    
  517.         )
    
  518. 
    
  519.     def test_extract_iso_year_func_boundaries(self):
    
  520.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  521.         if settings.USE_TZ:
    
  522.             end_datetime = timezone.make_aware(end_datetime)
    
  523.         week_52_day_2014 = datetime(2014, 12, 27, 13, 0)  # Sunday
    
  524.         week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0)  # Wednesday
    
  525.         week_53_day_2015 = datetime(2015, 12, 31, 13, 0)  # Thursday
    
  526.         if settings.USE_TZ:
    
  527.             week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015)
    
  528.             week_52_day_2014 = timezone.make_aware(week_52_day_2014)
    
  529.             week_53_day_2015 = timezone.make_aware(week_53_day_2015)
    
  530.         days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
    
  531.         obj_1_iso_2014 = self.create_model(week_52_day_2014, end_datetime)
    
  532.         obj_1_iso_2015 = self.create_model(week_1_day_2014_2015, end_datetime)
    
  533.         obj_2_iso_2015 = self.create_model(week_53_day_2015, end_datetime)
    
  534.         qs = (
    
  535.             DTModel.objects.filter(start_datetime__in=days)
    
  536.             .annotate(
    
  537.                 extracted=ExtractIsoYear("start_datetime"),
    
  538.             )
    
  539.             .order_by("start_datetime")
    
  540.         )
    
  541.         self.assertQuerysetEqual(
    
  542.             qs,
    
  543.             [
    
  544.                 (week_52_day_2014, 2014),
    
  545.                 (week_1_day_2014_2015, 2015),
    
  546.                 (week_53_day_2015, 2015),
    
  547.             ],
    
  548.             lambda m: (m.start_datetime, m.extracted),
    
  549.         )
    
  550. 
    
  551.         qs = DTModel.objects.filter(
    
  552.             start_datetime__iso_year=2015,
    
  553.         ).order_by("start_datetime")
    
  554.         self.assertSequenceEqual(qs, [obj_1_iso_2015, obj_2_iso_2015])
    
  555.         qs = DTModel.objects.filter(
    
  556.             start_datetime__iso_year__gt=2014,
    
  557.         ).order_by("start_datetime")
    
  558.         self.assertSequenceEqual(qs, [obj_1_iso_2015, obj_2_iso_2015])
    
  559.         qs = DTModel.objects.filter(
    
  560.             start_datetime__iso_year__lte=2014,
    
  561.         ).order_by("start_datetime")
    
  562.         self.assertSequenceEqual(qs, [obj_1_iso_2014])
    
  563. 
    
  564.     def test_extract_month_func(self):
    
  565.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  566.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  567.         if settings.USE_TZ:
    
  568.             start_datetime = timezone.make_aware(start_datetime)
    
  569.             end_datetime = timezone.make_aware(end_datetime)
    
  570.         self.create_model(start_datetime, end_datetime)
    
  571.         self.create_model(end_datetime, start_datetime)
    
  572.         self.assertQuerysetEqual(
    
  573.             DTModel.objects.annotate(extracted=ExtractMonth("start_datetime")).order_by(
    
  574.                 "start_datetime"
    
  575.             ),
    
  576.             [
    
  577.                 (start_datetime, start_datetime.month),
    
  578.                 (end_datetime, end_datetime.month),
    
  579.             ],
    
  580.             lambda m: (m.start_datetime, m.extracted),
    
  581.         )
    
  582.         self.assertQuerysetEqual(
    
  583.             DTModel.objects.annotate(extracted=ExtractMonth("start_date")).order_by(
    
  584.                 "start_datetime"
    
  585.             ),
    
  586.             [
    
  587.                 (start_datetime, start_datetime.month),
    
  588.                 (end_datetime, end_datetime.month),
    
  589.             ],
    
  590.             lambda m: (m.start_datetime, m.extracted),
    
  591.         )
    
  592.         self.assertEqual(
    
  593.             DTModel.objects.filter(
    
  594.                 start_datetime__month=ExtractMonth("start_datetime")
    
  595.             ).count(),
    
  596.             2,
    
  597.         )
    
  598. 
    
  599.     def test_extract_day_func(self):
    
  600.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  601.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  602.         if settings.USE_TZ:
    
  603.             start_datetime = timezone.make_aware(start_datetime)
    
  604.             end_datetime = timezone.make_aware(end_datetime)
    
  605.         self.create_model(start_datetime, end_datetime)
    
  606.         self.create_model(end_datetime, start_datetime)
    
  607.         self.assertQuerysetEqual(
    
  608.             DTModel.objects.annotate(extracted=ExtractDay("start_datetime")).order_by(
    
  609.                 "start_datetime"
    
  610.             ),
    
  611.             [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
    
  612.             lambda m: (m.start_datetime, m.extracted),
    
  613.         )
    
  614.         self.assertQuerysetEqual(
    
  615.             DTModel.objects.annotate(extracted=ExtractDay("start_date")).order_by(
    
  616.                 "start_datetime"
    
  617.             ),
    
  618.             [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
    
  619.             lambda m: (m.start_datetime, m.extracted),
    
  620.         )
    
  621.         self.assertEqual(
    
  622.             DTModel.objects.filter(
    
  623.                 start_datetime__day=ExtractDay("start_datetime")
    
  624.             ).count(),
    
  625.             2,
    
  626.         )
    
  627. 
    
  628.     def test_extract_week_func(self):
    
  629.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  630.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  631.         if settings.USE_TZ:
    
  632.             start_datetime = timezone.make_aware(start_datetime)
    
  633.             end_datetime = timezone.make_aware(end_datetime)
    
  634.         self.create_model(start_datetime, end_datetime)
    
  635.         self.create_model(end_datetime, start_datetime)
    
  636.         self.assertQuerysetEqual(
    
  637.             DTModel.objects.annotate(extracted=ExtractWeek("start_datetime")).order_by(
    
  638.                 "start_datetime"
    
  639.             ),
    
  640.             [(start_datetime, 25), (end_datetime, 24)],
    
  641.             lambda m: (m.start_datetime, m.extracted),
    
  642.         )
    
  643.         self.assertQuerysetEqual(
    
  644.             DTModel.objects.annotate(extracted=ExtractWeek("start_date")).order_by(
    
  645.                 "start_datetime"
    
  646.             ),
    
  647.             [(start_datetime, 25), (end_datetime, 24)],
    
  648.             lambda m: (m.start_datetime, m.extracted),
    
  649.         )
    
  650.         # both dates are from the same week.
    
  651.         self.assertEqual(
    
  652.             DTModel.objects.filter(
    
  653.                 start_datetime__week=ExtractWeek("start_datetime")
    
  654.             ).count(),
    
  655.             2,
    
  656.         )
    
  657. 
    
  658.     def test_extract_quarter_func(self):
    
  659.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  660.         end_datetime = datetime(2016, 8, 15, 14, 10, 50, 123)
    
  661.         if settings.USE_TZ:
    
  662.             start_datetime = timezone.make_aware(start_datetime)
    
  663.             end_datetime = timezone.make_aware(end_datetime)
    
  664.         self.create_model(start_datetime, end_datetime)
    
  665.         self.create_model(end_datetime, start_datetime)
    
  666.         self.assertQuerysetEqual(
    
  667.             DTModel.objects.annotate(
    
  668.                 extracted=ExtractQuarter("start_datetime")
    
  669.             ).order_by("start_datetime"),
    
  670.             [(start_datetime, 2), (end_datetime, 3)],
    
  671.             lambda m: (m.start_datetime, m.extracted),
    
  672.         )
    
  673.         self.assertQuerysetEqual(
    
  674.             DTModel.objects.annotate(extracted=ExtractQuarter("start_date")).order_by(
    
  675.                 "start_datetime"
    
  676.             ),
    
  677.             [(start_datetime, 2), (end_datetime, 3)],
    
  678.             lambda m: (m.start_datetime, m.extracted),
    
  679.         )
    
  680.         self.assertEqual(
    
  681.             DTModel.objects.filter(
    
  682.                 start_datetime__quarter=ExtractQuarter("start_datetime")
    
  683.             ).count(),
    
  684.             2,
    
  685.         )
    
  686. 
    
  687.     def test_extract_quarter_func_boundaries(self):
    
  688.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  689.         if settings.USE_TZ:
    
  690.             end_datetime = timezone.make_aware(end_datetime)
    
  691. 
    
  692.         last_quarter_2014 = datetime(2014, 12, 31, 13, 0)
    
  693.         first_quarter_2015 = datetime(2015, 1, 1, 13, 0)
    
  694.         if settings.USE_TZ:
    
  695.             last_quarter_2014 = timezone.make_aware(last_quarter_2014)
    
  696.             first_quarter_2015 = timezone.make_aware(first_quarter_2015)
    
  697.         dates = [last_quarter_2014, first_quarter_2015]
    
  698.         self.create_model(last_quarter_2014, end_datetime)
    
  699.         self.create_model(first_quarter_2015, end_datetime)
    
  700.         qs = (
    
  701.             DTModel.objects.filter(start_datetime__in=dates)
    
  702.             .annotate(
    
  703.                 extracted=ExtractQuarter("start_datetime"),
    
  704.             )
    
  705.             .order_by("start_datetime")
    
  706.         )
    
  707.         self.assertQuerysetEqual(
    
  708.             qs,
    
  709.             [
    
  710.                 (last_quarter_2014, 4),
    
  711.                 (first_quarter_2015, 1),
    
  712.             ],
    
  713.             lambda m: (m.start_datetime, m.extracted),
    
  714.         )
    
  715. 
    
  716.     def test_extract_week_func_boundaries(self):
    
  717.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  718.         if settings.USE_TZ:
    
  719.             end_datetime = timezone.make_aware(end_datetime)
    
  720. 
    
  721.         week_52_day_2014 = datetime(2014, 12, 27, 13, 0)  # Sunday
    
  722.         week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0)  # Wednesday
    
  723.         week_53_day_2015 = datetime(2015, 12, 31, 13, 0)  # Thursday
    
  724.         if settings.USE_TZ:
    
  725.             week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015)
    
  726.             week_52_day_2014 = timezone.make_aware(week_52_day_2014)
    
  727.             week_53_day_2015 = timezone.make_aware(week_53_day_2015)
    
  728. 
    
  729.         days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
    
  730.         self.create_model(week_53_day_2015, end_datetime)
    
  731.         self.create_model(week_52_day_2014, end_datetime)
    
  732.         self.create_model(week_1_day_2014_2015, end_datetime)
    
  733.         qs = (
    
  734.             DTModel.objects.filter(start_datetime__in=days)
    
  735.             .annotate(
    
  736.                 extracted=ExtractWeek("start_datetime"),
    
  737.             )
    
  738.             .order_by("start_datetime")
    
  739.         )
    
  740.         self.assertQuerysetEqual(
    
  741.             qs,
    
  742.             [
    
  743.                 (week_52_day_2014, 52),
    
  744.                 (week_1_day_2014_2015, 1),
    
  745.                 (week_53_day_2015, 53),
    
  746.             ],
    
  747.             lambda m: (m.start_datetime, m.extracted),
    
  748.         )
    
  749. 
    
  750.     def test_extract_weekday_func(self):
    
  751.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  752.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  753.         if settings.USE_TZ:
    
  754.             start_datetime = timezone.make_aware(start_datetime)
    
  755.             end_datetime = timezone.make_aware(end_datetime)
    
  756.         self.create_model(start_datetime, end_datetime)
    
  757.         self.create_model(end_datetime, start_datetime)
    
  758.         self.assertQuerysetEqual(
    
  759.             DTModel.objects.annotate(
    
  760.                 extracted=ExtractWeekDay("start_datetime")
    
  761.             ).order_by("start_datetime"),
    
  762.             [
    
  763.                 (start_datetime, (start_datetime.isoweekday() % 7) + 1),
    
  764.                 (end_datetime, (end_datetime.isoweekday() % 7) + 1),
    
  765.             ],
    
  766.             lambda m: (m.start_datetime, m.extracted),
    
  767.         )
    
  768.         self.assertQuerysetEqual(
    
  769.             DTModel.objects.annotate(extracted=ExtractWeekDay("start_date")).order_by(
    
  770.                 "start_datetime"
    
  771.             ),
    
  772.             [
    
  773.                 (start_datetime, (start_datetime.isoweekday() % 7) + 1),
    
  774.                 (end_datetime, (end_datetime.isoweekday() % 7) + 1),
    
  775.             ],
    
  776.             lambda m: (m.start_datetime, m.extracted),
    
  777.         )
    
  778.         self.assertEqual(
    
  779.             DTModel.objects.filter(
    
  780.                 start_datetime__week_day=ExtractWeekDay("start_datetime")
    
  781.             ).count(),
    
  782.             2,
    
  783.         )
    
  784. 
    
  785.     def test_extract_iso_weekday_func(self):
    
  786.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  787.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  788.         if settings.USE_TZ:
    
  789.             start_datetime = timezone.make_aware(start_datetime)
    
  790.             end_datetime = timezone.make_aware(end_datetime)
    
  791.         self.create_model(start_datetime, end_datetime)
    
  792.         self.create_model(end_datetime, start_datetime)
    
  793.         self.assertQuerysetEqual(
    
  794.             DTModel.objects.annotate(
    
  795.                 extracted=ExtractIsoWeekDay("start_datetime"),
    
  796.             ).order_by("start_datetime"),
    
  797.             [
    
  798.                 (start_datetime, start_datetime.isoweekday()),
    
  799.                 (end_datetime, end_datetime.isoweekday()),
    
  800.             ],
    
  801.             lambda m: (m.start_datetime, m.extracted),
    
  802.         )
    
  803.         self.assertQuerysetEqual(
    
  804.             DTModel.objects.annotate(
    
  805.                 extracted=ExtractIsoWeekDay("start_date"),
    
  806.             ).order_by("start_datetime"),
    
  807.             [
    
  808.                 (start_datetime, start_datetime.isoweekday()),
    
  809.                 (end_datetime, end_datetime.isoweekday()),
    
  810.             ],
    
  811.             lambda m: (m.start_datetime, m.extracted),
    
  812.         )
    
  813.         self.assertEqual(
    
  814.             DTModel.objects.filter(
    
  815.                 start_datetime__week_day=ExtractWeekDay("start_datetime"),
    
  816.             ).count(),
    
  817.             2,
    
  818.         )
    
  819. 
    
  820.     def test_extract_hour_func(self):
    
  821.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  822.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  823.         if settings.USE_TZ:
    
  824.             start_datetime = timezone.make_aware(start_datetime)
    
  825.             end_datetime = timezone.make_aware(end_datetime)
    
  826.         self.create_model(start_datetime, end_datetime)
    
  827.         self.create_model(end_datetime, start_datetime)
    
  828.         self.assertQuerysetEqual(
    
  829.             DTModel.objects.annotate(extracted=ExtractHour("start_datetime")).order_by(
    
  830.                 "start_datetime"
    
  831.             ),
    
  832.             [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
    
  833.             lambda m: (m.start_datetime, m.extracted),
    
  834.         )
    
  835.         self.assertQuerysetEqual(
    
  836.             DTModel.objects.annotate(extracted=ExtractHour("start_time")).order_by(
    
  837.                 "start_datetime"
    
  838.             ),
    
  839.             [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
    
  840.             lambda m: (m.start_datetime, m.extracted),
    
  841.         )
    
  842.         self.assertEqual(
    
  843.             DTModel.objects.filter(
    
  844.                 start_datetime__hour=ExtractHour("start_datetime")
    
  845.             ).count(),
    
  846.             2,
    
  847.         )
    
  848. 
    
  849.     def test_extract_minute_func(self):
    
  850.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  851.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  852.         if settings.USE_TZ:
    
  853.             start_datetime = timezone.make_aware(start_datetime)
    
  854.             end_datetime = timezone.make_aware(end_datetime)
    
  855.         self.create_model(start_datetime, end_datetime)
    
  856.         self.create_model(end_datetime, start_datetime)
    
  857.         self.assertQuerysetEqual(
    
  858.             DTModel.objects.annotate(
    
  859.                 extracted=ExtractMinute("start_datetime")
    
  860.             ).order_by("start_datetime"),
    
  861.             [
    
  862.                 (start_datetime, start_datetime.minute),
    
  863.                 (end_datetime, end_datetime.minute),
    
  864.             ],
    
  865.             lambda m: (m.start_datetime, m.extracted),
    
  866.         )
    
  867.         self.assertQuerysetEqual(
    
  868.             DTModel.objects.annotate(extracted=ExtractMinute("start_time")).order_by(
    
  869.                 "start_datetime"
    
  870.             ),
    
  871.             [
    
  872.                 (start_datetime, start_datetime.minute),
    
  873.                 (end_datetime, end_datetime.minute),
    
  874.             ],
    
  875.             lambda m: (m.start_datetime, m.extracted),
    
  876.         )
    
  877.         self.assertEqual(
    
  878.             DTModel.objects.filter(
    
  879.                 start_datetime__minute=ExtractMinute("start_datetime")
    
  880.             ).count(),
    
  881.             2,
    
  882.         )
    
  883. 
    
  884.     def test_extract_second_func(self):
    
  885.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  886.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  887.         if settings.USE_TZ:
    
  888.             start_datetime = timezone.make_aware(start_datetime)
    
  889.             end_datetime = timezone.make_aware(end_datetime)
    
  890.         self.create_model(start_datetime, end_datetime)
    
  891.         self.create_model(end_datetime, start_datetime)
    
  892.         self.assertQuerysetEqual(
    
  893.             DTModel.objects.annotate(
    
  894.                 extracted=ExtractSecond("start_datetime")
    
  895.             ).order_by("start_datetime"),
    
  896.             [
    
  897.                 (start_datetime, start_datetime.second),
    
  898.                 (end_datetime, end_datetime.second),
    
  899.             ],
    
  900.             lambda m: (m.start_datetime, m.extracted),
    
  901.         )
    
  902.         self.assertQuerysetEqual(
    
  903.             DTModel.objects.annotate(extracted=ExtractSecond("start_time")).order_by(
    
  904.                 "start_datetime"
    
  905.             ),
    
  906.             [
    
  907.                 (start_datetime, start_datetime.second),
    
  908.                 (end_datetime, end_datetime.second),
    
  909.             ],
    
  910.             lambda m: (m.start_datetime, m.extracted),
    
  911.         )
    
  912.         self.assertEqual(
    
  913.             DTModel.objects.filter(
    
  914.                 start_datetime__second=ExtractSecond("start_datetime")
    
  915.             ).count(),
    
  916.             2,
    
  917.         )
    
  918. 
    
  919.     def test_extract_second_func_no_fractional(self):
    
  920.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  921.         end_datetime = datetime(2016, 6, 15, 14, 30, 50, 783)
    
  922.         if settings.USE_TZ:
    
  923.             start_datetime = timezone.make_aware(start_datetime)
    
  924.             end_datetime = timezone.make_aware(end_datetime)
    
  925.         obj = self.create_model(start_datetime, end_datetime)
    
  926.         self.assertSequenceEqual(
    
  927.             DTModel.objects.filter(start_datetime__second=F("end_datetime__second")),
    
  928.             [obj],
    
  929.         )
    
  930.         self.assertSequenceEqual(
    
  931.             DTModel.objects.filter(start_time__second=F("end_time__second")),
    
  932.             [obj],
    
  933.         )
    
  934. 
    
  935.     def test_trunc_lookup_name_sql_injection(self):
    
  936.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  937.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  938.         if settings.USE_TZ:
    
  939.             start_datetime = timezone.make_aware(start_datetime)
    
  940.             end_datetime = timezone.make_aware(end_datetime)
    
  941.         self.create_model(start_datetime, end_datetime)
    
  942.         self.create_model(end_datetime, start_datetime)
    
  943.         # Database backends raise an exception or don't return any results.
    
  944.         try:
    
  945.             exists = DTModel.objects.filter(
    
  946.                 start_datetime__date=Trunc(
    
  947.                     "start_datetime",
    
  948.                     "year', start_datetime)) OR 1=1;--",
    
  949.                 )
    
  950.             ).exists()
    
  951.         except (DataError, OperationalError):
    
  952.             pass
    
  953.         else:
    
  954.             self.assertIs(exists, False)
    
  955. 
    
  956.     def test_trunc_func(self):
    
  957.         start_datetime = datetime(999, 6, 15, 14, 30, 50, 321)
    
  958.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  959.         if settings.USE_TZ:
    
  960.             start_datetime = timezone.make_aware(start_datetime)
    
  961.             end_datetime = timezone.make_aware(end_datetime)
    
  962.         self.create_model(start_datetime, end_datetime)
    
  963.         self.create_model(end_datetime, start_datetime)
    
  964. 
    
  965.         def test_datetime_kind(kind):
    
  966.             self.assertQuerysetEqual(
    
  967.                 DTModel.objects.annotate(
    
  968.                     truncated=Trunc(
    
  969.                         "start_datetime", kind, output_field=DateTimeField()
    
  970.                     )
    
  971.                 ).order_by("start_datetime"),
    
  972.                 [
    
  973.                     (start_datetime, truncate_to(start_datetime, kind)),
    
  974.                     (end_datetime, truncate_to(end_datetime, kind)),
    
  975.                 ],
    
  976.                 lambda m: (m.start_datetime, m.truncated),
    
  977.             )
    
  978. 
    
  979.         def test_date_kind(kind):
    
  980.             self.assertQuerysetEqual(
    
  981.                 DTModel.objects.annotate(
    
  982.                     truncated=Trunc("start_date", kind, output_field=DateField())
    
  983.                 ).order_by("start_datetime"),
    
  984.                 [
    
  985.                     (start_datetime, truncate_to(start_datetime.date(), kind)),
    
  986.                     (end_datetime, truncate_to(end_datetime.date(), kind)),
    
  987.                 ],
    
  988.                 lambda m: (m.start_datetime, m.truncated),
    
  989.             )
    
  990. 
    
  991.         def test_time_kind(kind):
    
  992.             self.assertQuerysetEqual(
    
  993.                 DTModel.objects.annotate(
    
  994.                     truncated=Trunc("start_time", kind, output_field=TimeField())
    
  995.                 ).order_by("start_datetime"),
    
  996.                 [
    
  997.                     (start_datetime, truncate_to(start_datetime.time(), kind)),
    
  998.                     (end_datetime, truncate_to(end_datetime.time(), kind)),
    
  999.                 ],
    
  1000.                 lambda m: (m.start_datetime, m.truncated),
    
  1001.             )
    
  1002. 
    
  1003.         def test_datetime_to_time_kind(kind):
    
  1004.             self.assertQuerysetEqual(
    
  1005.                 DTModel.objects.annotate(
    
  1006.                     truncated=Trunc("start_datetime", kind, output_field=TimeField()),
    
  1007.                 ).order_by("start_datetime"),
    
  1008.                 [
    
  1009.                     (start_datetime, truncate_to(start_datetime.time(), kind)),
    
  1010.                     (end_datetime, truncate_to(end_datetime.time(), kind)),
    
  1011.                 ],
    
  1012.                 lambda m: (m.start_datetime, m.truncated),
    
  1013.             )
    
  1014. 
    
  1015.         test_date_kind("year")
    
  1016.         test_date_kind("quarter")
    
  1017.         test_date_kind("month")
    
  1018.         test_date_kind("day")
    
  1019.         test_time_kind("hour")
    
  1020.         test_time_kind("minute")
    
  1021.         test_time_kind("second")
    
  1022.         test_datetime_kind("year")
    
  1023.         test_datetime_kind("quarter")
    
  1024.         test_datetime_kind("month")
    
  1025.         test_datetime_kind("day")
    
  1026.         test_datetime_kind("hour")
    
  1027.         test_datetime_kind("minute")
    
  1028.         test_datetime_kind("second")
    
  1029.         test_datetime_to_time_kind("hour")
    
  1030.         test_datetime_to_time_kind("minute")
    
  1031.         test_datetime_to_time_kind("second")
    
  1032. 
    
  1033.         qs = DTModel.objects.filter(
    
  1034.             start_datetime__date=Trunc(
    
  1035.                 "start_datetime", "day", output_field=DateField()
    
  1036.             )
    
  1037.         )
    
  1038.         self.assertEqual(qs.count(), 2)
    
  1039. 
    
  1040.     def _test_trunc_week(self, start_datetime, end_datetime):
    
  1041.         if settings.USE_TZ:
    
  1042.             start_datetime = timezone.make_aware(start_datetime)
    
  1043.             end_datetime = timezone.make_aware(end_datetime)
    
  1044.         self.create_model(start_datetime, end_datetime)
    
  1045.         self.create_model(end_datetime, start_datetime)
    
  1046. 
    
  1047.         self.assertQuerysetEqual(
    
  1048.             DTModel.objects.annotate(
    
  1049.                 truncated=Trunc("start_datetime", "week", output_field=DateTimeField())
    
  1050.             ).order_by("start_datetime"),
    
  1051.             [
    
  1052.                 (start_datetime, truncate_to(start_datetime, "week")),
    
  1053.                 (end_datetime, truncate_to(end_datetime, "week")),
    
  1054.             ],
    
  1055.             lambda m: (m.start_datetime, m.truncated),
    
  1056.         )
    
  1057.         self.assertQuerysetEqual(
    
  1058.             DTModel.objects.annotate(
    
  1059.                 truncated=Trunc("start_date", "week", output_field=DateField())
    
  1060.             ).order_by("start_datetime"),
    
  1061.             [
    
  1062.                 (start_datetime, truncate_to(start_datetime.date(), "week")),
    
  1063.                 (end_datetime, truncate_to(end_datetime.date(), "week")),
    
  1064.             ],
    
  1065.             lambda m: (m.start_datetime, m.truncated),
    
  1066.         )
    
  1067. 
    
  1068.     def test_trunc_week(self):
    
  1069.         self._test_trunc_week(
    
  1070.             start_datetime=datetime(2015, 6, 15, 14, 30, 50, 321),
    
  1071.             end_datetime=datetime(2016, 6, 15, 14, 10, 50, 123),
    
  1072.         )
    
  1073. 
    
  1074.     def test_trunc_week_before_1000(self):
    
  1075.         self._test_trunc_week(
    
  1076.             start_datetime=datetime(999, 6, 15, 14, 30, 50, 321),
    
  1077.             end_datetime=datetime(2016, 6, 15, 14, 10, 50, 123),
    
  1078.         )
    
  1079. 
    
  1080.     def test_trunc_invalid_arguments(self):
    
  1081.         msg = "output_field must be either DateField, TimeField, or DateTimeField"
    
  1082.         with self.assertRaisesMessage(ValueError, msg):
    
  1083.             list(
    
  1084.                 DTModel.objects.annotate(
    
  1085.                     truncated=Trunc(
    
  1086.                         "start_datetime", "year", output_field=IntegerField()
    
  1087.                     ),
    
  1088.                 )
    
  1089.             )
    
  1090.         msg = "'name' isn't a DateField, TimeField, or DateTimeField."
    
  1091.         with self.assertRaisesMessage(TypeError, msg):
    
  1092.             list(
    
  1093.                 DTModel.objects.annotate(
    
  1094.                     truncated=Trunc("name", "year", output_field=DateTimeField()),
    
  1095.                 )
    
  1096.             )
    
  1097.         msg = "Cannot truncate DateField 'start_date' to DateTimeField"
    
  1098.         with self.assertRaisesMessage(ValueError, msg):
    
  1099.             list(DTModel.objects.annotate(truncated=Trunc("start_date", "second")))
    
  1100.         with self.assertRaisesMessage(ValueError, msg):
    
  1101.             list(
    
  1102.                 DTModel.objects.annotate(
    
  1103.                     truncated=Trunc(
    
  1104.                         "start_date", "month", output_field=DateTimeField()
    
  1105.                     ),
    
  1106.                 )
    
  1107.             )
    
  1108.         msg = "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1109.         with self.assertRaisesMessage(ValueError, msg):
    
  1110.             list(DTModel.objects.annotate(truncated=Trunc("start_time", "month")))
    
  1111.         with self.assertRaisesMessage(ValueError, msg):
    
  1112.             list(
    
  1113.                 DTModel.objects.annotate(
    
  1114.                     truncated=Trunc(
    
  1115.                         "start_time", "second", output_field=DateTimeField()
    
  1116.                     ),
    
  1117.                 )
    
  1118.             )
    
  1119. 
    
  1120.     def test_trunc_none(self):
    
  1121.         self.create_model(None, None)
    
  1122.         for t in (
    
  1123.             Trunc("start_datetime", "year"),
    
  1124.             Trunc("start_date", "year"),
    
  1125.             Trunc("start_time", "hour"),
    
  1126.         ):
    
  1127.             with self.subTest(t):
    
  1128.                 self.assertIsNone(
    
  1129.                     DTModel.objects.annotate(truncated=t).first().truncated
    
  1130.                 )
    
  1131. 
    
  1132.     def test_trunc_year_func(self):
    
  1133.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1134.         end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "year")
    
  1135.         if settings.USE_TZ:
    
  1136.             start_datetime = timezone.make_aware(start_datetime)
    
  1137.             end_datetime = timezone.make_aware(end_datetime)
    
  1138.         self.create_model(start_datetime, end_datetime)
    
  1139.         self.create_model(end_datetime, start_datetime)
    
  1140.         self.assertQuerysetEqual(
    
  1141.             DTModel.objects.annotate(extracted=TruncYear("start_datetime")).order_by(
    
  1142.                 "start_datetime"
    
  1143.             ),
    
  1144.             [
    
  1145.                 (start_datetime, truncate_to(start_datetime, "year")),
    
  1146.                 (end_datetime, truncate_to(end_datetime, "year")),
    
  1147.             ],
    
  1148.             lambda m: (m.start_datetime, m.extracted),
    
  1149.         )
    
  1150.         self.assertQuerysetEqual(
    
  1151.             DTModel.objects.annotate(extracted=TruncYear("start_date")).order_by(
    
  1152.                 "start_datetime"
    
  1153.             ),
    
  1154.             [
    
  1155.                 (start_datetime, truncate_to(start_datetime.date(), "year")),
    
  1156.                 (end_datetime, truncate_to(end_datetime.date(), "year")),
    
  1157.             ],
    
  1158.             lambda m: (m.start_datetime, m.extracted),
    
  1159.         )
    
  1160.         self.assertEqual(
    
  1161.             DTModel.objects.filter(start_datetime=TruncYear("start_datetime")).count(),
    
  1162.             1,
    
  1163.         )
    
  1164. 
    
  1165.         with self.assertRaisesMessage(
    
  1166.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1167.         ):
    
  1168.             list(DTModel.objects.annotate(truncated=TruncYear("start_time")))
    
  1169. 
    
  1170.         with self.assertRaisesMessage(
    
  1171.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1172.         ):
    
  1173.             list(
    
  1174.                 DTModel.objects.annotate(
    
  1175.                     truncated=TruncYear("start_time", output_field=TimeField())
    
  1176.                 )
    
  1177.             )
    
  1178. 
    
  1179.     def test_trunc_quarter_func(self):
    
  1180.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1181.         end_datetime = truncate_to(datetime(2016, 10, 15, 14, 10, 50, 123), "quarter")
    
  1182.         last_quarter_2015 = truncate_to(
    
  1183.             datetime(2015, 12, 31, 14, 10, 50, 123), "quarter"
    
  1184.         )
    
  1185.         first_quarter_2016 = truncate_to(
    
  1186.             datetime(2016, 1, 1, 14, 10, 50, 123), "quarter"
    
  1187.         )
    
  1188.         if settings.USE_TZ:
    
  1189.             start_datetime = timezone.make_aware(start_datetime)
    
  1190.             end_datetime = timezone.make_aware(end_datetime)
    
  1191.             last_quarter_2015 = timezone.make_aware(last_quarter_2015)
    
  1192.             first_quarter_2016 = timezone.make_aware(first_quarter_2016)
    
  1193.         self.create_model(start_datetime=start_datetime, end_datetime=end_datetime)
    
  1194.         self.create_model(start_datetime=end_datetime, end_datetime=start_datetime)
    
  1195.         self.create_model(start_datetime=last_quarter_2015, end_datetime=end_datetime)
    
  1196.         self.create_model(start_datetime=first_quarter_2016, end_datetime=end_datetime)
    
  1197.         self.assertQuerysetEqual(
    
  1198.             DTModel.objects.annotate(extracted=TruncQuarter("start_date")).order_by(
    
  1199.                 "start_datetime"
    
  1200.             ),
    
  1201.             [
    
  1202.                 (start_datetime, truncate_to(start_datetime.date(), "quarter")),
    
  1203.                 (last_quarter_2015, truncate_to(last_quarter_2015.date(), "quarter")),
    
  1204.                 (first_quarter_2016, truncate_to(first_quarter_2016.date(), "quarter")),
    
  1205.                 (end_datetime, truncate_to(end_datetime.date(), "quarter")),
    
  1206.             ],
    
  1207.             lambda m: (m.start_datetime, m.extracted),
    
  1208.         )
    
  1209.         self.assertQuerysetEqual(
    
  1210.             DTModel.objects.annotate(extracted=TruncQuarter("start_datetime")).order_by(
    
  1211.                 "start_datetime"
    
  1212.             ),
    
  1213.             [
    
  1214.                 (start_datetime, truncate_to(start_datetime, "quarter")),
    
  1215.                 (last_quarter_2015, truncate_to(last_quarter_2015, "quarter")),
    
  1216.                 (first_quarter_2016, truncate_to(first_quarter_2016, "quarter")),
    
  1217.                 (end_datetime, truncate_to(end_datetime, "quarter")),
    
  1218.             ],
    
  1219.             lambda m: (m.start_datetime, m.extracted),
    
  1220.         )
    
  1221. 
    
  1222.         with self.assertRaisesMessage(
    
  1223.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1224.         ):
    
  1225.             list(DTModel.objects.annotate(truncated=TruncQuarter("start_time")))
    
  1226. 
    
  1227.         with self.assertRaisesMessage(
    
  1228.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1229.         ):
    
  1230.             list(
    
  1231.                 DTModel.objects.annotate(
    
  1232.                     truncated=TruncQuarter("start_time", output_field=TimeField())
    
  1233.                 )
    
  1234.             )
    
  1235. 
    
  1236.     def test_trunc_month_func(self):
    
  1237.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1238.         end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "month")
    
  1239.         if settings.USE_TZ:
    
  1240.             start_datetime = timezone.make_aware(start_datetime)
    
  1241.             end_datetime = timezone.make_aware(end_datetime)
    
  1242.         self.create_model(start_datetime, end_datetime)
    
  1243.         self.create_model(end_datetime, start_datetime)
    
  1244.         self.assertQuerysetEqual(
    
  1245.             DTModel.objects.annotate(extracted=TruncMonth("start_datetime")).order_by(
    
  1246.                 "start_datetime"
    
  1247.             ),
    
  1248.             [
    
  1249.                 (start_datetime, truncate_to(start_datetime, "month")),
    
  1250.                 (end_datetime, truncate_to(end_datetime, "month")),
    
  1251.             ],
    
  1252.             lambda m: (m.start_datetime, m.extracted),
    
  1253.         )
    
  1254.         self.assertQuerysetEqual(
    
  1255.             DTModel.objects.annotate(extracted=TruncMonth("start_date")).order_by(
    
  1256.                 "start_datetime"
    
  1257.             ),
    
  1258.             [
    
  1259.                 (start_datetime, truncate_to(start_datetime.date(), "month")),
    
  1260.                 (end_datetime, truncate_to(end_datetime.date(), "month")),
    
  1261.             ],
    
  1262.             lambda m: (m.start_datetime, m.extracted),
    
  1263.         )
    
  1264.         self.assertEqual(
    
  1265.             DTModel.objects.filter(start_datetime=TruncMonth("start_datetime")).count(),
    
  1266.             1,
    
  1267.         )
    
  1268. 
    
  1269.         with self.assertRaisesMessage(
    
  1270.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1271.         ):
    
  1272.             list(DTModel.objects.annotate(truncated=TruncMonth("start_time")))
    
  1273. 
    
  1274.         with self.assertRaisesMessage(
    
  1275.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1276.         ):
    
  1277.             list(
    
  1278.                 DTModel.objects.annotate(
    
  1279.                     truncated=TruncMonth("start_time", output_field=TimeField())
    
  1280.                 )
    
  1281.             )
    
  1282. 
    
  1283.     def test_trunc_week_func(self):
    
  1284.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1285.         end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "week")
    
  1286.         if settings.USE_TZ:
    
  1287.             start_datetime = timezone.make_aware(start_datetime)
    
  1288.             end_datetime = timezone.make_aware(end_datetime)
    
  1289.         self.create_model(start_datetime, end_datetime)
    
  1290.         self.create_model(end_datetime, start_datetime)
    
  1291.         self.assertQuerysetEqual(
    
  1292.             DTModel.objects.annotate(extracted=TruncWeek("start_datetime")).order_by(
    
  1293.                 "start_datetime"
    
  1294.             ),
    
  1295.             [
    
  1296.                 (start_datetime, truncate_to(start_datetime, "week")),
    
  1297.                 (end_datetime, truncate_to(end_datetime, "week")),
    
  1298.             ],
    
  1299.             lambda m: (m.start_datetime, m.extracted),
    
  1300.         )
    
  1301.         self.assertEqual(
    
  1302.             DTModel.objects.filter(start_datetime=TruncWeek("start_datetime")).count(),
    
  1303.             1,
    
  1304.         )
    
  1305. 
    
  1306.         with self.assertRaisesMessage(
    
  1307.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1308.         ):
    
  1309.             list(DTModel.objects.annotate(truncated=TruncWeek("start_time")))
    
  1310. 
    
  1311.         with self.assertRaisesMessage(
    
  1312.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1313.         ):
    
  1314.             list(
    
  1315.                 DTModel.objects.annotate(
    
  1316.                     truncated=TruncWeek("start_time", output_field=TimeField())
    
  1317.                 )
    
  1318.             )
    
  1319. 
    
  1320.     def test_trunc_date_func(self):
    
  1321.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1322.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  1323.         if settings.USE_TZ:
    
  1324.             start_datetime = timezone.make_aware(start_datetime)
    
  1325.             end_datetime = timezone.make_aware(end_datetime)
    
  1326.         self.create_model(start_datetime, end_datetime)
    
  1327.         self.create_model(end_datetime, start_datetime)
    
  1328.         self.assertQuerysetEqual(
    
  1329.             DTModel.objects.annotate(extracted=TruncDate("start_datetime")).order_by(
    
  1330.                 "start_datetime"
    
  1331.             ),
    
  1332.             [
    
  1333.                 (start_datetime, start_datetime.date()),
    
  1334.                 (end_datetime, end_datetime.date()),
    
  1335.             ],
    
  1336.             lambda m: (m.start_datetime, m.extracted),
    
  1337.         )
    
  1338.         self.assertEqual(
    
  1339.             DTModel.objects.filter(
    
  1340.                 start_datetime__date=TruncDate("start_datetime")
    
  1341.             ).count(),
    
  1342.             2,
    
  1343.         )
    
  1344. 
    
  1345.         with self.assertRaisesMessage(
    
  1346.             ValueError, "Cannot truncate TimeField 'start_time' to DateField"
    
  1347.         ):
    
  1348.             list(DTModel.objects.annotate(truncated=TruncDate("start_time")))
    
  1349. 
    
  1350.         with self.assertRaisesMessage(
    
  1351.             ValueError, "Cannot truncate TimeField 'start_time' to DateField"
    
  1352.         ):
    
  1353.             list(
    
  1354.                 DTModel.objects.annotate(
    
  1355.                     truncated=TruncDate("start_time", output_field=TimeField())
    
  1356.                 )
    
  1357.             )
    
  1358. 
    
  1359.     def test_trunc_date_none(self):
    
  1360.         self.create_model(None, None)
    
  1361.         self.assertIsNone(
    
  1362.             DTModel.objects.annotate(truncated=TruncDate("start_datetime"))
    
  1363.             .first()
    
  1364.             .truncated
    
  1365.         )
    
  1366. 
    
  1367.     def test_trunc_time_func(self):
    
  1368.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1369.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  1370.         if settings.USE_TZ:
    
  1371.             start_datetime = timezone.make_aware(start_datetime)
    
  1372.             end_datetime = timezone.make_aware(end_datetime)
    
  1373.         self.create_model(start_datetime, end_datetime)
    
  1374.         self.create_model(end_datetime, start_datetime)
    
  1375.         self.assertQuerysetEqual(
    
  1376.             DTModel.objects.annotate(extracted=TruncTime("start_datetime")).order_by(
    
  1377.                 "start_datetime"
    
  1378.             ),
    
  1379.             [
    
  1380.                 (start_datetime, start_datetime.time()),
    
  1381.                 (end_datetime, end_datetime.time()),
    
  1382.             ],
    
  1383.             lambda m: (m.start_datetime, m.extracted),
    
  1384.         )
    
  1385.         self.assertEqual(
    
  1386.             DTModel.objects.filter(
    
  1387.                 start_datetime__time=TruncTime("start_datetime")
    
  1388.             ).count(),
    
  1389.             2,
    
  1390.         )
    
  1391. 
    
  1392.         with self.assertRaisesMessage(
    
  1393.             ValueError, "Cannot truncate DateField 'start_date' to TimeField"
    
  1394.         ):
    
  1395.             list(DTModel.objects.annotate(truncated=TruncTime("start_date")))
    
  1396. 
    
  1397.         with self.assertRaisesMessage(
    
  1398.             ValueError, "Cannot truncate DateField 'start_date' to TimeField"
    
  1399.         ):
    
  1400.             list(
    
  1401.                 DTModel.objects.annotate(
    
  1402.                     truncated=TruncTime("start_date", output_field=DateField())
    
  1403.                 )
    
  1404.             )
    
  1405. 
    
  1406.     def test_trunc_time_none(self):
    
  1407.         self.create_model(None, None)
    
  1408.         self.assertIsNone(
    
  1409.             DTModel.objects.annotate(truncated=TruncTime("start_datetime"))
    
  1410.             .first()
    
  1411.             .truncated
    
  1412.         )
    
  1413. 
    
  1414.     def test_trunc_time_comparison(self):
    
  1415.         start_datetime = datetime(2015, 6, 15, 14, 30, 26)  # 0 microseconds.
    
  1416.         end_datetime = datetime(2015, 6, 15, 14, 30, 26, 321)
    
  1417.         if settings.USE_TZ:
    
  1418.             start_datetime = timezone.make_aware(start_datetime)
    
  1419.             end_datetime = timezone.make_aware(end_datetime)
    
  1420.         self.create_model(start_datetime, end_datetime)
    
  1421.         self.assertIs(
    
  1422.             DTModel.objects.filter(
    
  1423.                 start_datetime__time=start_datetime.time(),
    
  1424.                 end_datetime__time=end_datetime.time(),
    
  1425.             ).exists(),
    
  1426.             True,
    
  1427.         )
    
  1428.         self.assertIs(
    
  1429.             DTModel.objects.annotate(
    
  1430.                 extracted_start=TruncTime("start_datetime"),
    
  1431.                 extracted_end=TruncTime("end_datetime"),
    
  1432.             )
    
  1433.             .filter(
    
  1434.                 extracted_start=start_datetime.time(),
    
  1435.                 extracted_end=end_datetime.time(),
    
  1436.             )
    
  1437.             .exists(),
    
  1438.             True,
    
  1439.         )
    
  1440. 
    
  1441.     def test_trunc_day_func(self):
    
  1442.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1443.         end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "day")
    
  1444.         if settings.USE_TZ:
    
  1445.             start_datetime = timezone.make_aware(start_datetime)
    
  1446.             end_datetime = timezone.make_aware(end_datetime)
    
  1447.         self.create_model(start_datetime, end_datetime)
    
  1448.         self.create_model(end_datetime, start_datetime)
    
  1449.         self.assertQuerysetEqual(
    
  1450.             DTModel.objects.annotate(extracted=TruncDay("start_datetime")).order_by(
    
  1451.                 "start_datetime"
    
  1452.             ),
    
  1453.             [
    
  1454.                 (start_datetime, truncate_to(start_datetime, "day")),
    
  1455.                 (end_datetime, truncate_to(end_datetime, "day")),
    
  1456.             ],
    
  1457.             lambda m: (m.start_datetime, m.extracted),
    
  1458.         )
    
  1459.         self.assertEqual(
    
  1460.             DTModel.objects.filter(start_datetime=TruncDay("start_datetime")).count(), 1
    
  1461.         )
    
  1462. 
    
  1463.         with self.assertRaisesMessage(
    
  1464.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1465.         ):
    
  1466.             list(DTModel.objects.annotate(truncated=TruncDay("start_time")))
    
  1467. 
    
  1468.         with self.assertRaisesMessage(
    
  1469.             ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
    
  1470.         ):
    
  1471.             list(
    
  1472.                 DTModel.objects.annotate(
    
  1473.                     truncated=TruncDay("start_time", output_field=TimeField())
    
  1474.                 )
    
  1475.             )
    
  1476. 
    
  1477.     def test_trunc_hour_func(self):
    
  1478.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1479.         end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "hour")
    
  1480.         if settings.USE_TZ:
    
  1481.             start_datetime = timezone.make_aware(start_datetime)
    
  1482.             end_datetime = timezone.make_aware(end_datetime)
    
  1483.         self.create_model(start_datetime, end_datetime)
    
  1484.         self.create_model(end_datetime, start_datetime)
    
  1485.         self.assertQuerysetEqual(
    
  1486.             DTModel.objects.annotate(extracted=TruncHour("start_datetime")).order_by(
    
  1487.                 "start_datetime"
    
  1488.             ),
    
  1489.             [
    
  1490.                 (start_datetime, truncate_to(start_datetime, "hour")),
    
  1491.                 (end_datetime, truncate_to(end_datetime, "hour")),
    
  1492.             ],
    
  1493.             lambda m: (m.start_datetime, m.extracted),
    
  1494.         )
    
  1495.         self.assertQuerysetEqual(
    
  1496.             DTModel.objects.annotate(extracted=TruncHour("start_time")).order_by(
    
  1497.                 "start_datetime"
    
  1498.             ),
    
  1499.             [
    
  1500.                 (start_datetime, truncate_to(start_datetime.time(), "hour")),
    
  1501.                 (end_datetime, truncate_to(end_datetime.time(), "hour")),
    
  1502.             ],
    
  1503.             lambda m: (m.start_datetime, m.extracted),
    
  1504.         )
    
  1505.         self.assertEqual(
    
  1506.             DTModel.objects.filter(start_datetime=TruncHour("start_datetime")).count(),
    
  1507.             1,
    
  1508.         )
    
  1509. 
    
  1510.         with self.assertRaisesMessage(
    
  1511.             ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
    
  1512.         ):
    
  1513.             list(DTModel.objects.annotate(truncated=TruncHour("start_date")))
    
  1514. 
    
  1515.         with self.assertRaisesMessage(
    
  1516.             ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
    
  1517.         ):
    
  1518.             list(
    
  1519.                 DTModel.objects.annotate(
    
  1520.                     truncated=TruncHour("start_date", output_field=DateField())
    
  1521.                 )
    
  1522.             )
    
  1523. 
    
  1524.     def test_trunc_minute_func(self):
    
  1525.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1526.         end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "minute")
    
  1527.         if settings.USE_TZ:
    
  1528.             start_datetime = timezone.make_aware(start_datetime)
    
  1529.             end_datetime = timezone.make_aware(end_datetime)
    
  1530.         self.create_model(start_datetime, end_datetime)
    
  1531.         self.create_model(end_datetime, start_datetime)
    
  1532.         self.assertQuerysetEqual(
    
  1533.             DTModel.objects.annotate(extracted=TruncMinute("start_datetime")).order_by(
    
  1534.                 "start_datetime"
    
  1535.             ),
    
  1536.             [
    
  1537.                 (start_datetime, truncate_to(start_datetime, "minute")),
    
  1538.                 (end_datetime, truncate_to(end_datetime, "minute")),
    
  1539.             ],
    
  1540.             lambda m: (m.start_datetime, m.extracted),
    
  1541.         )
    
  1542.         self.assertQuerysetEqual(
    
  1543.             DTModel.objects.annotate(extracted=TruncMinute("start_time")).order_by(
    
  1544.                 "start_datetime"
    
  1545.             ),
    
  1546.             [
    
  1547.                 (start_datetime, truncate_to(start_datetime.time(), "minute")),
    
  1548.                 (end_datetime, truncate_to(end_datetime.time(), "minute")),
    
  1549.             ],
    
  1550.             lambda m: (m.start_datetime, m.extracted),
    
  1551.         )
    
  1552.         self.assertEqual(
    
  1553.             DTModel.objects.filter(
    
  1554.                 start_datetime=TruncMinute("start_datetime")
    
  1555.             ).count(),
    
  1556.             1,
    
  1557.         )
    
  1558. 
    
  1559.         with self.assertRaisesMessage(
    
  1560.             ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
    
  1561.         ):
    
  1562.             list(DTModel.objects.annotate(truncated=TruncMinute("start_date")))
    
  1563. 
    
  1564.         with self.assertRaisesMessage(
    
  1565.             ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
    
  1566.         ):
    
  1567.             list(
    
  1568.                 DTModel.objects.annotate(
    
  1569.                     truncated=TruncMinute("start_date", output_field=DateField())
    
  1570.                 )
    
  1571.             )
    
  1572. 
    
  1573.     def test_trunc_second_func(self):
    
  1574.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1575.         end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "second")
    
  1576.         if settings.USE_TZ:
    
  1577.             start_datetime = timezone.make_aware(start_datetime)
    
  1578.             end_datetime = timezone.make_aware(end_datetime)
    
  1579.         self.create_model(start_datetime, end_datetime)
    
  1580.         self.create_model(end_datetime, start_datetime)
    
  1581.         self.assertQuerysetEqual(
    
  1582.             DTModel.objects.annotate(extracted=TruncSecond("start_datetime")).order_by(
    
  1583.                 "start_datetime"
    
  1584.             ),
    
  1585.             [
    
  1586.                 (start_datetime, truncate_to(start_datetime, "second")),
    
  1587.                 (end_datetime, truncate_to(end_datetime, "second")),
    
  1588.             ],
    
  1589.             lambda m: (m.start_datetime, m.extracted),
    
  1590.         )
    
  1591.         self.assertQuerysetEqual(
    
  1592.             DTModel.objects.annotate(extracted=TruncSecond("start_time")).order_by(
    
  1593.                 "start_datetime"
    
  1594.             ),
    
  1595.             [
    
  1596.                 (start_datetime, truncate_to(start_datetime.time(), "second")),
    
  1597.                 (end_datetime, truncate_to(end_datetime.time(), "second")),
    
  1598.             ],
    
  1599.             lambda m: (m.start_datetime, m.extracted),
    
  1600.         )
    
  1601.         self.assertEqual(
    
  1602.             DTModel.objects.filter(
    
  1603.                 start_datetime=TruncSecond("start_datetime")
    
  1604.             ).count(),
    
  1605.             1,
    
  1606.         )
    
  1607. 
    
  1608.         with self.assertRaisesMessage(
    
  1609.             ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
    
  1610.         ):
    
  1611.             list(DTModel.objects.annotate(truncated=TruncSecond("start_date")))
    
  1612. 
    
  1613.         with self.assertRaisesMessage(
    
  1614.             ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
    
  1615.         ):
    
  1616.             list(
    
  1617.                 DTModel.objects.annotate(
    
  1618.                     truncated=TruncSecond("start_date", output_field=DateField())
    
  1619.                 )
    
  1620.             )
    
  1621. 
    
  1622.     def test_trunc_subquery_with_parameters(self):
    
  1623.         author_1 = Author.objects.create(name="J. R. R. Tolkien")
    
  1624.         author_2 = Author.objects.create(name="G. R. R. Martin")
    
  1625.         fan_since_1 = datetime(2016, 2, 3, 15, 0, 0)
    
  1626.         fan_since_2 = datetime(2015, 2, 3, 15, 0, 0)
    
  1627.         fan_since_3 = datetime(2017, 2, 3, 15, 0, 0)
    
  1628.         if settings.USE_TZ:
    
  1629.             fan_since_1 = timezone.make_aware(fan_since_1)
    
  1630.             fan_since_2 = timezone.make_aware(fan_since_2)
    
  1631.             fan_since_3 = timezone.make_aware(fan_since_3)
    
  1632.         Fan.objects.create(author=author_1, name="Tom", fan_since=fan_since_1)
    
  1633.         Fan.objects.create(author=author_1, name="Emma", fan_since=fan_since_2)
    
  1634.         Fan.objects.create(author=author_2, name="Isabella", fan_since=fan_since_3)
    
  1635. 
    
  1636.         inner = (
    
  1637.             Fan.objects.filter(
    
  1638.                 author=OuterRef("pk"), name__in=("Emma", "Isabella", "Tom")
    
  1639.             )
    
  1640.             .values("author")
    
  1641.             .annotate(newest_fan=Max("fan_since"))
    
  1642.             .values("newest_fan")
    
  1643.         )
    
  1644.         outer = Author.objects.annotate(
    
  1645.             newest_fan_year=TruncYear(Subquery(inner, output_field=DateTimeField()))
    
  1646.         )
    
  1647.         tz = datetime_timezone.utc if settings.USE_TZ else None
    
  1648.         self.assertSequenceEqual(
    
  1649.             outer.order_by("name").values("name", "newest_fan_year"),
    
  1650.             [
    
  1651.                 {
    
  1652.                     "name": "G. R. R. Martin",
    
  1653.                     "newest_fan_year": datetime(2017, 1, 1, 0, 0, tzinfo=tz),
    
  1654.                 },
    
  1655.                 {
    
  1656.                     "name": "J. R. R. Tolkien",
    
  1657.                     "newest_fan_year": datetime(2016, 1, 1, 0, 0, tzinfo=tz),
    
  1658.                 },
    
  1659.             ],
    
  1660.         )
    
  1661. 
    
  1662.     def test_extract_outerref(self):
    
  1663.         datetime_1 = datetime(2000, 1, 1)
    
  1664.         datetime_2 = datetime(2001, 3, 5)
    
  1665.         datetime_3 = datetime(2002, 1, 3)
    
  1666.         if settings.USE_TZ:
    
  1667.             datetime_1 = timezone.make_aware(datetime_1)
    
  1668.             datetime_2 = timezone.make_aware(datetime_2)
    
  1669.             datetime_3 = timezone.make_aware(datetime_3)
    
  1670.         obj_1 = self.create_model(datetime_1, datetime_3)
    
  1671.         obj_2 = self.create_model(datetime_2, datetime_1)
    
  1672.         obj_3 = self.create_model(datetime_3, datetime_2)
    
  1673. 
    
  1674.         inner_qs = DTModel.objects.filter(
    
  1675.             start_datetime__year=2000,
    
  1676.             start_datetime__month=ExtractMonth(OuterRef("end_datetime")),
    
  1677.         )
    
  1678.         qs = DTModel.objects.annotate(
    
  1679.             related_pk=Subquery(inner_qs.values("pk")[:1]),
    
  1680.         )
    
  1681.         self.assertSequenceEqual(
    
  1682.             qs.order_by("name").values("pk", "related_pk"),
    
  1683.             [
    
  1684.                 {"pk": obj_1.pk, "related_pk": obj_1.pk},
    
  1685.                 {"pk": obj_2.pk, "related_pk": obj_1.pk},
    
  1686.                 {"pk": obj_3.pk, "related_pk": None},
    
  1687.             ],
    
  1688.         )
    
  1689. 
    
  1690. 
    
  1691. @override_settings(USE_TZ=True, TIME_ZONE="UTC")
    
  1692. class DateFunctionWithTimeZoneTests(DateFunctionTests):
    
  1693.     def get_timezones(self, key):
    
  1694.         for constructor in ZONE_CONSTRUCTORS:
    
  1695.             yield constructor(key)
    
  1696. 
    
  1697.     def test_extract_func_with_timezone(self):
    
  1698.         start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
    
  1699.         end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
    
  1700.         start_datetime = timezone.make_aware(start_datetime)
    
  1701.         end_datetime = timezone.make_aware(end_datetime)
    
  1702.         self.create_model(start_datetime, end_datetime)
    
  1703.         delta_tzinfo_pos = datetime_timezone(timedelta(hours=5))
    
  1704.         delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17))
    
  1705. 
    
  1706.         for melb in self.get_timezones("Australia/Melbourne"):
    
  1707.             with self.subTest(repr(melb)):
    
  1708.                 qs = DTModel.objects.annotate(
    
  1709.                     day=Extract("start_datetime", "day"),
    
  1710.                     day_melb=Extract("start_datetime", "day", tzinfo=melb),
    
  1711.                     week=Extract("start_datetime", "week", tzinfo=melb),
    
  1712.                     isoyear=ExtractIsoYear("start_datetime", tzinfo=melb),
    
  1713.                     weekday=ExtractWeekDay("start_datetime"),
    
  1714.                     weekday_melb=ExtractWeekDay("start_datetime", tzinfo=melb),
    
  1715.                     isoweekday=ExtractIsoWeekDay("start_datetime"),
    
  1716.                     isoweekday_melb=ExtractIsoWeekDay("start_datetime", tzinfo=melb),
    
  1717.                     quarter=ExtractQuarter("start_datetime", tzinfo=melb),
    
  1718.                     hour=ExtractHour("start_datetime"),
    
  1719.                     hour_melb=ExtractHour("start_datetime", tzinfo=melb),
    
  1720.                     hour_with_delta_pos=ExtractHour(
    
  1721.                         "start_datetime", tzinfo=delta_tzinfo_pos
    
  1722.                     ),
    
  1723.                     hour_with_delta_neg=ExtractHour(
    
  1724.                         "start_datetime", tzinfo=delta_tzinfo_neg
    
  1725.                     ),
    
  1726.                     minute_with_delta_neg=ExtractMinute(
    
  1727.                         "start_datetime", tzinfo=delta_tzinfo_neg
    
  1728.                     ),
    
  1729.                 ).order_by("start_datetime")
    
  1730. 
    
  1731.                 utc_model = qs.get()
    
  1732.                 self.assertEqual(utc_model.day, 15)
    
  1733.                 self.assertEqual(utc_model.day_melb, 16)
    
  1734.                 self.assertEqual(utc_model.week, 25)
    
  1735.                 self.assertEqual(utc_model.isoyear, 2015)
    
  1736.                 self.assertEqual(utc_model.weekday, 2)
    
  1737.                 self.assertEqual(utc_model.weekday_melb, 3)
    
  1738.                 self.assertEqual(utc_model.isoweekday, 1)
    
  1739.                 self.assertEqual(utc_model.isoweekday_melb, 2)
    
  1740.                 self.assertEqual(utc_model.quarter, 2)
    
  1741.                 self.assertEqual(utc_model.hour, 23)
    
  1742.                 self.assertEqual(utc_model.hour_melb, 9)
    
  1743.                 self.assertEqual(utc_model.hour_with_delta_pos, 4)
    
  1744.                 self.assertEqual(utc_model.hour_with_delta_neg, 18)
    
  1745.                 self.assertEqual(utc_model.minute_with_delta_neg, 47)
    
  1746. 
    
  1747.                 with timezone.override(melb):
    
  1748.                     melb_model = qs.get()
    
  1749. 
    
  1750.                 self.assertEqual(melb_model.day, 16)
    
  1751.                 self.assertEqual(melb_model.day_melb, 16)
    
  1752.                 self.assertEqual(melb_model.week, 25)
    
  1753.                 self.assertEqual(melb_model.isoyear, 2015)
    
  1754.                 self.assertEqual(melb_model.weekday, 3)
    
  1755.                 self.assertEqual(melb_model.isoweekday, 2)
    
  1756.                 self.assertEqual(melb_model.quarter, 2)
    
  1757.                 self.assertEqual(melb_model.weekday_melb, 3)
    
  1758.                 self.assertEqual(melb_model.isoweekday_melb, 2)
    
  1759.                 self.assertEqual(melb_model.hour, 9)
    
  1760.                 self.assertEqual(melb_model.hour_melb, 9)
    
  1761. 
    
  1762.     def test_extract_func_with_timezone_minus_no_offset(self):
    
  1763.         start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
    
  1764.         end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
    
  1765.         start_datetime = timezone.make_aware(start_datetime)
    
  1766.         end_datetime = timezone.make_aware(end_datetime)
    
  1767.         self.create_model(start_datetime, end_datetime)
    
  1768.         for ust_nera in self.get_timezones("Asia/Ust-Nera"):
    
  1769.             with self.subTest(repr(ust_nera)):
    
  1770.                 qs = DTModel.objects.annotate(
    
  1771.                     hour=ExtractHour("start_datetime"),
    
  1772.                     hour_tz=ExtractHour("start_datetime", tzinfo=ust_nera),
    
  1773.                 ).order_by("start_datetime")
    
  1774. 
    
  1775.                 utc_model = qs.get()
    
  1776.                 self.assertEqual(utc_model.hour, 23)
    
  1777.                 self.assertEqual(utc_model.hour_tz, 9)
    
  1778. 
    
  1779.                 with timezone.override(ust_nera):
    
  1780.                     ust_nera_model = qs.get()
    
  1781. 
    
  1782.                 self.assertEqual(ust_nera_model.hour, 9)
    
  1783.                 self.assertEqual(ust_nera_model.hour_tz, 9)
    
  1784. 
    
  1785.     def test_extract_func_explicit_timezone_priority(self):
    
  1786.         start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
    
  1787.         end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
    
  1788.         start_datetime = timezone.make_aware(start_datetime)
    
  1789.         end_datetime = timezone.make_aware(end_datetime)
    
  1790.         self.create_model(start_datetime, end_datetime)
    
  1791. 
    
  1792.         for melb in self.get_timezones("Australia/Melbourne"):
    
  1793.             with self.subTest(repr(melb)):
    
  1794.                 with timezone.override(melb):
    
  1795.                     model = (
    
  1796.                         DTModel.objects.annotate(
    
  1797.                             day_melb=Extract("start_datetime", "day"),
    
  1798.                             day_utc=Extract(
    
  1799.                                 "start_datetime", "day", tzinfo=datetime_timezone.utc
    
  1800.                             ),
    
  1801.                         )
    
  1802.                         .order_by("start_datetime")
    
  1803.                         .get()
    
  1804.                     )
    
  1805.                     self.assertEqual(model.day_melb, 16)
    
  1806.                     self.assertEqual(model.day_utc, 15)
    
  1807. 
    
  1808.     def test_extract_invalid_field_with_timezone(self):
    
  1809.         for melb in self.get_timezones("Australia/Melbourne"):
    
  1810.             with self.subTest(repr(melb)):
    
  1811.                 msg = "tzinfo can only be used with DateTimeField."
    
  1812.                 with self.assertRaisesMessage(ValueError, msg):
    
  1813.                     DTModel.objects.annotate(
    
  1814.                         day_melb=Extract("start_date", "day", tzinfo=melb),
    
  1815.                     ).get()
    
  1816.                 with self.assertRaisesMessage(ValueError, msg):
    
  1817.                     DTModel.objects.annotate(
    
  1818.                         hour_melb=Extract("start_time", "hour", tzinfo=melb),
    
  1819.                     ).get()
    
  1820. 
    
  1821.     def test_trunc_timezone_applied_before_truncation(self):
    
  1822.         start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
    
  1823.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  1824.         start_datetime = timezone.make_aware(start_datetime)
    
  1825.         end_datetime = timezone.make_aware(end_datetime)
    
  1826.         self.create_model(start_datetime, end_datetime)
    
  1827. 
    
  1828.         for melb, pacific in zip(
    
  1829.             self.get_timezones("Australia/Melbourne"),
    
  1830.             self.get_timezones("America/Los_Angeles"),
    
  1831.         ):
    
  1832.             with self.subTest((repr(melb), repr(pacific))):
    
  1833.                 model = (
    
  1834.                     DTModel.objects.annotate(
    
  1835.                         melb_year=TruncYear("start_datetime", tzinfo=melb),
    
  1836.                         pacific_year=TruncYear("start_datetime", tzinfo=pacific),
    
  1837.                         melb_date=TruncDate("start_datetime", tzinfo=melb),
    
  1838.                         pacific_date=TruncDate("start_datetime", tzinfo=pacific),
    
  1839.                         melb_time=TruncTime("start_datetime", tzinfo=melb),
    
  1840.                         pacific_time=TruncTime("start_datetime", tzinfo=pacific),
    
  1841.                     )
    
  1842.                     .order_by("start_datetime")
    
  1843.                     .get()
    
  1844.                 )
    
  1845. 
    
  1846.                 melb_start_datetime = start_datetime.astimezone(melb)
    
  1847.                 pacific_start_datetime = start_datetime.astimezone(pacific)
    
  1848.                 self.assertEqual(model.start_datetime, start_datetime)
    
  1849.                 self.assertEqual(
    
  1850.                     model.melb_year, truncate_to(start_datetime, "year", melb)
    
  1851.                 )
    
  1852.                 self.assertEqual(
    
  1853.                     model.pacific_year, truncate_to(start_datetime, "year", pacific)
    
  1854.                 )
    
  1855.                 self.assertEqual(model.start_datetime.year, 2016)
    
  1856.                 self.assertEqual(model.melb_year.year, 2016)
    
  1857.                 self.assertEqual(model.pacific_year.year, 2015)
    
  1858.                 self.assertEqual(model.melb_date, melb_start_datetime.date())
    
  1859.                 self.assertEqual(model.pacific_date, pacific_start_datetime.date())
    
  1860.                 self.assertEqual(model.melb_time, melb_start_datetime.time())
    
  1861.                 self.assertEqual(model.pacific_time, pacific_start_datetime.time())
    
  1862. 
    
  1863.     @needs_pytz
    
  1864.     @ignore_warnings(category=RemovedInDjango50Warning)
    
  1865.     def test_trunc_ambiguous_and_invalid_times(self):
    
  1866.         sao = pytz.timezone("America/Sao_Paulo")
    
  1867.         start_datetime = datetime(2016, 10, 16, 13, tzinfo=datetime_timezone.utc)
    
  1868.         end_datetime = datetime(2016, 2, 21, 1, tzinfo=datetime_timezone.utc)
    
  1869.         self.create_model(start_datetime, end_datetime)
    
  1870.         with timezone.override(sao):
    
  1871.             with self.assertRaisesMessage(
    
  1872.                 pytz.NonExistentTimeError, "2016-10-16 00:00:00"
    
  1873.             ):
    
  1874.                 model = DTModel.objects.annotate(
    
  1875.                     truncated_start=TruncDay("start_datetime")
    
  1876.                 ).get()
    
  1877.             with self.assertRaisesMessage(
    
  1878.                 pytz.AmbiguousTimeError, "2016-02-20 23:00:00"
    
  1879.             ):
    
  1880.                 model = DTModel.objects.annotate(
    
  1881.                     truncated_end=TruncHour("end_datetime")
    
  1882.                 ).get()
    
  1883.             model = DTModel.objects.annotate(
    
  1884.                 truncated_start=TruncDay("start_datetime", is_dst=False),
    
  1885.                 truncated_end=TruncHour("end_datetime", is_dst=False),
    
  1886.             ).get()
    
  1887.             self.assertEqual(model.truncated_start.dst(), timedelta(0))
    
  1888.             self.assertEqual(model.truncated_end.dst(), timedelta(0))
    
  1889.             model = DTModel.objects.annotate(
    
  1890.                 truncated_start=TruncDay("start_datetime", is_dst=True),
    
  1891.                 truncated_end=TruncHour("end_datetime", is_dst=True),
    
  1892.             ).get()
    
  1893.             self.assertEqual(model.truncated_start.dst(), timedelta(0, 3600))
    
  1894.             self.assertEqual(model.truncated_end.dst(), timedelta(0, 3600))
    
  1895. 
    
  1896.     def test_trunc_func_with_timezone(self):
    
  1897.         """
    
  1898.         If the truncated datetime transitions to a different offset (daylight
    
  1899.         saving) then the returned value will have that new timezone/offset.
    
  1900.         """
    
  1901.         start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
    
  1902.         end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
    
  1903.         start_datetime = timezone.make_aware(start_datetime)
    
  1904.         end_datetime = timezone.make_aware(end_datetime)
    
  1905.         self.create_model(start_datetime, end_datetime)
    
  1906.         self.create_model(end_datetime, start_datetime)
    
  1907. 
    
  1908.         for melb in self.get_timezones("Australia/Melbourne"):
    
  1909.             with self.subTest(repr(melb)):
    
  1910. 
    
  1911.                 def test_datetime_kind(kind):
    
  1912.                     self.assertQuerysetEqual(
    
  1913.                         DTModel.objects.annotate(
    
  1914.                             truncated=Trunc(
    
  1915.                                 "start_datetime",
    
  1916.                                 kind,
    
  1917.                                 output_field=DateTimeField(),
    
  1918.                                 tzinfo=melb,
    
  1919.                             )
    
  1920.                         ).order_by("start_datetime"),
    
  1921.                         [
    
  1922.                             (
    
  1923.                                 start_datetime,
    
  1924.                                 truncate_to(
    
  1925.                                     start_datetime.astimezone(melb), kind, melb
    
  1926.                                 ),
    
  1927.                             ),
    
  1928.                             (
    
  1929.                                 end_datetime,
    
  1930.                                 truncate_to(end_datetime.astimezone(melb), kind, melb),
    
  1931.                             ),
    
  1932.                         ],
    
  1933.                         lambda m: (m.start_datetime, m.truncated),
    
  1934.                     )
    
  1935. 
    
  1936.                 def test_datetime_to_date_kind(kind):
    
  1937.                     self.assertQuerysetEqual(
    
  1938.                         DTModel.objects.annotate(
    
  1939.                             truncated=Trunc(
    
  1940.                                 "start_datetime",
    
  1941.                                 kind,
    
  1942.                                 output_field=DateField(),
    
  1943.                                 tzinfo=melb,
    
  1944.                             ),
    
  1945.                         ).order_by("start_datetime"),
    
  1946.                         [
    
  1947.                             (
    
  1948.                                 start_datetime,
    
  1949.                                 truncate_to(
    
  1950.                                     start_datetime.astimezone(melb).date(), kind
    
  1951.                                 ),
    
  1952.                             ),
    
  1953.                             (
    
  1954.                                 end_datetime,
    
  1955.                                 truncate_to(end_datetime.astimezone(melb).date(), kind),
    
  1956.                             ),
    
  1957.                         ],
    
  1958.                         lambda m: (m.start_datetime, m.truncated),
    
  1959.                     )
    
  1960. 
    
  1961.                 def test_datetime_to_time_kind(kind):
    
  1962.                     self.assertQuerysetEqual(
    
  1963.                         DTModel.objects.annotate(
    
  1964.                             truncated=Trunc(
    
  1965.                                 "start_datetime",
    
  1966.                                 kind,
    
  1967.                                 output_field=TimeField(),
    
  1968.                                 tzinfo=melb,
    
  1969.                             )
    
  1970.                         ).order_by("start_datetime"),
    
  1971.                         [
    
  1972.                             (
    
  1973.                                 start_datetime,
    
  1974.                                 truncate_to(
    
  1975.                                     start_datetime.astimezone(melb).time(), kind
    
  1976.                                 ),
    
  1977.                             ),
    
  1978.                             (
    
  1979.                                 end_datetime,
    
  1980.                                 truncate_to(end_datetime.astimezone(melb).time(), kind),
    
  1981.                             ),
    
  1982.                         ],
    
  1983.                         lambda m: (m.start_datetime, m.truncated),
    
  1984.                     )
    
  1985. 
    
  1986.                 test_datetime_to_date_kind("year")
    
  1987.                 test_datetime_to_date_kind("quarter")
    
  1988.                 test_datetime_to_date_kind("month")
    
  1989.                 test_datetime_to_date_kind("week")
    
  1990.                 test_datetime_to_date_kind("day")
    
  1991.                 test_datetime_to_time_kind("hour")
    
  1992.                 test_datetime_to_time_kind("minute")
    
  1993.                 test_datetime_to_time_kind("second")
    
  1994.                 test_datetime_kind("year")
    
  1995.                 test_datetime_kind("quarter")
    
  1996.                 test_datetime_kind("month")
    
  1997.                 test_datetime_kind("week")
    
  1998.                 test_datetime_kind("day")
    
  1999.                 test_datetime_kind("hour")
    
  2000.                 test_datetime_kind("minute")
    
  2001.                 test_datetime_kind("second")
    
  2002. 
    
  2003.                 qs = DTModel.objects.filter(
    
  2004.                     start_datetime__date=Trunc(
    
  2005.                         "start_datetime", "day", output_field=DateField()
    
  2006.                     )
    
  2007.                 )
    
  2008.                 self.assertEqual(qs.count(), 2)
    
  2009. 
    
  2010.     def test_trunc_invalid_field_with_timezone(self):
    
  2011.         for melb in self.get_timezones("Australia/Melbourne"):
    
  2012.             with self.subTest(repr(melb)):
    
  2013.                 msg = "tzinfo can only be used with DateTimeField."
    
  2014.                 with self.assertRaisesMessage(ValueError, msg):
    
  2015.                     DTModel.objects.annotate(
    
  2016.                         day_melb=Trunc("start_date", "day", tzinfo=melb),
    
  2017.                     ).get()
    
  2018.                 with self.assertRaisesMessage(ValueError, msg):
    
  2019.                     DTModel.objects.annotate(
    
  2020.                         hour_melb=Trunc("start_time", "hour", tzinfo=melb),
    
  2021.                     ).get()