test_datetime.py 51 KB

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