test_extract_trunc.py 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  1. from datetime import datetime, timedelta
  2. import pytz
  3. from django.conf import settings
  4. from django.db.models import (
  5. DateField, DateTimeField, IntegerField, Max, OuterRef, Subquery, TimeField,
  6. )
  7. from django.db.models.functions import (
  8. Extract, ExtractDay, ExtractHour, ExtractIsoYear, ExtractMinute,
  9. ExtractMonth, ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay,
  10. ExtractYear, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute,
  11. TruncMonth, TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
  12. )
  13. from django.test import (
  14. TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
  15. )
  16. from django.utils import timezone
  17. from ..models import Author, DTModel, Fan
  18. def truncate_to(value, kind, tzinfo=None):
  19. # Convert to target timezone before truncation
  20. if tzinfo is not None:
  21. value = value.astimezone(tzinfo)
  22. def truncate(value, kind):
  23. if kind == 'second':
  24. return value.replace(microsecond=0)
  25. if kind == 'minute':
  26. return value.replace(second=0, microsecond=0)
  27. if kind == 'hour':
  28. return value.replace(minute=0, second=0, microsecond=0)
  29. if kind == 'day':
  30. if isinstance(value, datetime):
  31. return value.replace(hour=0, minute=0, second=0, microsecond=0)
  32. return value
  33. if kind == 'week':
  34. if isinstance(value, datetime):
  35. return (value - timedelta(days=value.weekday())).replace(hour=0, minute=0, second=0, microsecond=0)
  36. return value - timedelta(days=value.weekday())
  37. if kind == 'month':
  38. if isinstance(value, datetime):
  39. return value.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
  40. return value.replace(day=1)
  41. if kind == 'quarter':
  42. month_in_quarter = value.month - (value.month - 1) % 3
  43. if isinstance(value, datetime):
  44. return value.replace(month=month_in_quarter, day=1, hour=0, minute=0, second=0, microsecond=0)
  45. return value.replace(month=month_in_quarter, day=1)
  46. # otherwise, truncate to year
  47. if isinstance(value, datetime):
  48. return value.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
  49. return value.replace(month=1, day=1)
  50. value = truncate(value, kind)
  51. if tzinfo is not None:
  52. # If there was a daylight saving transition, then reset the timezone.
  53. value = timezone.make_aware(value.replace(tzinfo=None), tzinfo)
  54. return value
  55. @override_settings(USE_TZ=False)
  56. class DateFunctionTests(TestCase):
  57. def create_model(self, start_datetime, end_datetime):
  58. return DTModel.objects.create(
  59. name=start_datetime.isoformat(),
  60. start_datetime=start_datetime, end_datetime=end_datetime,
  61. start_date=start_datetime.date(), end_date=end_datetime.date(),
  62. start_time=start_datetime.time(), end_time=end_datetime.time(),
  63. duration=(end_datetime - start_datetime),
  64. )
  65. def test_extract_year_exact_lookup(self):
  66. """
  67. Extract year uses a BETWEEN filter to compare the year to allow indexes
  68. to be used.
  69. """
  70. start_datetime = datetime(2015, 6, 15, 14, 10)
  71. end_datetime = datetime(2016, 6, 15, 14, 10)
  72. if settings.USE_TZ:
  73. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  74. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  75. self.create_model(start_datetime, end_datetime)
  76. self.create_model(end_datetime, start_datetime)
  77. for lookup in ('year', 'iso_year'):
  78. with self.subTest(lookup):
  79. qs = DTModel.objects.filter(**{'start_datetime__%s__exact' % lookup: 2015})
  80. self.assertEqual(qs.count(), 1)
  81. query_string = str(qs.query).lower()
  82. self.assertEqual(query_string.count(' between '), 1)
  83. self.assertEqual(query_string.count('extract'), 0)
  84. # exact is implied and should be the same
  85. qs = DTModel.objects.filter(**{'start_datetime__%s' % lookup: 2015})
  86. self.assertEqual(qs.count(), 1)
  87. query_string = str(qs.query).lower()
  88. self.assertEqual(query_string.count(' between '), 1)
  89. self.assertEqual(query_string.count('extract'), 0)
  90. # date and datetime fields should behave the same
  91. qs = DTModel.objects.filter(**{'start_date__%s' % lookup: 2015})
  92. self.assertEqual(qs.count(), 1)
  93. query_string = str(qs.query).lower()
  94. self.assertEqual(query_string.count(' between '), 1)
  95. self.assertEqual(query_string.count('extract'), 0)
  96. def test_extract_year_greaterthan_lookup(self):
  97. start_datetime = datetime(2015, 6, 15, 14, 10)
  98. end_datetime = datetime(2016, 6, 15, 14, 10)
  99. if settings.USE_TZ:
  100. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  101. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  102. self.create_model(start_datetime, end_datetime)
  103. self.create_model(end_datetime, start_datetime)
  104. for lookup in ('year', 'iso_year'):
  105. with self.subTest(lookup):
  106. qs = DTModel.objects.filter(**{'start_datetime__%s__gt' % lookup: 2015})
  107. self.assertEqual(qs.count(), 1)
  108. self.assertEqual(str(qs.query).lower().count('extract'), 0)
  109. qs = DTModel.objects.filter(**{'start_datetime__%s__gte' % lookup: 2015})
  110. self.assertEqual(qs.count(), 2)
  111. self.assertEqual(str(qs.query).lower().count('extract'), 0)
  112. def test_extract_year_lessthan_lookup(self):
  113. start_datetime = datetime(2015, 6, 15, 14, 10)
  114. end_datetime = datetime(2016, 6, 15, 14, 10)
  115. if settings.USE_TZ:
  116. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  117. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  118. self.create_model(start_datetime, end_datetime)
  119. self.create_model(end_datetime, start_datetime)
  120. for lookup in ('year', 'iso_year'):
  121. with self.subTest(lookup):
  122. qs = DTModel.objects.filter(**{'start_datetime__%s__lt' % lookup: 2016})
  123. self.assertEqual(qs.count(), 1)
  124. self.assertEqual(str(qs.query).count('extract'), 0)
  125. qs = DTModel.objects.filter(**{'start_datetime__%s__lte' % lookup: 2016})
  126. self.assertEqual(qs.count(), 2)
  127. self.assertEqual(str(qs.query).count('extract'), 0)
  128. def test_extract_func(self):
  129. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  130. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  131. if settings.USE_TZ:
  132. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  133. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  134. self.create_model(start_datetime, end_datetime)
  135. self.create_model(end_datetime, start_datetime)
  136. with self.assertRaisesMessage(ValueError, 'lookup_name must be provided'):
  137. Extract('start_datetime')
  138. msg = 'Extract input expression must be DateField, DateTimeField, TimeField, or DurationField.'
  139. with self.assertRaisesMessage(ValueError, msg):
  140. list(DTModel.objects.annotate(extracted=Extract('name', 'hour')))
  141. with self.assertRaisesMessage(
  142. ValueError, "Cannot extract time component 'second' from DateField 'start_date'."):
  143. list(DTModel.objects.annotate(extracted=Extract('start_date', 'second')))
  144. self.assertQuerysetEqual(
  145. DTModel.objects.annotate(extracted=Extract('start_datetime', 'year')).order_by('start_datetime'),
  146. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  147. lambda m: (m.start_datetime, m.extracted)
  148. )
  149. self.assertQuerysetEqual(
  150. DTModel.objects.annotate(extracted=Extract('start_datetime', 'quarter')).order_by('start_datetime'),
  151. [(start_datetime, 2), (end_datetime, 2)],
  152. lambda m: (m.start_datetime, m.extracted)
  153. )
  154. self.assertQuerysetEqual(
  155. DTModel.objects.annotate(extracted=Extract('start_datetime', 'month')).order_by('start_datetime'),
  156. [(start_datetime, start_datetime.month), (end_datetime, end_datetime.month)],
  157. lambda m: (m.start_datetime, m.extracted)
  158. )
  159. self.assertQuerysetEqual(
  160. DTModel.objects.annotate(extracted=Extract('start_datetime', 'day')).order_by('start_datetime'),
  161. [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
  162. lambda m: (m.start_datetime, m.extracted)
  163. )
  164. self.assertQuerysetEqual(
  165. DTModel.objects.annotate(extracted=Extract('start_datetime', 'week')).order_by('start_datetime'),
  166. [(start_datetime, 25), (end_datetime, 24)],
  167. lambda m: (m.start_datetime, m.extracted)
  168. )
  169. self.assertQuerysetEqual(
  170. DTModel.objects.annotate(extracted=Extract('start_datetime', 'week_day')).order_by('start_datetime'),
  171. [
  172. (start_datetime, (start_datetime.isoweekday() % 7) + 1),
  173. (end_datetime, (end_datetime.isoweekday() % 7) + 1)
  174. ],
  175. lambda m: (m.start_datetime, m.extracted)
  176. )
  177. self.assertQuerysetEqual(
  178. DTModel.objects.annotate(extracted=Extract('start_datetime', 'hour')).order_by('start_datetime'),
  179. [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
  180. lambda m: (m.start_datetime, m.extracted)
  181. )
  182. self.assertQuerysetEqual(
  183. DTModel.objects.annotate(extracted=Extract('start_datetime', 'minute')).order_by('start_datetime'),
  184. [(start_datetime, start_datetime.minute), (end_datetime, end_datetime.minute)],
  185. lambda m: (m.start_datetime, m.extracted)
  186. )
  187. self.assertQuerysetEqual(
  188. DTModel.objects.annotate(extracted=Extract('start_datetime', 'second')).order_by('start_datetime'),
  189. [(start_datetime, start_datetime.second), (end_datetime, end_datetime.second)],
  190. lambda m: (m.start_datetime, m.extracted)
  191. )
  192. self.assertEqual(DTModel.objects.filter(start_datetime__year=Extract('start_datetime', 'year')).count(), 2)
  193. self.assertEqual(DTModel.objects.filter(start_datetime__hour=Extract('start_datetime', 'hour')).count(), 2)
  194. self.assertEqual(DTModel.objects.filter(start_date__month=Extract('start_date', 'month')).count(), 2)
  195. self.assertEqual(DTModel.objects.filter(start_time__hour=Extract('start_time', 'hour')).count(), 2)
  196. @skipUnlessDBFeature('has_native_duration_field')
  197. def test_extract_duration(self):
  198. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  199. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  200. if settings.USE_TZ:
  201. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  202. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  203. self.create_model(start_datetime, end_datetime)
  204. self.create_model(end_datetime, start_datetime)
  205. self.assertQuerysetEqual(
  206. DTModel.objects.annotate(extracted=Extract('duration', 'second')).order_by('start_datetime'),
  207. [
  208. (start_datetime, (end_datetime - start_datetime).seconds % 60),
  209. (end_datetime, (start_datetime - end_datetime).seconds % 60)
  210. ],
  211. lambda m: (m.start_datetime, m.extracted)
  212. )
  213. self.assertEqual(
  214. DTModel.objects.annotate(
  215. duration_days=Extract('duration', 'day'),
  216. ).filter(duration_days__gt=200).count(),
  217. 1
  218. )
  219. @skipIfDBFeature('has_native_duration_field')
  220. def test_extract_duration_without_native_duration_field(self):
  221. msg = 'Extract requires native DurationField database support.'
  222. with self.assertRaisesMessage(ValueError, msg):
  223. list(DTModel.objects.annotate(extracted=Extract('duration', 'second')))
  224. def test_extract_year_func(self):
  225. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  226. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  227. if settings.USE_TZ:
  228. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  229. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  230. self.create_model(start_datetime, end_datetime)
  231. self.create_model(end_datetime, start_datetime)
  232. self.assertQuerysetEqual(
  233. DTModel.objects.annotate(extracted=ExtractYear('start_datetime')).order_by('start_datetime'),
  234. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  235. lambda m: (m.start_datetime, m.extracted)
  236. )
  237. self.assertQuerysetEqual(
  238. DTModel.objects.annotate(extracted=ExtractYear('start_date')).order_by('start_datetime'),
  239. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  240. lambda m: (m.start_datetime, m.extracted)
  241. )
  242. self.assertEqual(DTModel.objects.filter(start_datetime__year=ExtractYear('start_datetime')).count(), 2)
  243. def test_extract_iso_year_func(self):
  244. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  245. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  246. if settings.USE_TZ:
  247. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  248. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  249. self.create_model(start_datetime, end_datetime)
  250. self.create_model(end_datetime, start_datetime)
  251. self.assertQuerysetEqual(
  252. DTModel.objects.annotate(extracted=ExtractIsoYear('start_datetime')).order_by('start_datetime'),
  253. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  254. lambda m: (m.start_datetime, m.extracted)
  255. )
  256. self.assertQuerysetEqual(
  257. DTModel.objects.annotate(extracted=ExtractIsoYear('start_date')).order_by('start_datetime'),
  258. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  259. lambda m: (m.start_datetime, m.extracted)
  260. )
  261. # Both dates are from the same week year.
  262. self.assertEqual(DTModel.objects.filter(start_datetime__iso_year=ExtractIsoYear('start_datetime')).count(), 2)
  263. def test_extract_iso_year_func_boundaries(self):
  264. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  265. if settings.USE_TZ:
  266. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  267. week_52_day_2014 = datetime(2014, 12, 27, 13, 0) # Sunday
  268. week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
  269. week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
  270. if settings.USE_TZ:
  271. week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015, is_dst=False)
  272. week_52_day_2014 = timezone.make_aware(week_52_day_2014, is_dst=False)
  273. week_53_day_2015 = timezone.make_aware(week_53_day_2015, is_dst=False)
  274. days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
  275. self.create_model(week_53_day_2015, end_datetime)
  276. self.create_model(week_52_day_2014, end_datetime)
  277. self.create_model(week_1_day_2014_2015, end_datetime)
  278. qs = DTModel.objects.filter(start_datetime__in=days).annotate(
  279. extracted=ExtractIsoYear('start_datetime'),
  280. ).order_by('start_datetime')
  281. self.assertQuerysetEqual(qs, [
  282. (week_52_day_2014, 2014),
  283. (week_1_day_2014_2015, 2015),
  284. (week_53_day_2015, 2015),
  285. ], lambda m: (m.start_datetime, m.extracted))
  286. def test_extract_month_func(self):
  287. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  288. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  289. if settings.USE_TZ:
  290. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  291. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  292. self.create_model(start_datetime, end_datetime)
  293. self.create_model(end_datetime, start_datetime)
  294. self.assertQuerysetEqual(
  295. DTModel.objects.annotate(extracted=ExtractMonth('start_datetime')).order_by('start_datetime'),
  296. [(start_datetime, start_datetime.month), (end_datetime, end_datetime.month)],
  297. lambda m: (m.start_datetime, m.extracted)
  298. )
  299. self.assertQuerysetEqual(
  300. DTModel.objects.annotate(extracted=ExtractMonth('start_date')).order_by('start_datetime'),
  301. [(start_datetime, start_datetime.month), (end_datetime, end_datetime.month)],
  302. lambda m: (m.start_datetime, m.extracted)
  303. )
  304. self.assertEqual(DTModel.objects.filter(start_datetime__month=ExtractMonth('start_datetime')).count(), 2)
  305. def test_extract_day_func(self):
  306. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  307. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  308. if settings.USE_TZ:
  309. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  310. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  311. self.create_model(start_datetime, end_datetime)
  312. self.create_model(end_datetime, start_datetime)
  313. self.assertQuerysetEqual(
  314. DTModel.objects.annotate(extracted=ExtractDay('start_datetime')).order_by('start_datetime'),
  315. [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
  316. lambda m: (m.start_datetime, m.extracted)
  317. )
  318. self.assertQuerysetEqual(
  319. DTModel.objects.annotate(extracted=ExtractDay('start_date')).order_by('start_datetime'),
  320. [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
  321. lambda m: (m.start_datetime, m.extracted)
  322. )
  323. self.assertEqual(DTModel.objects.filter(start_datetime__day=ExtractDay('start_datetime')).count(), 2)
  324. def test_extract_week_func(self):
  325. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  326. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  327. if settings.USE_TZ:
  328. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  329. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  330. self.create_model(start_datetime, end_datetime)
  331. self.create_model(end_datetime, start_datetime)
  332. self.assertQuerysetEqual(
  333. DTModel.objects.annotate(extracted=ExtractWeek('start_datetime')).order_by('start_datetime'),
  334. [(start_datetime, 25), (end_datetime, 24)],
  335. lambda m: (m.start_datetime, m.extracted)
  336. )
  337. self.assertQuerysetEqual(
  338. DTModel.objects.annotate(extracted=ExtractWeek('start_date')).order_by('start_datetime'),
  339. [(start_datetime, 25), (end_datetime, 24)],
  340. lambda m: (m.start_datetime, m.extracted)
  341. )
  342. # both dates are from the same week.
  343. self.assertEqual(DTModel.objects.filter(start_datetime__week=ExtractWeek('start_datetime')).count(), 2)
  344. def test_extract_quarter_func(self):
  345. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  346. end_datetime = datetime(2016, 8, 15, 14, 10, 50, 123)
  347. if settings.USE_TZ:
  348. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  349. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  350. self.create_model(start_datetime, end_datetime)
  351. self.create_model(end_datetime, start_datetime)
  352. self.assertQuerysetEqual(
  353. DTModel.objects.annotate(extracted=ExtractQuarter('start_datetime')).order_by('start_datetime'),
  354. [(start_datetime, 2), (end_datetime, 3)],
  355. lambda m: (m.start_datetime, m.extracted)
  356. )
  357. self.assertQuerysetEqual(
  358. DTModel.objects.annotate(extracted=ExtractQuarter('start_date')).order_by('start_datetime'),
  359. [(start_datetime, 2), (end_datetime, 3)],
  360. lambda m: (m.start_datetime, m.extracted)
  361. )
  362. self.assertEqual(DTModel.objects.filter(start_datetime__quarter=ExtractQuarter('start_datetime')).count(), 2)
  363. def test_extract_quarter_func_boundaries(self):
  364. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  365. if settings.USE_TZ:
  366. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  367. last_quarter_2014 = datetime(2014, 12, 31, 13, 0)
  368. first_quarter_2015 = datetime(2015, 1, 1, 13, 0)
  369. if settings.USE_TZ:
  370. last_quarter_2014 = timezone.make_aware(last_quarter_2014, is_dst=False)
  371. first_quarter_2015 = timezone.make_aware(first_quarter_2015, is_dst=False)
  372. dates = [last_quarter_2014, first_quarter_2015]
  373. self.create_model(last_quarter_2014, end_datetime)
  374. self.create_model(first_quarter_2015, end_datetime)
  375. qs = DTModel.objects.filter(start_datetime__in=dates).annotate(
  376. extracted=ExtractQuarter('start_datetime'),
  377. ).order_by('start_datetime')
  378. self.assertQuerysetEqual(qs, [
  379. (last_quarter_2014, 4),
  380. (first_quarter_2015, 1),
  381. ], lambda m: (m.start_datetime, m.extracted))
  382. def test_extract_week_func_boundaries(self):
  383. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  384. if settings.USE_TZ:
  385. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  386. week_52_day_2014 = datetime(2014, 12, 27, 13, 0) # Sunday
  387. week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
  388. week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
  389. if settings.USE_TZ:
  390. week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015, is_dst=False)
  391. week_52_day_2014 = timezone.make_aware(week_52_day_2014, is_dst=False)
  392. week_53_day_2015 = timezone.make_aware(week_53_day_2015, is_dst=False)
  393. days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
  394. self.create_model(week_53_day_2015, end_datetime)
  395. self.create_model(week_52_day_2014, end_datetime)
  396. self.create_model(week_1_day_2014_2015, end_datetime)
  397. qs = DTModel.objects.filter(start_datetime__in=days).annotate(
  398. extracted=ExtractWeek('start_datetime'),
  399. ).order_by('start_datetime')
  400. self.assertQuerysetEqual(qs, [
  401. (week_52_day_2014, 52),
  402. (week_1_day_2014_2015, 1),
  403. (week_53_day_2015, 53),
  404. ], lambda m: (m.start_datetime, m.extracted))
  405. def test_extract_weekday_func(self):
  406. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  407. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  408. if settings.USE_TZ:
  409. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  410. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  411. self.create_model(start_datetime, end_datetime)
  412. self.create_model(end_datetime, start_datetime)
  413. self.assertQuerysetEqual(
  414. DTModel.objects.annotate(extracted=ExtractWeekDay('start_datetime')).order_by('start_datetime'),
  415. [
  416. (start_datetime, (start_datetime.isoweekday() % 7) + 1),
  417. (end_datetime, (end_datetime.isoweekday() % 7) + 1),
  418. ],
  419. lambda m: (m.start_datetime, m.extracted)
  420. )
  421. self.assertQuerysetEqual(
  422. DTModel.objects.annotate(extracted=ExtractWeekDay('start_date')).order_by('start_datetime'),
  423. [
  424. (start_datetime, (start_datetime.isoweekday() % 7) + 1),
  425. (end_datetime, (end_datetime.isoweekday() % 7) + 1),
  426. ],
  427. lambda m: (m.start_datetime, m.extracted)
  428. )
  429. self.assertEqual(DTModel.objects.filter(start_datetime__week_day=ExtractWeekDay('start_datetime')).count(), 2)
  430. def test_extract_hour_func(self):
  431. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  432. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  433. if settings.USE_TZ:
  434. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  435. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  436. self.create_model(start_datetime, end_datetime)
  437. self.create_model(end_datetime, start_datetime)
  438. self.assertQuerysetEqual(
  439. DTModel.objects.annotate(extracted=ExtractHour('start_datetime')).order_by('start_datetime'),
  440. [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
  441. lambda m: (m.start_datetime, m.extracted)
  442. )
  443. self.assertQuerysetEqual(
  444. DTModel.objects.annotate(extracted=ExtractHour('start_time')).order_by('start_datetime'),
  445. [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
  446. lambda m: (m.start_datetime, m.extracted)
  447. )
  448. self.assertEqual(DTModel.objects.filter(start_datetime__hour=ExtractHour('start_datetime')).count(), 2)
  449. def test_extract_minute_func(self):
  450. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  451. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  452. if settings.USE_TZ:
  453. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  454. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  455. self.create_model(start_datetime, end_datetime)
  456. self.create_model(end_datetime, start_datetime)
  457. self.assertQuerysetEqual(
  458. DTModel.objects.annotate(extracted=ExtractMinute('start_datetime')).order_by('start_datetime'),
  459. [(start_datetime, start_datetime.minute), (end_datetime, end_datetime.minute)],
  460. lambda m: (m.start_datetime, m.extracted)
  461. )
  462. self.assertQuerysetEqual(
  463. DTModel.objects.annotate(extracted=ExtractMinute('start_time')).order_by('start_datetime'),
  464. [(start_datetime, start_datetime.minute), (end_datetime, end_datetime.minute)],
  465. lambda m: (m.start_datetime, m.extracted)
  466. )
  467. self.assertEqual(DTModel.objects.filter(start_datetime__minute=ExtractMinute('start_datetime')).count(), 2)
  468. def test_extract_second_func(self):
  469. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  470. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  471. if settings.USE_TZ:
  472. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  473. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  474. self.create_model(start_datetime, end_datetime)
  475. self.create_model(end_datetime, start_datetime)
  476. self.assertQuerysetEqual(
  477. DTModel.objects.annotate(extracted=ExtractSecond('start_datetime')).order_by('start_datetime'),
  478. [(start_datetime, start_datetime.second), (end_datetime, end_datetime.second)],
  479. lambda m: (m.start_datetime, m.extracted)
  480. )
  481. self.assertQuerysetEqual(
  482. DTModel.objects.annotate(extracted=ExtractSecond('start_time')).order_by('start_datetime'),
  483. [(start_datetime, start_datetime.second), (end_datetime, end_datetime.second)],
  484. lambda m: (m.start_datetime, m.extracted)
  485. )
  486. self.assertEqual(DTModel.objects.filter(start_datetime__second=ExtractSecond('start_datetime')).count(), 2)
  487. def test_trunc_func(self):
  488. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  489. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  490. if settings.USE_TZ:
  491. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  492. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  493. self.create_model(start_datetime, end_datetime)
  494. self.create_model(end_datetime, start_datetime)
  495. msg = 'output_field must be either DateField, TimeField, or DateTimeField'
  496. with self.assertRaisesMessage(ValueError, msg):
  497. list(DTModel.objects.annotate(truncated=Trunc('start_datetime', 'year', output_field=IntegerField())))
  498. with self.assertRaisesMessage(AssertionError, "'name' isn't a DateField, TimeField, or DateTimeField."):
  499. list(DTModel.objects.annotate(truncated=Trunc('name', 'year', output_field=DateTimeField())))
  500. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  501. list(DTModel.objects.annotate(truncated=Trunc('start_date', 'second')))
  502. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  503. list(DTModel.objects.annotate(truncated=Trunc('start_time', 'month')))
  504. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  505. list(DTModel.objects.annotate(truncated=Trunc('start_date', 'month', output_field=DateTimeField())))
  506. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  507. list(DTModel.objects.annotate(truncated=Trunc('start_time', 'second', output_field=DateTimeField())))
  508. def test_datetime_kind(kind):
  509. self.assertQuerysetEqual(
  510. DTModel.objects.annotate(
  511. truncated=Trunc('start_datetime', kind, output_field=DateTimeField())
  512. ).order_by('start_datetime'),
  513. [
  514. (start_datetime, truncate_to(start_datetime, kind)),
  515. (end_datetime, truncate_to(end_datetime, kind))
  516. ],
  517. lambda m: (m.start_datetime, m.truncated)
  518. )
  519. def test_date_kind(kind):
  520. self.assertQuerysetEqual(
  521. DTModel.objects.annotate(
  522. truncated=Trunc('start_date', kind, output_field=DateField())
  523. ).order_by('start_datetime'),
  524. [
  525. (start_datetime, truncate_to(start_datetime.date(), kind)),
  526. (end_datetime, truncate_to(end_datetime.date(), kind))
  527. ],
  528. lambda m: (m.start_datetime, m.truncated)
  529. )
  530. def test_time_kind(kind):
  531. self.assertQuerysetEqual(
  532. DTModel.objects.annotate(
  533. truncated=Trunc('start_time', kind, output_field=TimeField())
  534. ).order_by('start_datetime'),
  535. [
  536. (start_datetime, truncate_to(start_datetime.time(), kind)),
  537. (end_datetime, truncate_to(end_datetime.time(), kind))
  538. ],
  539. lambda m: (m.start_datetime, m.truncated)
  540. )
  541. test_date_kind('year')
  542. test_date_kind('quarter')
  543. test_date_kind('month')
  544. test_date_kind('week')
  545. test_date_kind('day')
  546. test_time_kind('hour')
  547. test_time_kind('minute')
  548. test_time_kind('second')
  549. test_datetime_kind('year')
  550. test_datetime_kind('quarter')
  551. test_datetime_kind('month')
  552. test_datetime_kind('week')
  553. test_datetime_kind('day')
  554. test_datetime_kind('hour')
  555. test_datetime_kind('minute')
  556. test_datetime_kind('second')
  557. qs = DTModel.objects.filter(start_datetime__date=Trunc('start_datetime', 'day', output_field=DateField()))
  558. self.assertEqual(qs.count(), 2)
  559. def test_trunc_year_func(self):
  560. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  561. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'year')
  562. if settings.USE_TZ:
  563. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  564. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  565. self.create_model(start_datetime, end_datetime)
  566. self.create_model(end_datetime, start_datetime)
  567. self.assertQuerysetEqual(
  568. DTModel.objects.annotate(extracted=TruncYear('start_datetime')).order_by('start_datetime'),
  569. [
  570. (start_datetime, truncate_to(start_datetime, 'year')),
  571. (end_datetime, truncate_to(end_datetime, 'year')),
  572. ],
  573. lambda m: (m.start_datetime, m.extracted)
  574. )
  575. self.assertQuerysetEqual(
  576. DTModel.objects.annotate(extracted=TruncYear('start_date')).order_by('start_datetime'),
  577. [
  578. (start_datetime, truncate_to(start_datetime.date(), 'year')),
  579. (end_datetime, truncate_to(end_datetime.date(), 'year')),
  580. ],
  581. lambda m: (m.start_datetime, m.extracted)
  582. )
  583. self.assertEqual(DTModel.objects.filter(start_datetime=TruncYear('start_datetime')).count(), 1)
  584. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  585. list(DTModel.objects.annotate(truncated=TruncYear('start_time')))
  586. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  587. list(DTModel.objects.annotate(truncated=TruncYear('start_time', output_field=TimeField())))
  588. def test_trunc_quarter_func(self):
  589. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  590. end_datetime = truncate_to(datetime(2016, 10, 15, 14, 10, 50, 123), 'quarter')
  591. last_quarter_2015 = truncate_to(datetime(2015, 12, 31, 14, 10, 50, 123), 'quarter')
  592. first_quarter_2016 = truncate_to(datetime(2016, 1, 1, 14, 10, 50, 123), 'quarter')
  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. last_quarter_2015 = timezone.make_aware(last_quarter_2015, is_dst=False)
  597. first_quarter_2016 = timezone.make_aware(first_quarter_2016, is_dst=False)
  598. self.create_model(start_datetime=start_datetime, end_datetime=end_datetime)
  599. self.create_model(start_datetime=end_datetime, end_datetime=start_datetime)
  600. self.create_model(start_datetime=last_quarter_2015, end_datetime=end_datetime)
  601. self.create_model(start_datetime=first_quarter_2016, end_datetime=end_datetime)
  602. self.assertQuerysetEqual(
  603. DTModel.objects.annotate(extracted=TruncQuarter('start_date')).order_by('start_datetime'),
  604. [
  605. (start_datetime, truncate_to(start_datetime.date(), 'quarter')),
  606. (last_quarter_2015, truncate_to(last_quarter_2015.date(), 'quarter')),
  607. (first_quarter_2016, truncate_to(first_quarter_2016.date(), 'quarter')),
  608. (end_datetime, truncate_to(end_datetime.date(), 'quarter')),
  609. ],
  610. lambda m: (m.start_datetime, m.extracted)
  611. )
  612. self.assertQuerysetEqual(
  613. DTModel.objects.annotate(extracted=TruncQuarter('start_datetime')).order_by('start_datetime'),
  614. [
  615. (start_datetime, truncate_to(start_datetime, 'quarter')),
  616. (last_quarter_2015, truncate_to(last_quarter_2015, 'quarter')),
  617. (first_quarter_2016, truncate_to(first_quarter_2016, 'quarter')),
  618. (end_datetime, truncate_to(end_datetime, 'quarter')),
  619. ],
  620. lambda m: (m.start_datetime, m.extracted)
  621. )
  622. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  623. list(DTModel.objects.annotate(truncated=TruncQuarter('start_time')))
  624. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  625. list(DTModel.objects.annotate(truncated=TruncQuarter('start_time', output_field=TimeField())))
  626. def test_trunc_month_func(self):
  627. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  628. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'month')
  629. if settings.USE_TZ:
  630. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  631. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  632. self.create_model(start_datetime, end_datetime)
  633. self.create_model(end_datetime, start_datetime)
  634. self.assertQuerysetEqual(
  635. DTModel.objects.annotate(extracted=TruncMonth('start_datetime')).order_by('start_datetime'),
  636. [
  637. (start_datetime, truncate_to(start_datetime, 'month')),
  638. (end_datetime, truncate_to(end_datetime, 'month')),
  639. ],
  640. lambda m: (m.start_datetime, m.extracted)
  641. )
  642. self.assertQuerysetEqual(
  643. DTModel.objects.annotate(extracted=TruncMonth('start_date')).order_by('start_datetime'),
  644. [
  645. (start_datetime, truncate_to(start_datetime.date(), 'month')),
  646. (end_datetime, truncate_to(end_datetime.date(), 'month')),
  647. ],
  648. lambda m: (m.start_datetime, m.extracted)
  649. )
  650. self.assertEqual(DTModel.objects.filter(start_datetime=TruncMonth('start_datetime')).count(), 1)
  651. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  652. list(DTModel.objects.annotate(truncated=TruncMonth('start_time')))
  653. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  654. list(DTModel.objects.annotate(truncated=TruncMonth('start_time', output_field=TimeField())))
  655. def test_trunc_week_func(self):
  656. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  657. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'week')
  658. if settings.USE_TZ:
  659. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  660. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  661. self.create_model(start_datetime, end_datetime)
  662. self.create_model(end_datetime, start_datetime)
  663. self.assertQuerysetEqual(
  664. DTModel.objects.annotate(extracted=TruncWeek('start_datetime')).order_by('start_datetime'),
  665. [
  666. (start_datetime, truncate_to(start_datetime, 'week')),
  667. (end_datetime, truncate_to(end_datetime, 'week')),
  668. ],
  669. lambda m: (m.start_datetime, m.extracted)
  670. )
  671. self.assertEqual(DTModel.objects.filter(start_datetime=TruncWeek('start_datetime')).count(), 1)
  672. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  673. list(DTModel.objects.annotate(truncated=TruncWeek('start_time')))
  674. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  675. list(DTModel.objects.annotate(truncated=TruncWeek('start_time', output_field=TimeField())))
  676. def test_trunc_date_func(self):
  677. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  678. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  679. if settings.USE_TZ:
  680. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  681. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  682. self.create_model(start_datetime, end_datetime)
  683. self.create_model(end_datetime, start_datetime)
  684. self.assertQuerysetEqual(
  685. DTModel.objects.annotate(extracted=TruncDate('start_datetime')).order_by('start_datetime'),
  686. [
  687. (start_datetime, start_datetime.date()),
  688. (end_datetime, end_datetime.date()),
  689. ],
  690. lambda m: (m.start_datetime, m.extracted)
  691. )
  692. self.assertEqual(DTModel.objects.filter(start_datetime__date=TruncDate('start_datetime')).count(), 2)
  693. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateField"):
  694. list(DTModel.objects.annotate(truncated=TruncDate('start_time')))
  695. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateField"):
  696. list(DTModel.objects.annotate(truncated=TruncDate('start_time', output_field=TimeField())))
  697. def test_trunc_time_func(self):
  698. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  699. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  700. if settings.USE_TZ:
  701. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  702. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  703. self.create_model(start_datetime, end_datetime)
  704. self.create_model(end_datetime, start_datetime)
  705. self.assertQuerysetEqual(
  706. DTModel.objects.annotate(extracted=TruncTime('start_datetime')).order_by('start_datetime'),
  707. [
  708. (start_datetime, start_datetime.time()),
  709. (end_datetime, end_datetime.time()),
  710. ],
  711. lambda m: (m.start_datetime, m.extracted)
  712. )
  713. self.assertEqual(DTModel.objects.filter(start_datetime__time=TruncTime('start_datetime')).count(), 2)
  714. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to TimeField"):
  715. list(DTModel.objects.annotate(truncated=TruncTime('start_date')))
  716. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to TimeField"):
  717. list(DTModel.objects.annotate(truncated=TruncTime('start_date', output_field=DateField())))
  718. def test_trunc_day_func(self):
  719. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  720. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'day')
  721. if settings.USE_TZ:
  722. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  723. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  724. self.create_model(start_datetime, end_datetime)
  725. self.create_model(end_datetime, start_datetime)
  726. self.assertQuerysetEqual(
  727. DTModel.objects.annotate(extracted=TruncDay('start_datetime')).order_by('start_datetime'),
  728. [
  729. (start_datetime, truncate_to(start_datetime, 'day')),
  730. (end_datetime, truncate_to(end_datetime, 'day')),
  731. ],
  732. lambda m: (m.start_datetime, m.extracted)
  733. )
  734. self.assertEqual(DTModel.objects.filter(start_datetime=TruncDay('start_datetime')).count(), 1)
  735. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  736. list(DTModel.objects.annotate(truncated=TruncDay('start_time')))
  737. with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
  738. list(DTModel.objects.annotate(truncated=TruncDay('start_time', output_field=TimeField())))
  739. def test_trunc_hour_func(self):
  740. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  741. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'hour')
  742. if settings.USE_TZ:
  743. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  744. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  745. self.create_model(start_datetime, end_datetime)
  746. self.create_model(end_datetime, start_datetime)
  747. self.assertQuerysetEqual(
  748. DTModel.objects.annotate(extracted=TruncHour('start_datetime')).order_by('start_datetime'),
  749. [
  750. (start_datetime, truncate_to(start_datetime, 'hour')),
  751. (end_datetime, truncate_to(end_datetime, 'hour')),
  752. ],
  753. lambda m: (m.start_datetime, m.extracted)
  754. )
  755. self.assertQuerysetEqual(
  756. DTModel.objects.annotate(extracted=TruncHour('start_time')).order_by('start_datetime'),
  757. [
  758. (start_datetime, truncate_to(start_datetime.time(), 'hour')),
  759. (end_datetime, truncate_to(end_datetime.time(), 'hour')),
  760. ],
  761. lambda m: (m.start_datetime, m.extracted)
  762. )
  763. self.assertEqual(DTModel.objects.filter(start_datetime=TruncHour('start_datetime')).count(), 1)
  764. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  765. list(DTModel.objects.annotate(truncated=TruncHour('start_date')))
  766. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  767. list(DTModel.objects.annotate(truncated=TruncHour('start_date', output_field=DateField())))
  768. def test_trunc_minute_func(self):
  769. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  770. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'minute')
  771. if settings.USE_TZ:
  772. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  773. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  774. self.create_model(start_datetime, end_datetime)
  775. self.create_model(end_datetime, start_datetime)
  776. self.assertQuerysetEqual(
  777. DTModel.objects.annotate(extracted=TruncMinute('start_datetime')).order_by('start_datetime'),
  778. [
  779. (start_datetime, truncate_to(start_datetime, 'minute')),
  780. (end_datetime, truncate_to(end_datetime, 'minute')),
  781. ],
  782. lambda m: (m.start_datetime, m.extracted)
  783. )
  784. self.assertQuerysetEqual(
  785. DTModel.objects.annotate(extracted=TruncMinute('start_time')).order_by('start_datetime'),
  786. [
  787. (start_datetime, truncate_to(start_datetime.time(), 'minute')),
  788. (end_datetime, truncate_to(end_datetime.time(), 'minute')),
  789. ],
  790. lambda m: (m.start_datetime, m.extracted)
  791. )
  792. self.assertEqual(DTModel.objects.filter(start_datetime=TruncMinute('start_datetime')).count(), 1)
  793. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  794. list(DTModel.objects.annotate(truncated=TruncMinute('start_date')))
  795. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  796. list(DTModel.objects.annotate(truncated=TruncMinute('start_date', output_field=DateField())))
  797. def test_trunc_second_func(self):
  798. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  799. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'second')
  800. if settings.USE_TZ:
  801. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  802. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  803. self.create_model(start_datetime, end_datetime)
  804. self.create_model(end_datetime, start_datetime)
  805. self.assertQuerysetEqual(
  806. DTModel.objects.annotate(extracted=TruncSecond('start_datetime')).order_by('start_datetime'),
  807. [
  808. (start_datetime, truncate_to(start_datetime, 'second')),
  809. (end_datetime, truncate_to(end_datetime, 'second'))
  810. ],
  811. lambda m: (m.start_datetime, m.extracted)
  812. )
  813. self.assertQuerysetEqual(
  814. DTModel.objects.annotate(extracted=TruncSecond('start_time')).order_by('start_datetime'),
  815. [
  816. (start_datetime, truncate_to(start_datetime.time(), 'second')),
  817. (end_datetime, truncate_to(end_datetime.time(), 'second'))
  818. ],
  819. lambda m: (m.start_datetime, m.extracted)
  820. )
  821. self.assertEqual(DTModel.objects.filter(start_datetime=TruncSecond('start_datetime')).count(), 1)
  822. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  823. list(DTModel.objects.annotate(truncated=TruncSecond('start_date')))
  824. with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
  825. list(DTModel.objects.annotate(truncated=TruncSecond('start_date', output_field=DateField())))
  826. def test_trunc_subquery_with_parameters(self):
  827. author_1 = Author.objects.create(name='J. R. R. Tolkien')
  828. author_2 = Author.objects.create(name='G. R. R. Martin')
  829. fan_since_1 = datetime(2016, 2, 3, 15, 0, 0)
  830. fan_since_2 = datetime(2015, 2, 3, 15, 0, 0)
  831. fan_since_3 = datetime(2017, 2, 3, 15, 0, 0)
  832. if settings.USE_TZ:
  833. fan_since_1 = timezone.make_aware(fan_since_1, is_dst=False)
  834. fan_since_2 = timezone.make_aware(fan_since_2, is_dst=False)
  835. fan_since_3 = timezone.make_aware(fan_since_3, is_dst=False)
  836. Fan.objects.create(author=author_1, name='Tom', fan_since=fan_since_1)
  837. Fan.objects.create(author=author_1, name='Emma', fan_since=fan_since_2)
  838. Fan.objects.create(author=author_2, name='Isabella', fan_since=fan_since_3)
  839. inner = Fan.objects.filter(
  840. author=OuterRef('pk'),
  841. name__in=('Emma', 'Isabella', 'Tom')
  842. ).values('author').annotate(newest_fan=Max('fan_since')).values('newest_fan')
  843. outer = Author.objects.annotate(
  844. newest_fan_year=TruncYear(Subquery(inner, output_field=DateTimeField()))
  845. )
  846. tz = pytz.UTC if settings.USE_TZ else None
  847. self.assertSequenceEqual(
  848. outer.order_by('name').values('name', 'newest_fan_year'),
  849. [
  850. {'name': 'G. R. R. Martin', 'newest_fan_year': datetime(2017, 1, 1, 0, 0, tzinfo=tz)},
  851. {'name': 'J. R. R. Tolkien', 'newest_fan_year': datetime(2016, 1, 1, 0, 0, tzinfo=tz)},
  852. ]
  853. )
  854. @override_settings(USE_TZ=True, TIME_ZONE='UTC')
  855. class DateFunctionWithTimeZoneTests(DateFunctionTests):
  856. def test_extract_func_with_timezone(self):
  857. start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
  858. end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
  859. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  860. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  861. self.create_model(start_datetime, end_datetime)
  862. melb = pytz.timezone('Australia/Melbourne')
  863. qs = DTModel.objects.annotate(
  864. day=Extract('start_datetime', 'day'),
  865. day_melb=Extract('start_datetime', 'day', tzinfo=melb),
  866. week=Extract('start_datetime', 'week', tzinfo=melb),
  867. isoyear=ExtractIsoYear('start_datetime', tzinfo=melb),
  868. weekday=ExtractWeekDay('start_datetime'),
  869. weekday_melb=ExtractWeekDay('start_datetime', tzinfo=melb),
  870. quarter=ExtractQuarter('start_datetime', tzinfo=melb),
  871. hour=ExtractHour('start_datetime'),
  872. hour_melb=ExtractHour('start_datetime', tzinfo=melb),
  873. ).order_by('start_datetime')
  874. utc_model = qs.get()
  875. self.assertEqual(utc_model.day, 15)
  876. self.assertEqual(utc_model.day_melb, 16)
  877. self.assertEqual(utc_model.week, 25)
  878. self.assertEqual(utc_model.isoyear, 2015)
  879. self.assertEqual(utc_model.weekday, 2)
  880. self.assertEqual(utc_model.weekday_melb, 3)
  881. self.assertEqual(utc_model.quarter, 2)
  882. self.assertEqual(utc_model.hour, 23)
  883. self.assertEqual(utc_model.hour_melb, 9)
  884. with timezone.override(melb):
  885. melb_model = qs.get()
  886. self.assertEqual(melb_model.day, 16)
  887. self.assertEqual(melb_model.day_melb, 16)
  888. self.assertEqual(melb_model.week, 25)
  889. self.assertEqual(melb_model.isoyear, 2015)
  890. self.assertEqual(melb_model.weekday, 3)
  891. self.assertEqual(melb_model.quarter, 2)
  892. self.assertEqual(melb_model.weekday_melb, 3)
  893. self.assertEqual(melb_model.hour, 9)
  894. self.assertEqual(melb_model.hour_melb, 9)
  895. def test_extract_func_explicit_timezone_priority(self):
  896. start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
  897. end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
  898. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  899. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  900. self.create_model(start_datetime, end_datetime)
  901. melb = pytz.timezone('Australia/Melbourne')
  902. with timezone.override(melb):
  903. model = DTModel.objects.annotate(
  904. day_melb=Extract('start_datetime', 'day'),
  905. day_utc=Extract('start_datetime', 'day', tzinfo=timezone.utc),
  906. ).order_by('start_datetime').get()
  907. self.assertEqual(model.day_melb, 16)
  908. self.assertEqual(model.day_utc, 15)
  909. def test_trunc_timezone_applied_before_truncation(self):
  910. start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
  911. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  912. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  913. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  914. self.create_model(start_datetime, end_datetime)
  915. melb = pytz.timezone('Australia/Melbourne')
  916. pacific = pytz.timezone('US/Pacific')
  917. model = DTModel.objects.annotate(
  918. melb_year=TruncYear('start_datetime', tzinfo=melb),
  919. pacific_year=TruncYear('start_datetime', tzinfo=pacific),
  920. ).order_by('start_datetime').get()
  921. self.assertEqual(model.start_datetime, start_datetime)
  922. self.assertEqual(model.melb_year, truncate_to(start_datetime, 'year', melb))
  923. self.assertEqual(model.pacific_year, truncate_to(start_datetime, 'year', pacific))
  924. self.assertEqual(model.start_datetime.year, 2016)
  925. self.assertEqual(model.melb_year.year, 2016)
  926. self.assertEqual(model.pacific_year.year, 2015)
  927. def test_trunc_func_with_timezone(self):
  928. """
  929. If the truncated datetime transitions to a different offset (daylight
  930. saving) then the returned value will have that new timezone/offset.
  931. """
  932. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  933. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  934. start_datetime = timezone.make_aware(start_datetime, is_dst=False)
  935. end_datetime = timezone.make_aware(end_datetime, is_dst=False)
  936. self.create_model(start_datetime, end_datetime)
  937. self.create_model(end_datetime, start_datetime)
  938. melb = pytz.timezone('Australia/Melbourne')
  939. def test_datetime_kind(kind):
  940. self.assertQuerysetEqual(
  941. DTModel.objects.annotate(
  942. truncated=Trunc('start_datetime', kind, output_field=DateTimeField(), tzinfo=melb)
  943. ).order_by('start_datetime'),
  944. [
  945. (start_datetime, truncate_to(start_datetime.astimezone(melb), kind, melb)),
  946. (end_datetime, truncate_to(end_datetime.astimezone(melb), kind, melb))
  947. ],
  948. lambda m: (m.start_datetime, m.truncated)
  949. )
  950. def test_date_kind(kind):
  951. self.assertQuerysetEqual(
  952. DTModel.objects.annotate(
  953. truncated=Trunc('start_date', kind, output_field=DateField(), tzinfo=melb)
  954. ).order_by('start_datetime'),
  955. [
  956. (start_datetime, truncate_to(start_datetime.date(), kind)),
  957. (end_datetime, truncate_to(end_datetime.date(), kind))
  958. ],
  959. lambda m: (m.start_datetime, m.truncated)
  960. )
  961. def test_time_kind(kind):
  962. self.assertQuerysetEqual(
  963. DTModel.objects.annotate(
  964. truncated=Trunc('start_time', kind, output_field=TimeField(), tzinfo=melb)
  965. ).order_by('start_datetime'),
  966. [
  967. (start_datetime, truncate_to(start_datetime.time(), kind)),
  968. (end_datetime, truncate_to(end_datetime.time(), kind))
  969. ],
  970. lambda m: (m.start_datetime, m.truncated)
  971. )
  972. test_date_kind('year')
  973. test_date_kind('quarter')
  974. test_date_kind('month')
  975. test_date_kind('week')
  976. test_date_kind('day')
  977. test_time_kind('hour')
  978. test_time_kind('minute')
  979. test_time_kind('second')
  980. test_datetime_kind('year')
  981. test_datetime_kind('quarter')
  982. test_datetime_kind('month')
  983. test_datetime_kind('week')
  984. test_datetime_kind('day')
  985. test_datetime_kind('hour')
  986. test_datetime_kind('minute')
  987. test_datetime_kind('second')
  988. qs = DTModel.objects.filter(start_datetime__date=Trunc('start_datetime', 'day', output_field=DateField()))
  989. self.assertEqual(qs.count(), 2)