test_extract_trunc.py 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362
  1. from datetime import datetime, timedelta, timezone as datetime_timezone
  2. import pytz
  3. try:
  4. import zoneinfo
  5. except ImportError:
  6. try:
  7. from backports import zoneinfo
  8. except ImportError:
  9. zoneinfo = None
  10. from django.conf import settings
  11. from django.db.models import (
  12. DateField, DateTimeField, F, IntegerField, Max, OuterRef, Subquery,
  13. TimeField,
  14. )
  15. from django.db.models.functions import (
  16. Extract, ExtractDay, ExtractHour, ExtractIsoWeekDay, ExtractIsoYear,
  17. ExtractMinute, ExtractMonth, ExtractQuarter, ExtractSecond, ExtractWeek,
  18. ExtractWeekDay, ExtractYear, Trunc, TruncDate, TruncDay, TruncHour,
  19. TruncMinute, TruncMonth, TruncQuarter, TruncSecond, TruncTime, TruncWeek,
  20. TruncYear,
  21. )
  22. from django.test import (
  23. TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
  24. )
  25. from django.utils import timezone
  26. from ..models import Author, DTModel, Fan
  27. ZONE_CONSTRUCTORS = (pytz.timezone,)
  28. if zoneinfo is not None:
  29. ZONE_CONSTRUCTORS += (zoneinfo.ZoneInfo,)
  30. def truncate_to(value, kind, tzinfo=None):
  31. # Convert to target timezone before truncation
  32. if tzinfo is not None:
  33. value = value.astimezone(tzinfo)
  34. def truncate(value, kind):
  35. if kind == 'second':
  36. return value.replace(microsecond=0)
  37. if kind == 'minute':
  38. return value.replace(second=0, microsecond=0)
  39. if kind == 'hour':
  40. return value.replace(minute=0, second=0, microsecond=0)
  41. if kind == 'day':
  42. if isinstance(value, datetime):
  43. return value.replace(hour=0, minute=0, second=0, microsecond=0)
  44. return value
  45. if kind == 'week':
  46. if isinstance(value, datetime):
  47. return (value - timedelta(days=value.weekday())).replace(hour=0, minute=0, second=0, microsecond=0)
  48. return value - timedelta(days=value.weekday())
  49. if kind == 'month':
  50. if isinstance(value, datetime):
  51. return value.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
  52. return value.replace(day=1)
  53. if kind == 'quarter':
  54. month_in_quarter = value.month - (value.month - 1) % 3
  55. if isinstance(value, datetime):
  56. return value.replace(month=month_in_quarter, day=1, hour=0, minute=0, second=0, microsecond=0)
  57. return value.replace(month=month_in_quarter, day=1)
  58. # otherwise, truncate to year
  59. if isinstance(value, datetime):
  60. return value.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
  61. return value.replace(month=1, day=1)
  62. value = truncate(value, kind)
  63. if tzinfo is not None:
  64. # If there was a daylight saving transition, then reset the timezone.
  65. value = timezone.make_aware(value.replace(tzinfo=None), tzinfo)
  66. return value
  67. @override_settings(USE_TZ=False)
  68. class DateFunctionTests(TestCase):
  69. def create_model(self, start_datetime, end_datetime):
  70. return DTModel.objects.create(
  71. name=start_datetime.isoformat() if start_datetime else 'None',
  72. start_datetime=start_datetime,
  73. end_datetime=end_datetime,
  74. start_date=start_datetime.date() if start_datetime else None,
  75. end_date=end_datetime.date() if end_datetime else None,
  76. start_time=start_datetime.time() if start_datetime else None,
  77. end_time=end_datetime.time() if end_datetime else None,
  78. duration=(end_datetime - start_datetime) if start_datetime and end_datetime else None,
  79. )
  80. def test_extract_year_exact_lookup(self):
  81. """
  82. Extract year uses a BETWEEN filter to compare the year to allow indexes
  83. to be used.
  84. """
  85. start_datetime = datetime(2015, 6, 15, 14, 10)
  86. end_datetime = datetime(2016, 6, 15, 14, 10)
  87. if settings.USE_TZ:
  88. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  89. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  90. self.create_model(start_datetime, end_datetime)
  91. self.create_model(end_datetime, start_datetime)
  92. for lookup in ('year', 'iso_year'):
  93. with self.subTest(lookup):
  94. qs = DTModel.objects.filter(**{'start_datetime__%s__exact' % lookup: 2015})
  95. self.assertEqual(qs.count(), 1)
  96. query_string = str(qs.query).lower()
  97. self.assertEqual(query_string.count(' between '), 1)
  98. self.assertEqual(query_string.count('extract'), 0)
  99. # exact is implied and should be the same
  100. qs = DTModel.objects.filter(**{'start_datetime__%s' % lookup: 2015})
  101. self.assertEqual(qs.count(), 1)
  102. query_string = str(qs.query).lower()
  103. self.assertEqual(query_string.count(' between '), 1)
  104. self.assertEqual(query_string.count('extract'), 0)
  105. # date and datetime fields should behave the same
  106. qs = DTModel.objects.filter(**{'start_date__%s' % lookup: 2015})
  107. self.assertEqual(qs.count(), 1)
  108. query_string = str(qs.query).lower()
  109. self.assertEqual(query_string.count(' between '), 1)
  110. self.assertEqual(query_string.count('extract'), 0)
  111. # an expression rhs cannot use the between optimization.
  112. qs = DTModel.objects.annotate(
  113. start_year=ExtractYear('start_datetime'),
  114. ).filter(end_datetime__year=F('start_year') + 1)
  115. self.assertEqual(qs.count(), 1)
  116. query_string = str(qs.query).lower()
  117. self.assertEqual(query_string.count(' between '), 0)
  118. self.assertEqual(query_string.count('extract'), 3)
  119. def test_extract_year_greaterthan_lookup(self):
  120. start_datetime = datetime(2015, 6, 15, 14, 10)
  121. end_datetime = datetime(2016, 6, 15, 14, 10)
  122. if settings.USE_TZ:
  123. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  124. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  125. self.create_model(start_datetime, end_datetime)
  126. self.create_model(end_datetime, start_datetime)
  127. for lookup in ('year', 'iso_year'):
  128. with self.subTest(lookup):
  129. qs = DTModel.objects.filter(**{'start_datetime__%s__gt' % lookup: 2015})
  130. self.assertEqual(qs.count(), 1)
  131. self.assertEqual(str(qs.query).lower().count('extract'), 0)
  132. qs = DTModel.objects.filter(**{'start_datetime__%s__gte' % lookup: 2015})
  133. self.assertEqual(qs.count(), 2)
  134. self.assertEqual(str(qs.query).lower().count('extract'), 0)
  135. qs = DTModel.objects.annotate(
  136. start_year=ExtractYear('start_datetime'),
  137. ).filter(**{'end_datetime__%s__gte' % lookup: F('start_year')})
  138. self.assertEqual(qs.count(), 1)
  139. self.assertGreaterEqual(str(qs.query).lower().count('extract'), 2)
  140. def test_extract_year_lessthan_lookup(self):
  141. start_datetime = datetime(2015, 6, 15, 14, 10)
  142. end_datetime = datetime(2016, 6, 15, 14, 10)
  143. if settings.USE_TZ:
  144. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  145. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  146. self.create_model(start_datetime, end_datetime)
  147. self.create_model(end_datetime, start_datetime)
  148. for lookup in ('year', 'iso_year'):
  149. with self.subTest(lookup):
  150. qs = DTModel.objects.filter(**{'start_datetime__%s__lt' % lookup: 2016})
  151. self.assertEqual(qs.count(), 1)
  152. self.assertEqual(str(qs.query).count('extract'), 0)
  153. qs = DTModel.objects.filter(**{'start_datetime__%s__lte' % lookup: 2016})
  154. self.assertEqual(qs.count(), 2)
  155. self.assertEqual(str(qs.query).count('extract'), 0)
  156. qs = DTModel.objects.annotate(
  157. end_year=ExtractYear('end_datetime'),
  158. ).filter(**{'start_datetime__%s__lte' % lookup: F('end_year')})
  159. self.assertEqual(qs.count(), 1)
  160. self.assertGreaterEqual(str(qs.query).lower().count('extract'), 2)
  161. def test_extract_func(self):
  162. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  163. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  164. if settings.USE_TZ:
  165. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  166. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  167. self.create_model(start_datetime, end_datetime)
  168. self.create_model(end_datetime, start_datetime)
  169. with self.assertRaisesMessage(ValueError, 'lookup_name must be provided'):
  170. Extract('start_datetime')
  171. msg = 'Extract input expression must be DateField, DateTimeField, TimeField, or DurationField.'
  172. with self.assertRaisesMessage(ValueError, msg):
  173. list(DTModel.objects.annotate(extracted=Extract('name', 'hour')))
  174. with self.assertRaisesMessage(
  175. ValueError, "Cannot extract time component 'second' from DateField 'start_date'."):
  176. list(DTModel.objects.annotate(extracted=Extract('start_date', 'second')))
  177. self.assertQuerysetEqual(
  178. DTModel.objects.annotate(extracted=Extract('start_datetime', 'year')).order_by('start_datetime'),
  179. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  180. lambda m: (m.start_datetime, m.extracted)
  181. )
  182. self.assertQuerysetEqual(
  183. DTModel.objects.annotate(extracted=Extract('start_datetime', 'quarter')).order_by('start_datetime'),
  184. [(start_datetime, 2), (end_datetime, 2)],
  185. lambda m: (m.start_datetime, m.extracted)
  186. )
  187. self.assertQuerysetEqual(
  188. DTModel.objects.annotate(extracted=Extract('start_datetime', 'month')).order_by('start_datetime'),
  189. [(start_datetime, start_datetime.month), (end_datetime, end_datetime.month)],
  190. lambda m: (m.start_datetime, m.extracted)
  191. )
  192. self.assertQuerysetEqual(
  193. DTModel.objects.annotate(extracted=Extract('start_datetime', 'day')).order_by('start_datetime'),
  194. [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
  195. lambda m: (m.start_datetime, m.extracted)
  196. )
  197. self.assertQuerysetEqual(
  198. DTModel.objects.annotate(extracted=Extract('start_datetime', 'week')).order_by('start_datetime'),
  199. [(start_datetime, 25), (end_datetime, 24)],
  200. lambda m: (m.start_datetime, m.extracted)
  201. )
  202. self.assertQuerysetEqual(
  203. DTModel.objects.annotate(extracted=Extract('start_datetime', 'week_day')).order_by('start_datetime'),
  204. [
  205. (start_datetime, (start_datetime.isoweekday() % 7) + 1),
  206. (end_datetime, (end_datetime.isoweekday() % 7) + 1)
  207. ],
  208. lambda m: (m.start_datetime, m.extracted)
  209. )
  210. self.assertQuerysetEqual(
  211. DTModel.objects.annotate(
  212. extracted=Extract('start_datetime', 'iso_week_day'),
  213. ).order_by('start_datetime'),
  214. [
  215. (start_datetime, start_datetime.isoweekday()),
  216. (end_datetime, end_datetime.isoweekday()),
  217. ],
  218. lambda m: (m.start_datetime, m.extracted)
  219. )
  220. self.assertQuerysetEqual(
  221. DTModel.objects.annotate(extracted=Extract('start_datetime', 'hour')).order_by('start_datetime'),
  222. [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
  223. lambda m: (m.start_datetime, m.extracted)
  224. )
  225. self.assertQuerysetEqual(
  226. DTModel.objects.annotate(extracted=Extract('start_datetime', 'minute')).order_by('start_datetime'),
  227. [(start_datetime, start_datetime.minute), (end_datetime, end_datetime.minute)],
  228. lambda m: (m.start_datetime, m.extracted)
  229. )
  230. self.assertQuerysetEqual(
  231. DTModel.objects.annotate(extracted=Extract('start_datetime', 'second')).order_by('start_datetime'),
  232. [(start_datetime, start_datetime.second), (end_datetime, end_datetime.second)],
  233. lambda m: (m.start_datetime, m.extracted)
  234. )
  235. self.assertEqual(DTModel.objects.filter(start_datetime__year=Extract('start_datetime', 'year')).count(), 2)
  236. self.assertEqual(DTModel.objects.filter(start_datetime__hour=Extract('start_datetime', 'hour')).count(), 2)
  237. self.assertEqual(DTModel.objects.filter(start_date__month=Extract('start_date', 'month')).count(), 2)
  238. self.assertEqual(DTModel.objects.filter(start_time__hour=Extract('start_time', 'hour')).count(), 2)
  239. def test_extract_none(self):
  240. self.create_model(None, None)
  241. for t in (Extract('start_datetime', 'year'), Extract('start_date', 'year'), Extract('start_time', 'hour')):
  242. with self.subTest(t):
  243. self.assertIsNone(DTModel.objects.annotate(extracted=t).first().extracted)
  244. @skipUnlessDBFeature('has_native_duration_field')
  245. def test_extract_duration(self):
  246. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  247. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  248. if settings.USE_TZ:
  249. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  250. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  251. self.create_model(start_datetime, end_datetime)
  252. self.create_model(end_datetime, start_datetime)
  253. self.assertQuerysetEqual(
  254. DTModel.objects.annotate(extracted=Extract('duration', 'second')).order_by('start_datetime'),
  255. [
  256. (start_datetime, (end_datetime - start_datetime).seconds % 60),
  257. (end_datetime, (start_datetime - end_datetime).seconds % 60)
  258. ],
  259. lambda m: (m.start_datetime, m.extracted)
  260. )
  261. self.assertEqual(
  262. DTModel.objects.annotate(
  263. duration_days=Extract('duration', 'day'),
  264. ).filter(duration_days__gt=200).count(),
  265. 1
  266. )
  267. @skipIfDBFeature('has_native_duration_field')
  268. def test_extract_duration_without_native_duration_field(self):
  269. msg = 'Extract requires native DurationField database support.'
  270. with self.assertRaisesMessage(ValueError, msg):
  271. list(DTModel.objects.annotate(extracted=Extract('duration', 'second')))
  272. def test_extract_duration_unsupported_lookups(self):
  273. msg = "Cannot extract component '%s' from DurationField 'duration'."
  274. for lookup in (
  275. 'year', 'iso_year', 'month', 'week', 'week_day', 'iso_week_day',
  276. 'quarter',
  277. ):
  278. with self.subTest(lookup):
  279. with self.assertRaisesMessage(ValueError, msg % lookup):
  280. DTModel.objects.annotate(extracted=Extract('duration', lookup))
  281. def test_extract_year_func(self):
  282. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  283. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  284. if settings.USE_TZ:
  285. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  286. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  287. self.create_model(start_datetime, end_datetime)
  288. self.create_model(end_datetime, start_datetime)
  289. self.assertQuerysetEqual(
  290. DTModel.objects.annotate(extracted=ExtractYear('start_datetime')).order_by('start_datetime'),
  291. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  292. lambda m: (m.start_datetime, m.extracted)
  293. )
  294. self.assertQuerysetEqual(
  295. DTModel.objects.annotate(extracted=ExtractYear('start_date')).order_by('start_datetime'),
  296. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  297. lambda m: (m.start_datetime, m.extracted)
  298. )
  299. self.assertEqual(DTModel.objects.filter(start_datetime__year=ExtractYear('start_datetime')).count(), 2)
  300. def test_extract_iso_year_func(self):
  301. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  302. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  303. if settings.USE_TZ:
  304. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  305. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  306. self.create_model(start_datetime, end_datetime)
  307. self.create_model(end_datetime, start_datetime)
  308. self.assertQuerysetEqual(
  309. DTModel.objects.annotate(extracted=ExtractIsoYear('start_datetime')).order_by('start_datetime'),
  310. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  311. lambda m: (m.start_datetime, m.extracted)
  312. )
  313. self.assertQuerysetEqual(
  314. DTModel.objects.annotate(extracted=ExtractIsoYear('start_date')).order_by('start_datetime'),
  315. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  316. lambda m: (m.start_datetime, m.extracted)
  317. )
  318. # Both dates are from the same week year.
  319. self.assertEqual(DTModel.objects.filter(start_datetime__iso_year=ExtractIsoYear('start_datetime')).count(), 2)
  320. def test_extract_iso_year_func_boundaries(self):
  321. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  322. if settings.USE_TZ:
  323. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  324. week_52_day_2014 = datetime(2014, 12, 27, 13, 0) # Sunday
  325. week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
  326. week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
  327. if settings.USE_TZ:
  328. week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015, is_dst=False)
  329. week_52_day_2014 = timezone.make_aware(week_52_day_2014, is_dst=False)
  330. week_53_day_2015 = timezone.make_aware(week_53_day_2015, is_dst=False)
  331. days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
  332. obj_1_iso_2014 = self.create_model(week_52_day_2014, end_datetime)
  333. obj_1_iso_2015 = self.create_model(week_1_day_2014_2015, end_datetime)
  334. obj_2_iso_2015 = self.create_model(week_53_day_2015, end_datetime)
  335. qs = DTModel.objects.filter(start_datetime__in=days).annotate(
  336. extracted=ExtractIsoYear('start_datetime'),
  337. ).order_by('start_datetime')
  338. self.assertQuerysetEqual(qs, [
  339. (week_52_day_2014, 2014),
  340. (week_1_day_2014_2015, 2015),
  341. (week_53_day_2015, 2015),
  342. ], lambda m: (m.start_datetime, m.extracted))
  343. qs = DTModel.objects.filter(
  344. start_datetime__iso_year=2015,
  345. ).order_by('start_datetime')
  346. self.assertSequenceEqual(qs, [obj_1_iso_2015, obj_2_iso_2015])
  347. qs = DTModel.objects.filter(
  348. start_datetime__iso_year__gt=2014,
  349. ).order_by('start_datetime')
  350. self.assertSequenceEqual(qs, [obj_1_iso_2015, obj_2_iso_2015])
  351. qs = DTModel.objects.filter(
  352. start_datetime__iso_year__lte=2014,
  353. ).order_by('start_datetime')
  354. self.assertSequenceEqual(qs, [obj_1_iso_2014])
  355. def test_extract_month_func(self):
  356. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  357. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  358. if settings.USE_TZ:
  359. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  360. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  361. self.create_model(start_datetime, end_datetime)
  362. self.create_model(end_datetime, start_datetime)
  363. self.assertQuerysetEqual(
  364. DTModel.objects.annotate(extracted=ExtractMonth('start_datetime')).order_by('start_datetime'),
  365. [(start_datetime, start_datetime.month), (end_datetime, end_datetime.month)],
  366. lambda m: (m.start_datetime, m.extracted)
  367. )
  368. self.assertQuerysetEqual(
  369. DTModel.objects.annotate(extracted=ExtractMonth('start_date')).order_by('start_datetime'),
  370. [(start_datetime, start_datetime.month), (end_datetime, end_datetime.month)],
  371. lambda m: (m.start_datetime, m.extracted)
  372. )
  373. self.assertEqual(DTModel.objects.filter(start_datetime__month=ExtractMonth('start_datetime')).count(), 2)
  374. def test_extract_day_func(self):
  375. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  376. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  377. if settings.USE_TZ:
  378. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  379. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  380. self.create_model(start_datetime, end_datetime)
  381. self.create_model(end_datetime, start_datetime)
  382. self.assertQuerysetEqual(
  383. DTModel.objects.annotate(extracted=ExtractDay('start_datetime')).order_by('start_datetime'),
  384. [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
  385. lambda m: (m.start_datetime, m.extracted)
  386. )
  387. self.assertQuerysetEqual(
  388. DTModel.objects.annotate(extracted=ExtractDay('start_date')).order_by('start_datetime'),
  389. [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
  390. lambda m: (m.start_datetime, m.extracted)
  391. )
  392. self.assertEqual(DTModel.objects.filter(start_datetime__day=ExtractDay('start_datetime')).count(), 2)
  393. def test_extract_week_func(self):
  394. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  395. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  396. if settings.USE_TZ:
  397. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  398. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  399. self.create_model(start_datetime, end_datetime)
  400. self.create_model(end_datetime, start_datetime)
  401. self.assertQuerysetEqual(
  402. DTModel.objects.annotate(extracted=ExtractWeek('start_datetime')).order_by('start_datetime'),
  403. [(start_datetime, 25), (end_datetime, 24)],
  404. lambda m: (m.start_datetime, m.extracted)
  405. )
  406. self.assertQuerysetEqual(
  407. DTModel.objects.annotate(extracted=ExtractWeek('start_date')).order_by('start_datetime'),
  408. [(start_datetime, 25), (end_datetime, 24)],
  409. lambda m: (m.start_datetime, m.extracted)
  410. )
  411. # both dates are from the same week.
  412. self.assertEqual(DTModel.objects.filter(start_datetime__week=ExtractWeek('start_datetime')).count(), 2)
  413. def test_extract_quarter_func(self):
  414. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  415. end_datetime = datetime(2016, 8, 15, 14, 10, 50, 123)
  416. if settings.USE_TZ:
  417. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  418. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  419. self.create_model(start_datetime, end_datetime)
  420. self.create_model(end_datetime, start_datetime)
  421. self.assertQuerysetEqual(
  422. DTModel.objects.annotate(extracted=ExtractQuarter('start_datetime')).order_by('start_datetime'),
  423. [(start_datetime, 2), (end_datetime, 3)],
  424. lambda m: (m.start_datetime, m.extracted)
  425. )
  426. self.assertQuerysetEqual(
  427. DTModel.objects.annotate(extracted=ExtractQuarter('start_date')).order_by('start_datetime'),
  428. [(start_datetime, 2), (end_datetime, 3)],
  429. lambda m: (m.start_datetime, m.extracted)
  430. )
  431. self.assertEqual(DTModel.objects.filter(start_datetime__quarter=ExtractQuarter('start_datetime')).count(), 2)
  432. def test_extract_quarter_func_boundaries(self):
  433. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  434. if settings.USE_TZ:
  435. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  436. last_quarter_2014 = datetime(2014, 12, 31, 13, 0)
  437. first_quarter_2015 = datetime(2015, 1, 1, 13, 0)
  438. if settings.USE_TZ:
  439. last_quarter_2014 = timezone.make_aware(last_quarter_2014, is_dst=False)
  440. first_quarter_2015 = timezone.make_aware(first_quarter_2015, is_dst=False)
  441. dates = [last_quarter_2014, first_quarter_2015]
  442. self.create_model(last_quarter_2014, end_datetime)
  443. self.create_model(first_quarter_2015, end_datetime)
  444. qs = DTModel.objects.filter(start_datetime__in=dates).annotate(
  445. extracted=ExtractQuarter('start_datetime'),
  446. ).order_by('start_datetime')
  447. self.assertQuerysetEqual(qs, [
  448. (last_quarter_2014, 4),
  449. (first_quarter_2015, 1),
  450. ], lambda m: (m.start_datetime, m.extracted))
  451. def test_extract_week_func_boundaries(self):
  452. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  453. if settings.USE_TZ:
  454. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  455. week_52_day_2014 = datetime(2014, 12, 27, 13, 0) # Sunday
  456. week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
  457. week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
  458. if settings.USE_TZ:
  459. week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015, is_dst=False)
  460. week_52_day_2014 = timezone.make_aware(week_52_day_2014, is_dst=False)
  461. week_53_day_2015 = timezone.make_aware(week_53_day_2015, is_dst=False)
  462. days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
  463. self.create_model(week_53_day_2015, end_datetime)
  464. self.create_model(week_52_day_2014, end_datetime)
  465. self.create_model(week_1_day_2014_2015, end_datetime)
  466. qs = DTModel.objects.filter(start_datetime__in=days).annotate(
  467. extracted=ExtractWeek('start_datetime'),
  468. ).order_by('start_datetime')
  469. self.assertQuerysetEqual(qs, [
  470. (week_52_day_2014, 52),
  471. (week_1_day_2014_2015, 1),
  472. (week_53_day_2015, 53),
  473. ], lambda m: (m.start_datetime, m.extracted))
  474. def test_extract_weekday_func(self):
  475. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  476. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  477. if settings.USE_TZ:
  478. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  479. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  480. self.create_model(start_datetime, end_datetime)
  481. self.create_model(end_datetime, start_datetime)
  482. self.assertQuerysetEqual(
  483. DTModel.objects.annotate(extracted=ExtractWeekDay('start_datetime')).order_by('start_datetime'),
  484. [
  485. (start_datetime, (start_datetime.isoweekday() % 7) + 1),
  486. (end_datetime, (end_datetime.isoweekday() % 7) + 1),
  487. ],
  488. lambda m: (m.start_datetime, m.extracted)
  489. )
  490. self.assertQuerysetEqual(
  491. DTModel.objects.annotate(extracted=ExtractWeekDay('start_date')).order_by('start_datetime'),
  492. [
  493. (start_datetime, (start_datetime.isoweekday() % 7) + 1),
  494. (end_datetime, (end_datetime.isoweekday() % 7) + 1),
  495. ],
  496. lambda m: (m.start_datetime, m.extracted)
  497. )
  498. self.assertEqual(DTModel.objects.filter(start_datetime__week_day=ExtractWeekDay('start_datetime')).count(), 2)
  499. def test_extract_iso_weekday_func(self):
  500. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  501. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  502. if settings.USE_TZ:
  503. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  504. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  505. self.create_model(start_datetime, end_datetime)
  506. self.create_model(end_datetime, start_datetime)
  507. self.assertQuerysetEqual(
  508. DTModel.objects.annotate(
  509. extracted=ExtractIsoWeekDay('start_datetime'),
  510. ).order_by('start_datetime'),
  511. [
  512. (start_datetime, start_datetime.isoweekday()),
  513. (end_datetime, end_datetime.isoweekday()),
  514. ],
  515. lambda m: (m.start_datetime, m.extracted)
  516. )
  517. self.assertQuerysetEqual(
  518. DTModel.objects.annotate(
  519. extracted=ExtractIsoWeekDay('start_date'),
  520. ).order_by('start_datetime'),
  521. [
  522. (start_datetime, start_datetime.isoweekday()),
  523. (end_datetime, end_datetime.isoweekday()),
  524. ],
  525. lambda m: (m.start_datetime, m.extracted)
  526. )
  527. self.assertEqual(
  528. DTModel.objects.filter(
  529. start_datetime__week_day=ExtractWeekDay('start_datetime'),
  530. ).count(),
  531. 2,
  532. )
  533. def test_extract_hour_func(self):
  534. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  535. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  536. if settings.USE_TZ:
  537. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  538. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  539. self.create_model(start_datetime, end_datetime)
  540. self.create_model(end_datetime, start_datetime)
  541. self.assertQuerysetEqual(
  542. DTModel.objects.annotate(extracted=ExtractHour('start_datetime')).order_by('start_datetime'),
  543. [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
  544. lambda m: (m.start_datetime, m.extracted)
  545. )
  546. self.assertQuerysetEqual(
  547. DTModel.objects.annotate(extracted=ExtractHour('start_time')).order_by('start_datetime'),
  548. [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
  549. lambda m: (m.start_datetime, m.extracted)
  550. )
  551. self.assertEqual(DTModel.objects.filter(start_datetime__hour=ExtractHour('start_datetime')).count(), 2)
  552. def test_extract_minute_func(self):
  553. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  554. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  555. if settings.USE_TZ:
  556. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  557. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  558. self.create_model(start_datetime, end_datetime)
  559. self.create_model(end_datetime, start_datetime)
  560. self.assertQuerysetEqual(
  561. DTModel.objects.annotate(extracted=ExtractMinute('start_datetime')).order_by('start_datetime'),
  562. [(start_datetime, start_datetime.minute), (end_datetime, end_datetime.minute)],
  563. lambda m: (m.start_datetime, m.extracted)
  564. )
  565. self.assertQuerysetEqual(
  566. DTModel.objects.annotate(extracted=ExtractMinute('start_time')).order_by('start_datetime'),
  567. [(start_datetime, start_datetime.minute), (end_datetime, end_datetime.minute)],
  568. lambda m: (m.start_datetime, m.extracted)
  569. )
  570. self.assertEqual(DTModel.objects.filter(start_datetime__minute=ExtractMinute('start_datetime')).count(), 2)
  571. def test_extract_second_func(self):
  572. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  573. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  574. if settings.USE_TZ:
  575. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  576. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  577. self.create_model(start_datetime, end_datetime)
  578. self.create_model(end_datetime, start_datetime)
  579. self.assertQuerysetEqual(
  580. DTModel.objects.annotate(extracted=ExtractSecond('start_datetime')).order_by('start_datetime'),
  581. [(start_datetime, start_datetime.second), (end_datetime, end_datetime.second)],
  582. lambda m: (m.start_datetime, m.extracted)
  583. )
  584. self.assertQuerysetEqual(
  585. DTModel.objects.annotate(extracted=ExtractSecond('start_time')).order_by('start_datetime'),
  586. [(start_datetime, start_datetime.second), (end_datetime, end_datetime.second)],
  587. lambda m: (m.start_datetime, m.extracted)
  588. )
  589. self.assertEqual(DTModel.objects.filter(start_datetime__second=ExtractSecond('start_datetime')).count(), 2)
  590. def test_trunc_func(self):
  591. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  592. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  593. if settings.USE_TZ:
  594. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  595. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  596. self.create_model(start_datetime, end_datetime)
  597. self.create_model(end_datetime, start_datetime)
  598. msg = 'output_field must be either DateField, TimeField, or DateTimeField'
  599. with self.assertRaisesMessage(ValueError, msg):
  600. list(DTModel.objects.annotate(truncated=Trunc('start_datetime', 'year', output_field=IntegerField())))
  601. with self.assertRaisesMessage(AssertionError, "'name' isn't a DateField, TimeField, or DateTimeField."):
  602. list(DTModel.objects.annotate(truncated=Trunc('name', 'year', output_field=DateTimeField())))
  603. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  604. list(DTModel.objects.annotate(truncated=Trunc('start_date', 'second')))
  605. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  606. list(DTModel.objects.annotate(truncated=Trunc('start_time', 'month')))
  607. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  608. list(DTModel.objects.annotate(truncated=Trunc('start_date', 'month', output_field=DateTimeField())))
  609. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  610. list(DTModel.objects.annotate(truncated=Trunc('start_time', 'second', output_field=DateTimeField())))
  611. def test_datetime_kind(kind):
  612. self.assertQuerysetEqual(
  613. DTModel.objects.annotate(
  614. truncated=Trunc('start_datetime', kind, output_field=DateTimeField())
  615. ).order_by('start_datetime'),
  616. [
  617. (start_datetime, truncate_to(start_datetime, kind)),
  618. (end_datetime, truncate_to(end_datetime, kind))
  619. ],
  620. lambda m: (m.start_datetime, m.truncated)
  621. )
  622. def test_date_kind(kind):
  623. self.assertQuerysetEqual(
  624. DTModel.objects.annotate(
  625. truncated=Trunc('start_date', kind, output_field=DateField())
  626. ).order_by('start_datetime'),
  627. [
  628. (start_datetime, truncate_to(start_datetime.date(), kind)),
  629. (end_datetime, truncate_to(end_datetime.date(), kind))
  630. ],
  631. lambda m: (m.start_datetime, m.truncated)
  632. )
  633. def test_time_kind(kind):
  634. self.assertQuerysetEqual(
  635. DTModel.objects.annotate(
  636. truncated=Trunc('start_time', kind, output_field=TimeField())
  637. ).order_by('start_datetime'),
  638. [
  639. (start_datetime, truncate_to(start_datetime.time(), kind)),
  640. (end_datetime, truncate_to(end_datetime.time(), kind))
  641. ],
  642. lambda m: (m.start_datetime, m.truncated)
  643. )
  644. def test_datetime_to_time_kind(kind):
  645. self.assertQuerysetEqual(
  646. DTModel.objects.annotate(
  647. truncated=Trunc('start_datetime', kind, output_field=TimeField()),
  648. ).order_by('start_datetime'),
  649. [
  650. (start_datetime, truncate_to(start_datetime.time(), kind)),
  651. (end_datetime, truncate_to(end_datetime.time(), kind)),
  652. ],
  653. lambda m: (m.start_datetime, m.truncated),
  654. )
  655. test_date_kind('year')
  656. test_date_kind('quarter')
  657. test_date_kind('month')
  658. test_date_kind('week')
  659. test_date_kind('day')
  660. test_time_kind('hour')
  661. test_time_kind('minute')
  662. test_time_kind('second')
  663. test_datetime_kind('year')
  664. test_datetime_kind('quarter')
  665. test_datetime_kind('month')
  666. test_datetime_kind('week')
  667. test_datetime_kind('day')
  668. test_datetime_kind('hour')
  669. test_datetime_kind('minute')
  670. test_datetime_kind('second')
  671. test_datetime_to_time_kind('hour')
  672. test_datetime_to_time_kind('minute')
  673. test_datetime_to_time_kind('second')
  674. qs = DTModel.objects.filter(start_datetime__date=Trunc('start_datetime', 'day', output_field=DateField()))
  675. self.assertEqual(qs.count(), 2)
  676. def test_trunc_none(self):
  677. self.create_model(None, None)
  678. for t in (Trunc('start_datetime', 'year'), Trunc('start_date', 'year'), Trunc('start_time', 'hour')):
  679. with self.subTest(t):
  680. self.assertIsNone(DTModel.objects.annotate(truncated=t).first().truncated)
  681. def test_trunc_year_func(self):
  682. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  683. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'year')
  684. if settings.USE_TZ:
  685. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  686. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  687. self.create_model(start_datetime, end_datetime)
  688. self.create_model(end_datetime, start_datetime)
  689. self.assertQuerysetEqual(
  690. DTModel.objects.annotate(extracted=TruncYear('start_datetime')).order_by('start_datetime'),
  691. [
  692. (start_datetime, truncate_to(start_datetime, 'year')),
  693. (end_datetime, truncate_to(end_datetime, 'year')),
  694. ],
  695. lambda m: (m.start_datetime, m.extracted)
  696. )
  697. self.assertQuerysetEqual(
  698. DTModel.objects.annotate(extracted=TruncYear('start_date')).order_by('start_datetime'),
  699. [
  700. (start_datetime, truncate_to(start_datetime.date(), 'year')),
  701. (end_datetime, truncate_to(end_datetime.date(), 'year')),
  702. ],
  703. lambda m: (m.start_datetime, m.extracted)
  704. )
  705. self.assertEqual(DTModel.objects.filter(start_datetime=TruncYear('start_datetime')).count(), 1)
  706. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  707. list(DTModel.objects.annotate(truncated=TruncYear('start_time')))
  708. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  709. list(DTModel.objects.annotate(truncated=TruncYear('start_time', output_field=TimeField())))
  710. def test_trunc_quarter_func(self):
  711. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  712. end_datetime = truncate_to(datetime(2016, 10, 15, 14, 10, 50, 123), 'quarter')
  713. last_quarter_2015 = truncate_to(datetime(2015, 12, 31, 14, 10, 50, 123), 'quarter')
  714. first_quarter_2016 = truncate_to(datetime(2016, 1, 1, 14, 10, 50, 123), 'quarter')
  715. if settings.USE_TZ:
  716. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  717. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  718. last_quarter_2015 = timezone.make_aware(last_quarter_2015, is_dst=False)
  719. first_quarter_2016 = timezone.make_aware(first_quarter_2016, is_dst=False)
  720. self.create_model(start_datetime=start_datetime, end_datetime=end_datetime)
  721. self.create_model(start_datetime=end_datetime, end_datetime=start_datetime)
  722. self.create_model(start_datetime=last_quarter_2015, end_datetime=end_datetime)
  723. self.create_model(start_datetime=first_quarter_2016, end_datetime=end_datetime)
  724. self.assertQuerysetEqual(
  725. DTModel.objects.annotate(extracted=TruncQuarter('start_date')).order_by('start_datetime'),
  726. [
  727. (start_datetime, truncate_to(start_datetime.date(), 'quarter')),
  728. (last_quarter_2015, truncate_to(last_quarter_2015.date(), 'quarter')),
  729. (first_quarter_2016, truncate_to(first_quarter_2016.date(), 'quarter')),
  730. (end_datetime, truncate_to(end_datetime.date(), 'quarter')),
  731. ],
  732. lambda m: (m.start_datetime, m.extracted)
  733. )
  734. self.assertQuerysetEqual(
  735. DTModel.objects.annotate(extracted=TruncQuarter('start_datetime')).order_by('start_datetime'),
  736. [
  737. (start_datetime, truncate_to(start_datetime, 'quarter')),
  738. (last_quarter_2015, truncate_to(last_quarter_2015, 'quarter')),
  739. (first_quarter_2016, truncate_to(first_quarter_2016, 'quarter')),
  740. (end_datetime, truncate_to(end_datetime, 'quarter')),
  741. ],
  742. lambda m: (m.start_datetime, m.extracted)
  743. )
  744. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  745. list(DTModel.objects.annotate(truncated=TruncQuarter('start_time')))
  746. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  747. list(DTModel.objects.annotate(truncated=TruncQuarter('start_time', output_field=TimeField())))
  748. def test_trunc_month_func(self):
  749. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  750. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'month')
  751. if settings.USE_TZ:
  752. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  753. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  754. self.create_model(start_datetime, end_datetime)
  755. self.create_model(end_datetime, start_datetime)
  756. self.assertQuerysetEqual(
  757. DTModel.objects.annotate(extracted=TruncMonth('start_datetime')).order_by('start_datetime'),
  758. [
  759. (start_datetime, truncate_to(start_datetime, 'month')),
  760. (end_datetime, truncate_to(end_datetime, 'month')),
  761. ],
  762. lambda m: (m.start_datetime, m.extracted)
  763. )
  764. self.assertQuerysetEqual(
  765. DTModel.objects.annotate(extracted=TruncMonth('start_date')).order_by('start_datetime'),
  766. [
  767. (start_datetime, truncate_to(start_datetime.date(), 'month')),
  768. (end_datetime, truncate_to(end_datetime.date(), 'month')),
  769. ],
  770. lambda m: (m.start_datetime, m.extracted)
  771. )
  772. self.assertEqual(DTModel.objects.filter(start_datetime=TruncMonth('start_datetime')).count(), 1)
  773. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  774. list(DTModel.objects.annotate(truncated=TruncMonth('start_time')))
  775. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  776. list(DTModel.objects.annotate(truncated=TruncMonth('start_time', output_field=TimeField())))
  777. def test_trunc_week_func(self):
  778. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  779. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'week')
  780. if settings.USE_TZ:
  781. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  782. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  783. self.create_model(start_datetime, end_datetime)
  784. self.create_model(end_datetime, start_datetime)
  785. self.assertQuerysetEqual(
  786. DTModel.objects.annotate(extracted=TruncWeek('start_datetime')).order_by('start_datetime'),
  787. [
  788. (start_datetime, truncate_to(start_datetime, 'week')),
  789. (end_datetime, truncate_to(end_datetime, 'week')),
  790. ],
  791. lambda m: (m.start_datetime, m.extracted)
  792. )
  793. self.assertEqual(DTModel.objects.filter(start_datetime=TruncWeek('start_datetime')).count(), 1)
  794. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  795. list(DTModel.objects.annotate(truncated=TruncWeek('start_time')))
  796. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  797. list(DTModel.objects.annotate(truncated=TruncWeek('start_time', output_field=TimeField())))
  798. def test_trunc_date_func(self):
  799. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  800. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  801. if settings.USE_TZ:
  802. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  803. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  804. self.create_model(start_datetime, end_datetime)
  805. self.create_model(end_datetime, start_datetime)
  806. self.assertQuerysetEqual(
  807. DTModel.objects.annotate(extracted=TruncDate('start_datetime')).order_by('start_datetime'),
  808. [
  809. (start_datetime, start_datetime.date()),
  810. (end_datetime, end_datetime.date()),
  811. ],
  812. lambda m: (m.start_datetime, m.extracted)
  813. )
  814. self.assertEqual(DTModel.objects.filter(start_datetime__date=TruncDate('start_datetime')).count(), 2)
  815. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateField"):
  816. list(DTModel.objects.annotate(truncated=TruncDate('start_time')))
  817. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateField"):
  818. list(DTModel.objects.annotate(truncated=TruncDate('start_time', output_field=TimeField())))
  819. def test_trunc_date_none(self):
  820. self.create_model(None, None)
  821. self.assertIsNone(DTModel.objects.annotate(truncated=TruncDate('start_datetime')).first().truncated)
  822. def test_trunc_time_func(self):
  823. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  824. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  825. if settings.USE_TZ:
  826. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  827. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  828. self.create_model(start_datetime, end_datetime)
  829. self.create_model(end_datetime, start_datetime)
  830. self.assertQuerysetEqual(
  831. DTModel.objects.annotate(extracted=TruncTime('start_datetime')).order_by('start_datetime'),
  832. [
  833. (start_datetime, start_datetime.time()),
  834. (end_datetime, end_datetime.time()),
  835. ],
  836. lambda m: (m.start_datetime, m.extracted)
  837. )
  838. self.assertEqual(DTModel.objects.filter(start_datetime__time=TruncTime('start_datetime')).count(), 2)
  839. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to TimeField"):
  840. list(DTModel.objects.annotate(truncated=TruncTime('start_date')))
  841. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to TimeField"):
  842. list(DTModel.objects.annotate(truncated=TruncTime('start_date', output_field=DateField())))
  843. def test_trunc_time_none(self):
  844. self.create_model(None, None)
  845. self.assertIsNone(DTModel.objects.annotate(truncated=TruncTime('start_datetime')).first().truncated)
  846. def test_trunc_time_comparison(self):
  847. start_datetime = datetime(2015, 6, 15, 14, 30, 26) # 0 microseconds.
  848. end_datetime = datetime(2015, 6, 15, 14, 30, 26, 321)
  849. if settings.USE_TZ:
  850. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  851. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  852. self.create_model(start_datetime, end_datetime)
  853. self.assertIs(
  854. DTModel.objects.filter(
  855. start_datetime__time=start_datetime.time(),
  856. end_datetime__time=end_datetime.time(),
  857. ).exists(),
  858. True,
  859. )
  860. self.assertIs(
  861. DTModel.objects.annotate(
  862. extracted_start=TruncTime('start_datetime'),
  863. extracted_end=TruncTime('end_datetime'),
  864. ).filter(
  865. extracted_start=start_datetime.time(),
  866. extracted_end=end_datetime.time(),
  867. ).exists(),
  868. True,
  869. )
  870. def test_trunc_day_func(self):
  871. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  872. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'day')
  873. if settings.USE_TZ:
  874. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  875. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  876. self.create_model(start_datetime, end_datetime)
  877. self.create_model(end_datetime, start_datetime)
  878. self.assertQuerysetEqual(
  879. DTModel.objects.annotate(extracted=TruncDay('start_datetime')).order_by('start_datetime'),
  880. [
  881. (start_datetime, truncate_to(start_datetime, 'day')),
  882. (end_datetime, truncate_to(end_datetime, 'day')),
  883. ],
  884. lambda m: (m.start_datetime, m.extracted)
  885. )
  886. self.assertEqual(DTModel.objects.filter(start_datetime=TruncDay('start_datetime')).count(), 1)
  887. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  888. list(DTModel.objects.annotate(truncated=TruncDay('start_time')))
  889. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  890. list(DTModel.objects.annotate(truncated=TruncDay('start_time', output_field=TimeField())))
  891. def test_trunc_hour_func(self):
  892. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  893. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'hour')
  894. if settings.USE_TZ:
  895. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  896. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  897. self.create_model(start_datetime, end_datetime)
  898. self.create_model(end_datetime, start_datetime)
  899. self.assertQuerysetEqual(
  900. DTModel.objects.annotate(extracted=TruncHour('start_datetime')).order_by('start_datetime'),
  901. [
  902. (start_datetime, truncate_to(start_datetime, 'hour')),
  903. (end_datetime, truncate_to(end_datetime, 'hour')),
  904. ],
  905. lambda m: (m.start_datetime, m.extracted)
  906. )
  907. self.assertQuerysetEqual(
  908. DTModel.objects.annotate(extracted=TruncHour('start_time')).order_by('start_datetime'),
  909. [
  910. (start_datetime, truncate_to(start_datetime.time(), 'hour')),
  911. (end_datetime, truncate_to(end_datetime.time(), 'hour')),
  912. ],
  913. lambda m: (m.start_datetime, m.extracted)
  914. )
  915. self.assertEqual(DTModel.objects.filter(start_datetime=TruncHour('start_datetime')).count(), 1)
  916. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  917. list(DTModel.objects.annotate(truncated=TruncHour('start_date')))
  918. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  919. list(DTModel.objects.annotate(truncated=TruncHour('start_date', output_field=DateField())))
  920. def test_trunc_minute_func(self):
  921. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  922. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'minute')
  923. if settings.USE_TZ:
  924. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  925. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  926. self.create_model(start_datetime, end_datetime)
  927. self.create_model(end_datetime, start_datetime)
  928. self.assertQuerysetEqual(
  929. DTModel.objects.annotate(extracted=TruncMinute('start_datetime')).order_by('start_datetime'),
  930. [
  931. (start_datetime, truncate_to(start_datetime, 'minute')),
  932. (end_datetime, truncate_to(end_datetime, 'minute')),
  933. ],
  934. lambda m: (m.start_datetime, m.extracted)
  935. )
  936. self.assertQuerysetEqual(
  937. DTModel.objects.annotate(extracted=TruncMinute('start_time')).order_by('start_datetime'),
  938. [
  939. (start_datetime, truncate_to(start_datetime.time(), 'minute')),
  940. (end_datetime, truncate_to(end_datetime.time(), 'minute')),
  941. ],
  942. lambda m: (m.start_datetime, m.extracted)
  943. )
  944. self.assertEqual(DTModel.objects.filter(start_datetime=TruncMinute('start_datetime')).count(), 1)
  945. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  946. list(DTModel.objects.annotate(truncated=TruncMinute('start_date')))
  947. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  948. list(DTModel.objects.annotate(truncated=TruncMinute('start_date', output_field=DateField())))
  949. def test_trunc_second_func(self):
  950. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  951. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'second')
  952. if settings.USE_TZ:
  953. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  954. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  955. self.create_model(start_datetime, end_datetime)
  956. self.create_model(end_datetime, start_datetime)
  957. self.assertQuerysetEqual(
  958. DTModel.objects.annotate(extracted=TruncSecond('start_datetime')).order_by('start_datetime'),
  959. [
  960. (start_datetime, truncate_to(start_datetime, 'second')),
  961. (end_datetime, truncate_to(end_datetime, 'second'))
  962. ],
  963. lambda m: (m.start_datetime, m.extracted)
  964. )
  965. self.assertQuerysetEqual(
  966. DTModel.objects.annotate(extracted=TruncSecond('start_time')).order_by('start_datetime'),
  967. [
  968. (start_datetime, truncate_to(start_datetime.time(), 'second')),
  969. (end_datetime, truncate_to(end_datetime.time(), 'second'))
  970. ],
  971. lambda m: (m.start_datetime, m.extracted)
  972. )
  973. self.assertEqual(DTModel.objects.filter(start_datetime=TruncSecond('start_datetime')).count(), 1)
  974. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  975. list(DTModel.objects.annotate(truncated=TruncSecond('start_date')))
  976. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  977. list(DTModel.objects.annotate(truncated=TruncSecond('start_date', output_field=DateField())))
  978. def test_trunc_subquery_with_parameters(self):
  979. author_1 = Author.objects.create(name='J. R. R. Tolkien')
  980. author_2 = Author.objects.create(name='G. R. R. Martin')
  981. fan_since_1 = datetime(2016, 2, 3, 15, 0, 0)
  982. fan_since_2 = datetime(2015, 2, 3, 15, 0, 0)
  983. fan_since_3 = datetime(2017, 2, 3, 15, 0, 0)
  984. if settings.USE_TZ:
  985. fan_since_1 = timezone.make_aware(fan_since_1, is_dst=False)
  986. fan_since_2 = timezone.make_aware(fan_since_2, is_dst=False)
  987. fan_since_3 = timezone.make_aware(fan_since_3, is_dst=False)
  988. Fan.objects.create(author=author_1, name='Tom', fan_since=fan_since_1)
  989. Fan.objects.create(author=author_1, name='Emma', fan_since=fan_since_2)
  990. Fan.objects.create(author=author_2, name='Isabella', fan_since=fan_since_3)
  991. inner = Fan.objects.filter(
  992. author=OuterRef('pk'),
  993. name__in=('Emma', 'Isabella', 'Tom')
  994. ).values('author').annotate(newest_fan=Max('fan_since')).values('newest_fan')
  995. outer = Author.objects.annotate(
  996. newest_fan_year=TruncYear(Subquery(inner, output_field=DateTimeField()))
  997. )
  998. tz = timezone.utc if settings.USE_TZ else None
  999. self.assertSequenceEqual(
  1000. outer.order_by('name').values('name', 'newest_fan_year'),
  1001. [
  1002. {'name': 'G. R. R. Martin', 'newest_fan_year': datetime(2017, 1, 1, 0, 0, tzinfo=tz)},
  1003. {'name': 'J. R. R. Tolkien', 'newest_fan_year': datetime(2016, 1, 1, 0, 0, tzinfo=tz)},
  1004. ]
  1005. )
  1006. @override_settings(USE_TZ=True, TIME_ZONE='UTC')
  1007. class DateFunctionWithTimeZoneTests(DateFunctionTests):
  1008. def get_timezones(self, key):
  1009. for constructor in ZONE_CONSTRUCTORS:
  1010. yield constructor(key)
  1011. def test_extract_func_with_timezone(self):
  1012. start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
  1013. end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
  1014. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  1015. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  1016. self.create_model(start_datetime, end_datetime)
  1017. delta_tzinfo_pos = datetime_timezone(timedelta(hours=5))
  1018. delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17))
  1019. for melb in self.get_timezones('Australia/Melbourne'):
  1020. with self.subTest(repr(melb)):
  1021. qs = DTModel.objects.annotate(
  1022. day=Extract('start_datetime', 'day'),
  1023. day_melb=Extract('start_datetime', 'day', tzinfo=melb),
  1024. week=Extract('start_datetime', 'week', tzinfo=melb),
  1025. isoyear=ExtractIsoYear('start_datetime', tzinfo=melb),
  1026. weekday=ExtractWeekDay('start_datetime'),
  1027. weekday_melb=ExtractWeekDay('start_datetime', tzinfo=melb),
  1028. isoweekday=ExtractIsoWeekDay('start_datetime'),
  1029. isoweekday_melb=ExtractIsoWeekDay('start_datetime', tzinfo=melb),
  1030. quarter=ExtractQuarter('start_datetime', tzinfo=melb),
  1031. hour=ExtractHour('start_datetime'),
  1032. hour_melb=ExtractHour('start_datetime', tzinfo=melb),
  1033. hour_with_delta_pos=ExtractHour('start_datetime', tzinfo=delta_tzinfo_pos),
  1034. hour_with_delta_neg=ExtractHour('start_datetime', tzinfo=delta_tzinfo_neg),
  1035. minute_with_delta_neg=ExtractMinute('start_datetime', tzinfo=delta_tzinfo_neg),
  1036. ).order_by('start_datetime')
  1037. utc_model = qs.get()
  1038. self.assertEqual(utc_model.day, 15)
  1039. self.assertEqual(utc_model.day_melb, 16)
  1040. self.assertEqual(utc_model.week, 25)
  1041. self.assertEqual(utc_model.isoyear, 2015)
  1042. self.assertEqual(utc_model.weekday, 2)
  1043. self.assertEqual(utc_model.weekday_melb, 3)
  1044. self.assertEqual(utc_model.isoweekday, 1)
  1045. self.assertEqual(utc_model.isoweekday_melb, 2)
  1046. self.assertEqual(utc_model.quarter, 2)
  1047. self.assertEqual(utc_model.hour, 23)
  1048. self.assertEqual(utc_model.hour_melb, 9)
  1049. self.assertEqual(utc_model.hour_with_delta_pos, 4)
  1050. self.assertEqual(utc_model.hour_with_delta_neg, 18)
  1051. self.assertEqual(utc_model.minute_with_delta_neg, 47)
  1052. with timezone.override(melb):
  1053. melb_model = qs.get()
  1054. self.assertEqual(melb_model.day, 16)
  1055. self.assertEqual(melb_model.day_melb, 16)
  1056. self.assertEqual(melb_model.week, 25)
  1057. self.assertEqual(melb_model.isoyear, 2015)
  1058. self.assertEqual(melb_model.weekday, 3)
  1059. self.assertEqual(melb_model.isoweekday, 2)
  1060. self.assertEqual(melb_model.quarter, 2)
  1061. self.assertEqual(melb_model.weekday_melb, 3)
  1062. self.assertEqual(melb_model.isoweekday_melb, 2)
  1063. self.assertEqual(melb_model.hour, 9)
  1064. self.assertEqual(melb_model.hour_melb, 9)
  1065. def test_extract_func_explicit_timezone_priority(self):
  1066. start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
  1067. end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
  1068. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  1069. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  1070. self.create_model(start_datetime, end_datetime)
  1071. for melb in self.get_timezones('Australia/Melbourne'):
  1072. with self.subTest(repr(melb)):
  1073. with timezone.override(melb):
  1074. model = DTModel.objects.annotate(
  1075. day_melb=Extract('start_datetime', 'day'),
  1076. day_utc=Extract('start_datetime', 'day', tzinfo=timezone.utc),
  1077. ).order_by('start_datetime').get()
  1078. self.assertEqual(model.day_melb, 16)
  1079. self.assertEqual(model.day_utc, 15)
  1080. def test_extract_invalid_field_with_timezone(self):
  1081. for melb in self.get_timezones('Australia/Melbourne'):
  1082. with self.subTest(repr(melb)):
  1083. msg = 'tzinfo can only be used with DateTimeField.'
  1084. with self.assertRaisesMessage(ValueError, msg):
  1085. DTModel.objects.annotate(
  1086. day_melb=Extract('start_date', 'day', tzinfo=melb),
  1087. ).get()
  1088. with self.assertRaisesMessage(ValueError, msg):
  1089. DTModel.objects.annotate(
  1090. hour_melb=Extract('start_time', 'hour', tzinfo=melb),
  1091. ).get()
  1092. def test_trunc_timezone_applied_before_truncation(self):
  1093. start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
  1094. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  1095. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  1096. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  1097. self.create_model(start_datetime, end_datetime)
  1098. for melb, pacific in zip(
  1099. self.get_timezones('Australia/Melbourne'), self.get_timezones('America/Los_Angeles')
  1100. ):
  1101. with self.subTest((repr(melb), repr(pacific))):
  1102. model = DTModel.objects.annotate(
  1103. melb_year=TruncYear('start_datetime', tzinfo=melb),
  1104. pacific_year=TruncYear('start_datetime', tzinfo=pacific),
  1105. melb_date=TruncDate('start_datetime', tzinfo=melb),
  1106. pacific_date=TruncDate('start_datetime', tzinfo=pacific),
  1107. melb_time=TruncTime('start_datetime', tzinfo=melb),
  1108. pacific_time=TruncTime('start_datetime', tzinfo=pacific),
  1109. ).order_by('start_datetime').get()
  1110. melb_start_datetime = start_datetime.astimezone(melb)
  1111. pacific_start_datetime = start_datetime.astimezone(pacific)
  1112. self.assertEqual(model.start_datetime, start_datetime)
  1113. self.assertEqual(model.melb_year, truncate_to(start_datetime, 'year', melb))
  1114. self.assertEqual(model.pacific_year, truncate_to(start_datetime, 'year', pacific))
  1115. self.assertEqual(model.start_datetime.year, 2016)
  1116. self.assertEqual(model.melb_year.year, 2016)
  1117. self.assertEqual(model.pacific_year.year, 2015)
  1118. self.assertEqual(model.melb_date, melb_start_datetime.date())
  1119. self.assertEqual(model.pacific_date, pacific_start_datetime.date())
  1120. self.assertEqual(model.melb_time, melb_start_datetime.time())
  1121. self.assertEqual(model.pacific_time, pacific_start_datetime.time())
  1122. def test_trunc_ambiguous_and_invalid_times(self):
  1123. sao = pytz.timezone('America/Sao_Paulo')
  1124. utc = timezone.utc
  1125. start_datetime = datetime(2016, 10, 16, 13, tzinfo=utc)
  1126. end_datetime = datetime(2016, 2, 21, 1, tzinfo=utc)
  1127. self.create_model(start_datetime, end_datetime)
  1128. with timezone.override(sao):
  1129. with self.assertRaisesMessage(pytz.NonExistentTimeError, '2016-10-16 00:00:00'):
  1130. model = DTModel.objects.annotate(truncated_start=TruncDay('start_datetime')).get()
  1131. with self.assertRaisesMessage(pytz.AmbiguousTimeError, '2016-02-20 23:00:00'):
  1132. model = DTModel.objects.annotate(truncated_end=TruncHour('end_datetime')).get()
  1133. model = DTModel.objects.annotate(
  1134. truncated_start=TruncDay('start_datetime', is_dst=False),
  1135. truncated_end=TruncHour('end_datetime', is_dst=False),
  1136. ).get()
  1137. self.assertEqual(model.truncated_start.dst(), timedelta(0))
  1138. self.assertEqual(model.truncated_end.dst(), timedelta(0))
  1139. model = DTModel.objects.annotate(
  1140. truncated_start=TruncDay('start_datetime', is_dst=True),
  1141. truncated_end=TruncHour('end_datetime', is_dst=True),
  1142. ).get()
  1143. self.assertEqual(model.truncated_start.dst(), timedelta(0, 3600))
  1144. self.assertEqual(model.truncated_end.dst(), timedelta(0, 3600))
  1145. def test_trunc_func_with_timezone(self):
  1146. """
  1147. If the truncated datetime transitions to a different offset (daylight
  1148. saving) then the returned value will have that new timezone/offset.
  1149. """
  1150. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1151. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  1152. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  1153. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  1154. self.create_model(start_datetime, end_datetime)
  1155. self.create_model(end_datetime, start_datetime)
  1156. for melb in self.get_timezones('Australia/Melbourne'):
  1157. with self.subTest(repr(melb)):
  1158. def test_datetime_kind(kind):
  1159. self.assertQuerysetEqual(
  1160. DTModel.objects.annotate(
  1161. truncated=Trunc(
  1162. 'start_datetime', kind, output_field=DateTimeField(), tzinfo=melb
  1163. )
  1164. ).order_by('start_datetime'),
  1165. [
  1166. (start_datetime, truncate_to(start_datetime.astimezone(melb), kind, melb)),
  1167. (end_datetime, truncate_to(end_datetime.astimezone(melb), kind, melb))
  1168. ],
  1169. lambda m: (m.start_datetime, m.truncated)
  1170. )
  1171. def test_datetime_to_date_kind(kind):
  1172. self.assertQuerysetEqual(
  1173. DTModel.objects.annotate(
  1174. truncated=Trunc(
  1175. 'start_datetime',
  1176. kind,
  1177. output_field=DateField(),
  1178. tzinfo=melb,
  1179. ),
  1180. ).order_by('start_datetime'),
  1181. [
  1182. (
  1183. start_datetime,
  1184. truncate_to(start_datetime.astimezone(melb).date(), kind),
  1185. ),
  1186. (
  1187. end_datetime,
  1188. truncate_to(end_datetime.astimezone(melb).date(), kind),
  1189. ),
  1190. ],
  1191. lambda m: (m.start_datetime, m.truncated),
  1192. )
  1193. def test_datetime_to_time_kind(kind):
  1194. self.assertQuerysetEqual(
  1195. DTModel.objects.annotate(
  1196. truncated=Trunc(
  1197. 'start_datetime',
  1198. kind,
  1199. output_field=TimeField(),
  1200. tzinfo=melb,
  1201. )
  1202. ).order_by('start_datetime'),
  1203. [
  1204. (
  1205. start_datetime,
  1206. truncate_to(start_datetime.astimezone(melb).time(), kind),
  1207. ),
  1208. (
  1209. end_datetime,
  1210. truncate_to(end_datetime.astimezone(melb).time(), kind),
  1211. ),
  1212. ],
  1213. lambda m: (m.start_datetime, m.truncated),
  1214. )
  1215. test_datetime_to_date_kind('year')
  1216. test_datetime_to_date_kind('quarter')
  1217. test_datetime_to_date_kind('month')
  1218. test_datetime_to_date_kind('week')
  1219. test_datetime_to_date_kind('day')
  1220. test_datetime_to_time_kind('hour')
  1221. test_datetime_to_time_kind('minute')
  1222. test_datetime_to_time_kind('second')
  1223. test_datetime_kind('year')
  1224. test_datetime_kind('quarter')
  1225. test_datetime_kind('month')
  1226. test_datetime_kind('week')
  1227. test_datetime_kind('day')
  1228. test_datetime_kind('hour')
  1229. test_datetime_kind('minute')
  1230. test_datetime_kind('second')
  1231. qs = DTModel.objects.filter(
  1232. start_datetime__date=Trunc('start_datetime', 'day', output_field=DateField())
  1233. )
  1234. self.assertEqual(qs.count(), 2)
  1235. def test_trunc_invalid_field_with_timezone(self):
  1236. for melb in self.get_timezones('Australia/Melbourne'):
  1237. with self.subTest(repr(melb)):
  1238. msg = 'tzinfo can only be used with DateTimeField.'
  1239. with self.assertRaisesMessage(ValueError, msg):
  1240. DTModel.objects.annotate(
  1241. day_melb=Trunc('start_date', 'day', tzinfo=melb),
  1242. ).get()
  1243. with self.assertRaisesMessage(ValueError, msg):
  1244. DTModel.objects.annotate(
  1245. hour_melb=Trunc('start_time', 'hour', tzinfo=melb),
  1246. ).get()