test_datetime.py 51 KB

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