test_extract_trunc.py 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353
  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_no_microseconds(self):
  847. start_datetime = datetime(2015, 6, 15, 14, 30, 26)
  848. if settings.USE_TZ:
  849. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  850. self.create_model(start_datetime, None)
  851. self.assertIs(
  852. DTModel.objects.filter(start_datetime__time=start_datetime.time()).exists(),
  853. True,
  854. )
  855. self.assertIs(
  856. DTModel.objects.annotate(
  857. extracted=TruncTime('start_datetime'),
  858. ).filter(extracted=start_datetime.time()).exists(),
  859. True,
  860. )
  861. def test_trunc_day_func(self):
  862. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  863. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'day')
  864. if settings.USE_TZ:
  865. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  866. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  867. self.create_model(start_datetime, end_datetime)
  868. self.create_model(end_datetime, start_datetime)
  869. self.assertQuerysetEqual(
  870. DTModel.objects.annotate(extracted=TruncDay('start_datetime')).order_by('start_datetime'),
  871. [
  872. (start_datetime, truncate_to(start_datetime, 'day')),
  873. (end_datetime, truncate_to(end_datetime, 'day')),
  874. ],
  875. lambda m: (m.start_datetime, m.extracted)
  876. )
  877. self.assertEqual(DTModel.objects.filter(start_datetime=TruncDay('start_datetime')).count(), 1)
  878. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  879. list(DTModel.objects.annotate(truncated=TruncDay('start_time')))
  880. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  881. list(DTModel.objects.annotate(truncated=TruncDay('start_time', output_field=TimeField())))
  882. def test_trunc_hour_func(self):
  883. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  884. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'hour')
  885. if settings.USE_TZ:
  886. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  887. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  888. self.create_model(start_datetime, end_datetime)
  889. self.create_model(end_datetime, start_datetime)
  890. self.assertQuerysetEqual(
  891. DTModel.objects.annotate(extracted=TruncHour('start_datetime')).order_by('start_datetime'),
  892. [
  893. (start_datetime, truncate_to(start_datetime, 'hour')),
  894. (end_datetime, truncate_to(end_datetime, 'hour')),
  895. ],
  896. lambda m: (m.start_datetime, m.extracted)
  897. )
  898. self.assertQuerysetEqual(
  899. DTModel.objects.annotate(extracted=TruncHour('start_time')).order_by('start_datetime'),
  900. [
  901. (start_datetime, truncate_to(start_datetime.time(), 'hour')),
  902. (end_datetime, truncate_to(end_datetime.time(), 'hour')),
  903. ],
  904. lambda m: (m.start_datetime, m.extracted)
  905. )
  906. self.assertEqual(DTModel.objects.filter(start_datetime=TruncHour('start_datetime')).count(), 1)
  907. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  908. list(DTModel.objects.annotate(truncated=TruncHour('start_date')))
  909. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  910. list(DTModel.objects.annotate(truncated=TruncHour('start_date', output_field=DateField())))
  911. def test_trunc_minute_func(self):
  912. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  913. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'minute')
  914. if settings.USE_TZ:
  915. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  916. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  917. self.create_model(start_datetime, end_datetime)
  918. self.create_model(end_datetime, start_datetime)
  919. self.assertQuerysetEqual(
  920. DTModel.objects.annotate(extracted=TruncMinute('start_datetime')).order_by('start_datetime'),
  921. [
  922. (start_datetime, truncate_to(start_datetime, 'minute')),
  923. (end_datetime, truncate_to(end_datetime, 'minute')),
  924. ],
  925. lambda m: (m.start_datetime, m.extracted)
  926. )
  927. self.assertQuerysetEqual(
  928. DTModel.objects.annotate(extracted=TruncMinute('start_time')).order_by('start_datetime'),
  929. [
  930. (start_datetime, truncate_to(start_datetime.time(), 'minute')),
  931. (end_datetime, truncate_to(end_datetime.time(), 'minute')),
  932. ],
  933. lambda m: (m.start_datetime, m.extracted)
  934. )
  935. self.assertEqual(DTModel.objects.filter(start_datetime=TruncMinute('start_datetime')).count(), 1)
  936. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  937. list(DTModel.objects.annotate(truncated=TruncMinute('start_date')))
  938. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  939. list(DTModel.objects.annotate(truncated=TruncMinute('start_date', output_field=DateField())))
  940. def test_trunc_second_func(self):
  941. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  942. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'second')
  943. if settings.USE_TZ:
  944. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  945. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  946. self.create_model(start_datetime, end_datetime)
  947. self.create_model(end_datetime, start_datetime)
  948. self.assertQuerysetEqual(
  949. DTModel.objects.annotate(extracted=TruncSecond('start_datetime')).order_by('start_datetime'),
  950. [
  951. (start_datetime, truncate_to(start_datetime, 'second')),
  952. (end_datetime, truncate_to(end_datetime, 'second'))
  953. ],
  954. lambda m: (m.start_datetime, m.extracted)
  955. )
  956. self.assertQuerysetEqual(
  957. DTModel.objects.annotate(extracted=TruncSecond('start_time')).order_by('start_datetime'),
  958. [
  959. (start_datetime, truncate_to(start_datetime.time(), 'second')),
  960. (end_datetime, truncate_to(end_datetime.time(), 'second'))
  961. ],
  962. lambda m: (m.start_datetime, m.extracted)
  963. )
  964. self.assertEqual(DTModel.objects.filter(start_datetime=TruncSecond('start_datetime')).count(), 1)
  965. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  966. list(DTModel.objects.annotate(truncated=TruncSecond('start_date')))
  967. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  968. list(DTModel.objects.annotate(truncated=TruncSecond('start_date', output_field=DateField())))
  969. def test_trunc_subquery_with_parameters(self):
  970. author_1 = Author.objects.create(name='J. R. R. Tolkien')
  971. author_2 = Author.objects.create(name='G. R. R. Martin')
  972. fan_since_1 = datetime(2016, 2, 3, 15, 0, 0)
  973. fan_since_2 = datetime(2015, 2, 3, 15, 0, 0)
  974. fan_since_3 = datetime(2017, 2, 3, 15, 0, 0)
  975. if settings.USE_TZ:
  976. fan_since_1 = timezone.make_aware(fan_since_1, is_dst=False)
  977. fan_since_2 = timezone.make_aware(fan_since_2, is_dst=False)
  978. fan_since_3 = timezone.make_aware(fan_since_3, is_dst=False)
  979. Fan.objects.create(author=author_1, name='Tom', fan_since=fan_since_1)
  980. Fan.objects.create(author=author_1, name='Emma', fan_since=fan_since_2)
  981. Fan.objects.create(author=author_2, name='Isabella', fan_since=fan_since_3)
  982. inner = Fan.objects.filter(
  983. author=OuterRef('pk'),
  984. name__in=('Emma', 'Isabella', 'Tom')
  985. ).values('author').annotate(newest_fan=Max('fan_since')).values('newest_fan')
  986. outer = Author.objects.annotate(
  987. newest_fan_year=TruncYear(Subquery(inner, output_field=DateTimeField()))
  988. )
  989. tz = timezone.utc if settings.USE_TZ else None
  990. self.assertSequenceEqual(
  991. outer.order_by('name').values('name', 'newest_fan_year'),
  992. [
  993. {'name': 'G. R. R. Martin', 'newest_fan_year': datetime(2017, 1, 1, 0, 0, tzinfo=tz)},
  994. {'name': 'J. R. R. Tolkien', 'newest_fan_year': datetime(2016, 1, 1, 0, 0, tzinfo=tz)},
  995. ]
  996. )
  997. @override_settings(USE_TZ=True, TIME_ZONE='UTC')
  998. class DateFunctionWithTimeZoneTests(DateFunctionTests):
  999. def get_timezones(self, key):
  1000. for constructor in ZONE_CONSTRUCTORS:
  1001. yield constructor(key)
  1002. def test_extract_func_with_timezone(self):
  1003. start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
  1004. end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
  1005. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  1006. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  1007. self.create_model(start_datetime, end_datetime)
  1008. delta_tzinfo_pos = datetime_timezone(timedelta(hours=5))
  1009. delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17))
  1010. for melb in self.get_timezones('Australia/Melbourne'):
  1011. with self.subTest(repr(melb)):
  1012. qs = DTModel.objects.annotate(
  1013. day=Extract('start_datetime', 'day'),
  1014. day_melb=Extract('start_datetime', 'day', tzinfo=melb),
  1015. week=Extract('start_datetime', 'week', tzinfo=melb),
  1016. isoyear=ExtractIsoYear('start_datetime', tzinfo=melb),
  1017. weekday=ExtractWeekDay('start_datetime'),
  1018. weekday_melb=ExtractWeekDay('start_datetime', tzinfo=melb),
  1019. isoweekday=ExtractIsoWeekDay('start_datetime'),
  1020. isoweekday_melb=ExtractIsoWeekDay('start_datetime', tzinfo=melb),
  1021. quarter=ExtractQuarter('start_datetime', tzinfo=melb),
  1022. hour=ExtractHour('start_datetime'),
  1023. hour_melb=ExtractHour('start_datetime', tzinfo=melb),
  1024. hour_with_delta_pos=ExtractHour('start_datetime', tzinfo=delta_tzinfo_pos),
  1025. hour_with_delta_neg=ExtractHour('start_datetime', tzinfo=delta_tzinfo_neg),
  1026. minute_with_delta_neg=ExtractMinute('start_datetime', tzinfo=delta_tzinfo_neg),
  1027. ).order_by('start_datetime')
  1028. utc_model = qs.get()
  1029. self.assertEqual(utc_model.day, 15)
  1030. self.assertEqual(utc_model.day_melb, 16)
  1031. self.assertEqual(utc_model.week, 25)
  1032. self.assertEqual(utc_model.isoyear, 2015)
  1033. self.assertEqual(utc_model.weekday, 2)
  1034. self.assertEqual(utc_model.weekday_melb, 3)
  1035. self.assertEqual(utc_model.isoweekday, 1)
  1036. self.assertEqual(utc_model.isoweekday_melb, 2)
  1037. self.assertEqual(utc_model.quarter, 2)
  1038. self.assertEqual(utc_model.hour, 23)
  1039. self.assertEqual(utc_model.hour_melb, 9)
  1040. self.assertEqual(utc_model.hour_with_delta_pos, 4)
  1041. self.assertEqual(utc_model.hour_with_delta_neg, 18)
  1042. self.assertEqual(utc_model.minute_with_delta_neg, 47)
  1043. with timezone.override(melb):
  1044. melb_model = qs.get()
  1045. self.assertEqual(melb_model.day, 16)
  1046. self.assertEqual(melb_model.day_melb, 16)
  1047. self.assertEqual(melb_model.week, 25)
  1048. self.assertEqual(melb_model.isoyear, 2015)
  1049. self.assertEqual(melb_model.weekday, 3)
  1050. self.assertEqual(melb_model.isoweekday, 2)
  1051. self.assertEqual(melb_model.quarter, 2)
  1052. self.assertEqual(melb_model.weekday_melb, 3)
  1053. self.assertEqual(melb_model.isoweekday_melb, 2)
  1054. self.assertEqual(melb_model.hour, 9)
  1055. self.assertEqual(melb_model.hour_melb, 9)
  1056. def test_extract_func_explicit_timezone_priority(self):
  1057. start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
  1058. end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
  1059. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  1060. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  1061. self.create_model(start_datetime, end_datetime)
  1062. for melb in self.get_timezones('Australia/Melbourne'):
  1063. with self.subTest(repr(melb)):
  1064. with timezone.override(melb):
  1065. model = DTModel.objects.annotate(
  1066. day_melb=Extract('start_datetime', 'day'),
  1067. day_utc=Extract('start_datetime', 'day', tzinfo=timezone.utc),
  1068. ).order_by('start_datetime').get()
  1069. self.assertEqual(model.day_melb, 16)
  1070. self.assertEqual(model.day_utc, 15)
  1071. def test_extract_invalid_field_with_timezone(self):
  1072. for melb in self.get_timezones('Australia/Melbourne'):
  1073. with self.subTest(repr(melb)):
  1074. msg = 'tzinfo can only be used with DateTimeField.'
  1075. with self.assertRaisesMessage(ValueError, msg):
  1076. DTModel.objects.annotate(
  1077. day_melb=Extract('start_date', 'day', tzinfo=melb),
  1078. ).get()
  1079. with self.assertRaisesMessage(ValueError, msg):
  1080. DTModel.objects.annotate(
  1081. hour_melb=Extract('start_time', 'hour', tzinfo=melb),
  1082. ).get()
  1083. def test_trunc_timezone_applied_before_truncation(self):
  1084. start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
  1085. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  1086. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  1087. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  1088. self.create_model(start_datetime, end_datetime)
  1089. for melb, pacific in zip(
  1090. self.get_timezones('Australia/Melbourne'), self.get_timezones('America/Los_Angeles')
  1091. ):
  1092. with self.subTest((repr(melb), repr(pacific))):
  1093. model = DTModel.objects.annotate(
  1094. melb_year=TruncYear('start_datetime', tzinfo=melb),
  1095. pacific_year=TruncYear('start_datetime', tzinfo=pacific),
  1096. melb_date=TruncDate('start_datetime', tzinfo=melb),
  1097. pacific_date=TruncDate('start_datetime', tzinfo=pacific),
  1098. melb_time=TruncTime('start_datetime', tzinfo=melb),
  1099. pacific_time=TruncTime('start_datetime', tzinfo=pacific),
  1100. ).order_by('start_datetime').get()
  1101. melb_start_datetime = start_datetime.astimezone(melb)
  1102. pacific_start_datetime = start_datetime.astimezone(pacific)
  1103. self.assertEqual(model.start_datetime, start_datetime)
  1104. self.assertEqual(model.melb_year, truncate_to(start_datetime, 'year', melb))
  1105. self.assertEqual(model.pacific_year, truncate_to(start_datetime, 'year', pacific))
  1106. self.assertEqual(model.start_datetime.year, 2016)
  1107. self.assertEqual(model.melb_year.year, 2016)
  1108. self.assertEqual(model.pacific_year.year, 2015)
  1109. self.assertEqual(model.melb_date, melb_start_datetime.date())
  1110. self.assertEqual(model.pacific_date, pacific_start_datetime.date())
  1111. self.assertEqual(model.melb_time, melb_start_datetime.time())
  1112. self.assertEqual(model.pacific_time, pacific_start_datetime.time())
  1113. def test_trunc_ambiguous_and_invalid_times(self):
  1114. sao = pytz.timezone('America/Sao_Paulo')
  1115. utc = timezone.utc
  1116. start_datetime = datetime(2016, 10, 16, 13, tzinfo=utc)
  1117. end_datetime = datetime(2016, 2, 21, 1, tzinfo=utc)
  1118. self.create_model(start_datetime, end_datetime)
  1119. with timezone.override(sao):
  1120. with self.assertRaisesMessage(pytz.NonExistentTimeError, '2016-10-16 00:00:00'):
  1121. model = DTModel.objects.annotate(truncated_start=TruncDay('start_datetime')).get()
  1122. with self.assertRaisesMessage(pytz.AmbiguousTimeError, '2016-02-20 23:00:00'):
  1123. model = DTModel.objects.annotate(truncated_end=TruncHour('end_datetime')).get()
  1124. model = DTModel.objects.annotate(
  1125. truncated_start=TruncDay('start_datetime', is_dst=False),
  1126. truncated_end=TruncHour('end_datetime', is_dst=False),
  1127. ).get()
  1128. self.assertEqual(model.truncated_start.dst(), timedelta(0))
  1129. self.assertEqual(model.truncated_end.dst(), timedelta(0))
  1130. model = DTModel.objects.annotate(
  1131. truncated_start=TruncDay('start_datetime', is_dst=True),
  1132. truncated_end=TruncHour('end_datetime', is_dst=True),
  1133. ).get()
  1134. self.assertEqual(model.truncated_start.dst(), timedelta(0, 3600))
  1135. self.assertEqual(model.truncated_end.dst(), timedelta(0, 3600))
  1136. def test_trunc_func_with_timezone(self):
  1137. """
  1138. If the truncated datetime transitions to a different offset (daylight
  1139. saving) then the returned value will have that new timezone/offset.
  1140. """
  1141. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1142. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  1143. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  1144. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  1145. self.create_model(start_datetime, end_datetime)
  1146. self.create_model(end_datetime, start_datetime)
  1147. for melb in self.get_timezones('Australia/Melbourne'):
  1148. with self.subTest(repr(melb)):
  1149. def test_datetime_kind(kind):
  1150. self.assertQuerysetEqual(
  1151. DTModel.objects.annotate(
  1152. truncated=Trunc(
  1153. 'start_datetime', kind, output_field=DateTimeField(), tzinfo=melb
  1154. )
  1155. ).order_by('start_datetime'),
  1156. [
  1157. (start_datetime, truncate_to(start_datetime.astimezone(melb), kind, melb)),
  1158. (end_datetime, truncate_to(end_datetime.astimezone(melb), kind, melb))
  1159. ],
  1160. lambda m: (m.start_datetime, m.truncated)
  1161. )
  1162. def test_datetime_to_date_kind(kind):
  1163. self.assertQuerysetEqual(
  1164. DTModel.objects.annotate(
  1165. truncated=Trunc(
  1166. 'start_datetime',
  1167. kind,
  1168. output_field=DateField(),
  1169. tzinfo=melb,
  1170. ),
  1171. ).order_by('start_datetime'),
  1172. [
  1173. (
  1174. start_datetime,
  1175. truncate_to(start_datetime.astimezone(melb).date(), kind),
  1176. ),
  1177. (
  1178. end_datetime,
  1179. truncate_to(end_datetime.astimezone(melb).date(), kind),
  1180. ),
  1181. ],
  1182. lambda m: (m.start_datetime, m.truncated),
  1183. )
  1184. def test_datetime_to_time_kind(kind):
  1185. self.assertQuerysetEqual(
  1186. DTModel.objects.annotate(
  1187. truncated=Trunc(
  1188. 'start_datetime',
  1189. kind,
  1190. output_field=TimeField(),
  1191. tzinfo=melb,
  1192. )
  1193. ).order_by('start_datetime'),
  1194. [
  1195. (
  1196. start_datetime,
  1197. truncate_to(start_datetime.astimezone(melb).time(), kind),
  1198. ),
  1199. (
  1200. end_datetime,
  1201. truncate_to(end_datetime.astimezone(melb).time(), kind),
  1202. ),
  1203. ],
  1204. lambda m: (m.start_datetime, m.truncated),
  1205. )
  1206. test_datetime_to_date_kind('year')
  1207. test_datetime_to_date_kind('quarter')
  1208. test_datetime_to_date_kind('month')
  1209. test_datetime_to_date_kind('week')
  1210. test_datetime_to_date_kind('day')
  1211. test_datetime_to_time_kind('hour')
  1212. test_datetime_to_time_kind('minute')
  1213. test_datetime_to_time_kind('second')
  1214. test_datetime_kind('year')
  1215. test_datetime_kind('quarter')
  1216. test_datetime_kind('month')
  1217. test_datetime_kind('week')
  1218. test_datetime_kind('day')
  1219. test_datetime_kind('hour')
  1220. test_datetime_kind('minute')
  1221. test_datetime_kind('second')
  1222. qs = DTModel.objects.filter(
  1223. start_datetime__date=Trunc('start_datetime', 'day', output_field=DateField())
  1224. )
  1225. self.assertEqual(qs.count(), 2)
  1226. def test_trunc_invalid_field_with_timezone(self):
  1227. for melb in self.get_timezones('Australia/Melbourne'):
  1228. with self.subTest(repr(melb)):
  1229. msg = 'tzinfo can only be used with DateTimeField.'
  1230. with self.assertRaisesMessage(ValueError, msg):
  1231. DTModel.objects.annotate(
  1232. day_melb=Trunc('start_date', 'day', tzinfo=melb),
  1233. ).get()
  1234. with self.assertRaisesMessage(ValueError, msg):
  1235. DTModel.objects.annotate(
  1236. hour_melb=Trunc('start_time', 'hour', tzinfo=melb),
  1237. ).get()