summaryrefslogtreecommitdiff
path: root/tests/db_functions
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2023-01-09 09:03:38 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-01-17 11:49:15 +0100
commite6f82438d4e3750e8d299bfd79dac98eebe9f1e0 (patch)
tree4ee0cbf2c0be9822416aa3d65105f35a9784fd94 /tests/db_functions
parent8d98f99a4ab5de6f2c730399f53eba8bf6bea470 (diff)
downloaddjango-e6f82438d4e3750e8d299bfd79dac98eebe9f1e0.tar.gz
Refs #32365 -- Removed support for pytz timezones per deprecation timeline.
Diffstat (limited to 'tests/db_functions')
-rw-r--r--tests/db_functions/datetime/test_extract_trunc.py504
1 files changed, 212 insertions, 292 deletions
diff --git a/tests/db_functions/datetime/test_extract_trunc.py b/tests/db_functions/datetime/test_extract_trunc.py
index b2327931f0..80043fe3f4 100644
--- a/tests/db_functions/datetime/test_extract_trunc.py
+++ b/tests/db_functions/datetime/test_extract_trunc.py
@@ -1,4 +1,3 @@
-import unittest
from datetime import datetime, timedelta
from datetime import timezone as datetime_timezone
@@ -7,11 +6,6 @@ try:
except ImportError:
from backports import zoneinfo
-try:
- import pytz
-except ImportError:
- pytz = None
-
from django.conf import settings
from django.db import DataError, OperationalError
from django.db.models import (
@@ -51,29 +45,14 @@ from django.db.models.functions import (
)
from django.test import (
TestCase,
- ignore_warnings,
override_settings,
skipIfDBFeature,
skipUnlessDBFeature,
)
from django.utils import timezone
-from django.utils.deprecation import RemovedInDjango50Warning
from ..models import Author, DTModel, Fan
-HAS_PYTZ = pytz is not None
-if not HAS_PYTZ:
- needs_pytz = unittest.skip("Test requires pytz")
-else:
-
- def needs_pytz(f):
- return f
-
-
-ZONE_CONSTRUCTORS = (zoneinfo.ZoneInfo,)
-if HAS_PYTZ:
- ZONE_CONSTRUCTORS += (pytz.timezone,)
-
def truncate_to(value, kind, tzinfo=None):
# Convert to target timezone before truncation
@@ -1690,10 +1669,6 @@ class DateFunctionTests(TestCase):
@override_settings(USE_TZ=True, TIME_ZONE="UTC")
class DateFunctionWithTimeZoneTests(DateFunctionTests):
- def get_timezones(self, key):
- for constructor in ZONE_CONSTRUCTORS:
- yield constructor(key)
-
def test_extract_func_with_timezone(self):
start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
@@ -1702,62 +1677,57 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
self.create_model(start_datetime, end_datetime)
delta_tzinfo_pos = datetime_timezone(timedelta(hours=5))
delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17))
+ melb = zoneinfo.ZoneInfo("Australia/Melbourne")
- for melb in self.get_timezones("Australia/Melbourne"):
- with self.subTest(repr(melb)):
- qs = DTModel.objects.annotate(
- day=Extract("start_datetime", "day"),
- day_melb=Extract("start_datetime", "day", tzinfo=melb),
- week=Extract("start_datetime", "week", tzinfo=melb),
- isoyear=ExtractIsoYear("start_datetime", tzinfo=melb),
- weekday=ExtractWeekDay("start_datetime"),
- weekday_melb=ExtractWeekDay("start_datetime", tzinfo=melb),
- isoweekday=ExtractIsoWeekDay("start_datetime"),
- isoweekday_melb=ExtractIsoWeekDay("start_datetime", tzinfo=melb),
- quarter=ExtractQuarter("start_datetime", tzinfo=melb),
- hour=ExtractHour("start_datetime"),
- hour_melb=ExtractHour("start_datetime", tzinfo=melb),
- hour_with_delta_pos=ExtractHour(
- "start_datetime", tzinfo=delta_tzinfo_pos
- ),
- hour_with_delta_neg=ExtractHour(
- "start_datetime", tzinfo=delta_tzinfo_neg
- ),
- minute_with_delta_neg=ExtractMinute(
- "start_datetime", tzinfo=delta_tzinfo_neg
- ),
- ).order_by("start_datetime")
-
- utc_model = qs.get()
- self.assertEqual(utc_model.day, 15)
- self.assertEqual(utc_model.day_melb, 16)
- self.assertEqual(utc_model.week, 25)
- self.assertEqual(utc_model.isoyear, 2015)
- self.assertEqual(utc_model.weekday, 2)
- self.assertEqual(utc_model.weekday_melb, 3)
- self.assertEqual(utc_model.isoweekday, 1)
- self.assertEqual(utc_model.isoweekday_melb, 2)
- self.assertEqual(utc_model.quarter, 2)
- self.assertEqual(utc_model.hour, 23)
- self.assertEqual(utc_model.hour_melb, 9)
- self.assertEqual(utc_model.hour_with_delta_pos, 4)
- self.assertEqual(utc_model.hour_with_delta_neg, 18)
- self.assertEqual(utc_model.minute_with_delta_neg, 47)
-
- with timezone.override(melb):
- melb_model = qs.get()
-
- self.assertEqual(melb_model.day, 16)
- self.assertEqual(melb_model.day_melb, 16)
- self.assertEqual(melb_model.week, 25)
- self.assertEqual(melb_model.isoyear, 2015)
- self.assertEqual(melb_model.weekday, 3)
- self.assertEqual(melb_model.isoweekday, 2)
- self.assertEqual(melb_model.quarter, 2)
- self.assertEqual(melb_model.weekday_melb, 3)
- self.assertEqual(melb_model.isoweekday_melb, 2)
- self.assertEqual(melb_model.hour, 9)
- self.assertEqual(melb_model.hour_melb, 9)
+ qs = DTModel.objects.annotate(
+ day=Extract("start_datetime", "day"),
+ day_melb=Extract("start_datetime", "day", tzinfo=melb),
+ week=Extract("start_datetime", "week", tzinfo=melb),
+ isoyear=ExtractIsoYear("start_datetime", tzinfo=melb),
+ weekday=ExtractWeekDay("start_datetime"),
+ weekday_melb=ExtractWeekDay("start_datetime", tzinfo=melb),
+ isoweekday=ExtractIsoWeekDay("start_datetime"),
+ isoweekday_melb=ExtractIsoWeekDay("start_datetime", tzinfo=melb),
+ quarter=ExtractQuarter("start_datetime", tzinfo=melb),
+ hour=ExtractHour("start_datetime"),
+ hour_melb=ExtractHour("start_datetime", tzinfo=melb),
+ hour_with_delta_pos=ExtractHour("start_datetime", tzinfo=delta_tzinfo_pos),
+ hour_with_delta_neg=ExtractHour("start_datetime", tzinfo=delta_tzinfo_neg),
+ minute_with_delta_neg=ExtractMinute(
+ "start_datetime", tzinfo=delta_tzinfo_neg
+ ),
+ ).order_by("start_datetime")
+
+ utc_model = qs.get()
+ self.assertEqual(utc_model.day, 15)
+ self.assertEqual(utc_model.day_melb, 16)
+ self.assertEqual(utc_model.week, 25)
+ self.assertEqual(utc_model.isoyear, 2015)
+ self.assertEqual(utc_model.weekday, 2)
+ self.assertEqual(utc_model.weekday_melb, 3)
+ self.assertEqual(utc_model.isoweekday, 1)
+ self.assertEqual(utc_model.isoweekday_melb, 2)
+ self.assertEqual(utc_model.quarter, 2)
+ self.assertEqual(utc_model.hour, 23)
+ self.assertEqual(utc_model.hour_melb, 9)
+ self.assertEqual(utc_model.hour_with_delta_pos, 4)
+ self.assertEqual(utc_model.hour_with_delta_neg, 18)
+ self.assertEqual(utc_model.minute_with_delta_neg, 47)
+
+ with timezone.override(melb):
+ melb_model = qs.get()
+
+ self.assertEqual(melb_model.day, 16)
+ self.assertEqual(melb_model.day_melb, 16)
+ self.assertEqual(melb_model.week, 25)
+ self.assertEqual(melb_model.isoyear, 2015)
+ self.assertEqual(melb_model.weekday, 3)
+ self.assertEqual(melb_model.isoweekday, 2)
+ self.assertEqual(melb_model.quarter, 2)
+ self.assertEqual(melb_model.weekday_melb, 3)
+ self.assertEqual(melb_model.isoweekday_melb, 2)
+ self.assertEqual(melb_model.hour, 9)
+ self.assertEqual(melb_model.hour_melb, 9)
def test_extract_func_with_timezone_minus_no_offset(self):
start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
@@ -1765,22 +1735,22 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
start_datetime = timezone.make_aware(start_datetime)
end_datetime = timezone.make_aware(end_datetime)
self.create_model(start_datetime, end_datetime)
- for ust_nera in self.get_timezones("Asia/Ust-Nera"):
- with self.subTest(repr(ust_nera)):
- qs = DTModel.objects.annotate(
- hour=ExtractHour("start_datetime"),
- hour_tz=ExtractHour("start_datetime", tzinfo=ust_nera),
- ).order_by("start_datetime")
+ ust_nera = zoneinfo.ZoneInfo("Asia/Ust-Nera")
- utc_model = qs.get()
- self.assertEqual(utc_model.hour, 23)
- self.assertEqual(utc_model.hour_tz, 9)
+ qs = DTModel.objects.annotate(
+ hour=ExtractHour("start_datetime"),
+ hour_tz=ExtractHour("start_datetime", tzinfo=ust_nera),
+ ).order_by("start_datetime")
+
+ utc_model = qs.get()
+ self.assertEqual(utc_model.hour, 23)
+ self.assertEqual(utc_model.hour_tz, 9)
- with timezone.override(ust_nera):
- ust_nera_model = qs.get()
+ with timezone.override(ust_nera):
+ ust_nera_model = qs.get()
- self.assertEqual(ust_nera_model.hour, 9)
- self.assertEqual(ust_nera_model.hour_tz, 9)
+ self.assertEqual(ust_nera_model.hour, 9)
+ self.assertEqual(ust_nera_model.hour_tz, 9)
def test_extract_func_explicit_timezone_priority(self):
start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
@@ -1788,35 +1758,32 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
start_datetime = timezone.make_aware(start_datetime)
end_datetime = timezone.make_aware(end_datetime)
self.create_model(start_datetime, end_datetime)
-
- for melb in self.get_timezones("Australia/Melbourne"):
- with self.subTest(repr(melb)):
- with timezone.override(melb):
- model = (
- DTModel.objects.annotate(
- day_melb=Extract("start_datetime", "day"),
- day_utc=Extract(
- "start_datetime", "day", tzinfo=datetime_timezone.utc
- ),
- )
- .order_by("start_datetime")
- .get()
- )
- self.assertEqual(model.day_melb, 16)
- self.assertEqual(model.day_utc, 15)
+ melb = zoneinfo.ZoneInfo("Australia/Melbourne")
+ with timezone.override(melb):
+ model = (
+ DTModel.objects.annotate(
+ day_melb=Extract("start_datetime", "day"),
+ day_utc=Extract(
+ "start_datetime", "day", tzinfo=datetime_timezone.utc
+ ),
+ )
+ .order_by("start_datetime")
+ .get()
+ )
+ self.assertEqual(model.day_melb, 16)
+ self.assertEqual(model.day_utc, 15)
def test_extract_invalid_field_with_timezone(self):
- for melb in self.get_timezones("Australia/Melbourne"):
- with self.subTest(repr(melb)):
- msg = "tzinfo can only be used with DateTimeField."
- with self.assertRaisesMessage(ValueError, msg):
- DTModel.objects.annotate(
- day_melb=Extract("start_date", "day", tzinfo=melb),
- ).get()
- with self.assertRaisesMessage(ValueError, msg):
- DTModel.objects.annotate(
- hour_melb=Extract("start_time", "hour", tzinfo=melb),
- ).get()
+ melb = zoneinfo.ZoneInfo("Australia/Melbourne")
+ msg = "tzinfo can only be used with DateTimeField."
+ with self.assertRaisesMessage(ValueError, msg):
+ DTModel.objects.annotate(
+ day_melb=Extract("start_date", "day", tzinfo=melb),
+ ).get()
+ with self.assertRaisesMessage(ValueError, msg):
+ DTModel.objects.annotate(
+ hour_melb=Extract("start_time", "hour", tzinfo=melb),
+ ).get()
def test_trunc_timezone_applied_before_truncation(self):
start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
@@ -1824,74 +1791,36 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
start_datetime = timezone.make_aware(start_datetime)
end_datetime = timezone.make_aware(end_datetime)
self.create_model(start_datetime, end_datetime)
+ melb = zoneinfo.ZoneInfo("Australia/Melbourne")
+ pacific = zoneinfo.ZoneInfo("America/Los_Angeles")
- for melb, pacific in zip(
- self.get_timezones("Australia/Melbourne"),
- self.get_timezones("America/Los_Angeles"),
- ):
- with self.subTest((repr(melb), repr(pacific))):
- model = (
- DTModel.objects.annotate(
- melb_year=TruncYear("start_datetime", tzinfo=melb),
- pacific_year=TruncYear("start_datetime", tzinfo=pacific),
- melb_date=TruncDate("start_datetime", tzinfo=melb),
- pacific_date=TruncDate("start_datetime", tzinfo=pacific),
- melb_time=TruncTime("start_datetime", tzinfo=melb),
- pacific_time=TruncTime("start_datetime", tzinfo=pacific),
- )
- .order_by("start_datetime")
- .get()
- )
+ model = (
+ DTModel.objects.annotate(
+ melb_year=TruncYear("start_datetime", tzinfo=melb),
+ pacific_year=TruncYear("start_datetime", tzinfo=pacific),
+ melb_date=TruncDate("start_datetime", tzinfo=melb),
+ pacific_date=TruncDate("start_datetime", tzinfo=pacific),
+ melb_time=TruncTime("start_datetime", tzinfo=melb),
+ pacific_time=TruncTime("start_datetime", tzinfo=pacific),
+ )
+ .order_by("start_datetime")
+ .get()
+ )
- melb_start_datetime = start_datetime.astimezone(melb)
- pacific_start_datetime = start_datetime.astimezone(pacific)
- self.assertEqual(model.start_datetime, start_datetime)
- self.assertEqual(
- model.melb_year, truncate_to(start_datetime, "year", melb)
- )
- self.assertEqual(
- model.pacific_year, truncate_to(start_datetime, "year", pacific)
- )
- self.assertEqual(model.start_datetime.year, 2016)
- self.assertEqual(model.melb_year.year, 2016)
- self.assertEqual(model.pacific_year.year, 2015)
- self.assertEqual(model.melb_date, melb_start_datetime.date())
- self.assertEqual(model.pacific_date, pacific_start_datetime.date())
- self.assertEqual(model.melb_time, melb_start_datetime.time())
- self.assertEqual(model.pacific_time, pacific_start_datetime.time())
-
- @needs_pytz
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_trunc_ambiguous_and_invalid_times(self):
- sao = pytz.timezone("America/Sao_Paulo")
- start_datetime = datetime(2016, 10, 16, 13, tzinfo=datetime_timezone.utc)
- end_datetime = datetime(2016, 2, 21, 1, tzinfo=datetime_timezone.utc)
- self.create_model(start_datetime, end_datetime)
- with timezone.override(sao):
- with self.assertRaisesMessage(
- pytz.NonExistentTimeError, "2016-10-16 00:00:00"
- ):
- model = DTModel.objects.annotate(
- truncated_start=TruncDay("start_datetime")
- ).get()
- with self.assertRaisesMessage(
- pytz.AmbiguousTimeError, "2016-02-20 23:00:00"
- ):
- model = DTModel.objects.annotate(
- truncated_end=TruncHour("end_datetime")
- ).get()
- model = DTModel.objects.annotate(
- truncated_start=TruncDay("start_datetime", is_dst=False),
- truncated_end=TruncHour("end_datetime", is_dst=False),
- ).get()
- self.assertEqual(model.truncated_start.dst(), timedelta(0))
- self.assertEqual(model.truncated_end.dst(), timedelta(0))
- model = DTModel.objects.annotate(
- truncated_start=TruncDay("start_datetime", is_dst=True),
- truncated_end=TruncHour("end_datetime", is_dst=True),
- ).get()
- self.assertEqual(model.truncated_start.dst(), timedelta(0, 3600))
- self.assertEqual(model.truncated_end.dst(), timedelta(0, 3600))
+ melb_start_datetime = start_datetime.astimezone(melb)
+ pacific_start_datetime = start_datetime.astimezone(pacific)
+ self.assertEqual(model.start_datetime, start_datetime)
+ self.assertEqual(model.melb_year, truncate_to(start_datetime, "year", melb))
+ self.assertEqual(
+ model.pacific_year, truncate_to(start_datetime, "year", pacific)
+ )
+ self.assertEqual(model.start_datetime.year, 2016)
+ self.assertEqual(model.melb_year.year, 2016)
+ self.assertEqual(model.pacific_year.year, 2015)
+ self.assertEqual(model.melb_date, melb_start_datetime.date())
+ self.assertEqual(model.pacific_date, pacific_start_datetime.date())
+ self.assertEqual(model.melb_time, melb_start_datetime.time())
+ self.assertEqual(model.pacific_time, pacific_start_datetime.time())
def test_trunc_func_with_timezone(self):
"""
@@ -1904,118 +1833,109 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
end_datetime = timezone.make_aware(end_datetime)
self.create_model(start_datetime, end_datetime)
self.create_model(end_datetime, start_datetime)
+ melb = zoneinfo.ZoneInfo("Australia/Melbourne")
- for melb in self.get_timezones("Australia/Melbourne"):
- with self.subTest(repr(melb)):
-
- def test_datetime_kind(kind):
- self.assertQuerySetEqual(
- DTModel.objects.annotate(
- truncated=Trunc(
- "start_datetime",
- kind,
- output_field=DateTimeField(),
- tzinfo=melb,
- )
- ).order_by("start_datetime"),
- [
- (
- start_datetime,
- truncate_to(
- start_datetime.astimezone(melb), kind, melb
- ),
- ),
- (
- end_datetime,
- truncate_to(end_datetime.astimezone(melb), kind, melb),
- ),
- ],
- lambda m: (m.start_datetime, m.truncated),
+ def test_datetime_kind(kind):
+ self.assertQuerySetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc(
+ "start_datetime",
+ kind,
+ output_field=DateTimeField(),
+ tzinfo=melb,
)
+ ).order_by("start_datetime"),
+ [
+ (
+ start_datetime,
+ truncate_to(start_datetime.astimezone(melb), kind, melb),
+ ),
+ (
+ end_datetime,
+ truncate_to(end_datetime.astimezone(melb), kind, melb),
+ ),
+ ],
+ lambda m: (m.start_datetime, m.truncated),
+ )
- def test_datetime_to_date_kind(kind):
- self.assertQuerySetEqual(
- DTModel.objects.annotate(
- truncated=Trunc(
- "start_datetime",
- kind,
- output_field=DateField(),
- tzinfo=melb,
- ),
- ).order_by("start_datetime"),
- [
- (
- start_datetime,
- truncate_to(
- start_datetime.astimezone(melb).date(), kind
- ),
- ),
- (
- end_datetime,
- truncate_to(end_datetime.astimezone(melb).date(), kind),
- ),
- ],
- lambda m: (m.start_datetime, m.truncated),
- )
+ def test_datetime_to_date_kind(kind):
+ self.assertQuerySetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc(
+ "start_datetime",
+ kind,
+ output_field=DateField(),
+ tzinfo=melb,
+ ),
+ ).order_by("start_datetime"),
+ [
+ (
+ start_datetime,
+ truncate_to(start_datetime.astimezone(melb).date(), kind),
+ ),
+ (
+ end_datetime,
+ truncate_to(end_datetime.astimezone(melb).date(), kind),
+ ),
+ ],
+ lambda m: (m.start_datetime, m.truncated),
+ )
- def test_datetime_to_time_kind(kind):
- self.assertQuerySetEqual(
- DTModel.objects.annotate(
- truncated=Trunc(
- "start_datetime",
- kind,
- output_field=TimeField(),
- tzinfo=melb,
- )
- ).order_by("start_datetime"),
- [
- (
- start_datetime,
- truncate_to(
- start_datetime.astimezone(melb).time(), kind
- ),
- ),
- (
- end_datetime,
- truncate_to(end_datetime.astimezone(melb).time(), kind),
- ),
- ],
- lambda m: (m.start_datetime, m.truncated),
+ def test_datetime_to_time_kind(kind):
+ self.assertQuerySetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc(
+ "start_datetime",
+ kind,
+ output_field=TimeField(),
+ tzinfo=melb,
)
+ ).order_by("start_datetime"),
+ [
+ (
+ start_datetime,
+ truncate_to(start_datetime.astimezone(melb).time(), kind),
+ ),
+ (
+ end_datetime,
+ truncate_to(end_datetime.astimezone(melb).time(), kind),
+ ),
+ ],
+ lambda m: (m.start_datetime, m.truncated),
+ )
- test_datetime_to_date_kind("year")
- test_datetime_to_date_kind("quarter")
- test_datetime_to_date_kind("month")
- test_datetime_to_date_kind("week")
- test_datetime_to_date_kind("day")
- test_datetime_to_time_kind("hour")
- test_datetime_to_time_kind("minute")
- test_datetime_to_time_kind("second")
- test_datetime_kind("year")
- test_datetime_kind("quarter")
- test_datetime_kind("month")
- test_datetime_kind("week")
- test_datetime_kind("day")
- test_datetime_kind("hour")
- test_datetime_kind("minute")
- test_datetime_kind("second")
+ test_datetime_to_date_kind("year")
+ test_datetime_to_date_kind("quarter")
+ test_datetime_to_date_kind("month")
+ test_datetime_to_date_kind("week")
+ test_datetime_to_date_kind("day")
+ test_datetime_to_time_kind("hour")
+ test_datetime_to_time_kind("minute")
+ test_datetime_to_time_kind("second")
+ test_datetime_kind("year")
+ test_datetime_kind("quarter")
+ test_datetime_kind("month")
+ test_datetime_kind("week")
+ test_datetime_kind("day")
+ test_datetime_kind("hour")
+ test_datetime_kind("minute")
+ test_datetime_kind("second")
- qs = DTModel.objects.filter(
- start_datetime__date=Trunc(
- "start_datetime", "day", output_field=DateField()
- )
- )
- self.assertEqual(qs.count(), 2)
+ qs = DTModel.objects.filter(
+ start_datetime__date=Trunc(
+ "start_datetime", "day", output_field=DateField()
+ )
+ )
+ self.assertEqual(qs.count(), 2)
def test_trunc_invalid_field_with_timezone(self):
- for melb in self.get_timezones("Australia/Melbourne"):
- with self.subTest(repr(melb)):
- msg = "tzinfo can only be used with DateTimeField."
- with self.assertRaisesMessage(ValueError, msg):
- DTModel.objects.annotate(
- day_melb=Trunc("start_date", "day", tzinfo=melb),
- ).get()
- with self.assertRaisesMessage(ValueError, msg):
- DTModel.objects.annotate(
- hour_melb=Trunc("start_time", "hour", tzinfo=melb),
- ).get()
+ melb = zoneinfo.ZoneInfo("Australia/Melbourne")
+ msg = "tzinfo can only be used with DateTimeField."
+ with self.assertRaisesMessage(ValueError, msg):
+ DTModel.objects.annotate(
+ day_melb=Trunc("start_date", "day", tzinfo=melb),
+ ).get()
+ with self.assertRaisesMessage(ValueError, msg):
+ DTModel.objects.annotate(
+ hour_melb=Trunc("start_time", "hour", tzinfo=melb),
+ ).get()