test_extract_trunc.py 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983
  1. import unittest
  2. from datetime import datetime, timedelta
  3. from datetime import timezone as datetime_timezone
  4. try:
  5. import zoneinfo
  6. except ImportError:
  7. from backports import zoneinfo
  8. try:
  9. import pytz
  10. except ImportError:
  11. pytz = None
  12. from django.conf import settings
  13. from django.db.models import (
  14. DateField,
  15. DateTimeField,
  16. F,
  17. IntegerField,
  18. Max,
  19. OuterRef,
  20. Subquery,
  21. TimeField,
  22. )
  23. from django.db.models.functions import (
  24. Extract,
  25. ExtractDay,
  26. ExtractHour,
  27. ExtractIsoWeekDay,
  28. ExtractIsoYear,
  29. ExtractMinute,
  30. ExtractMonth,
  31. ExtractQuarter,
  32. ExtractSecond,
  33. ExtractWeek,
  34. ExtractWeekDay,
  35. ExtractYear,
  36. Trunc,
  37. TruncDate,
  38. TruncDay,
  39. TruncHour,
  40. TruncMinute,
  41. TruncMonth,
  42. TruncQuarter,
  43. TruncSecond,
  44. TruncTime,
  45. TruncWeek,
  46. TruncYear,
  47. )
  48. from django.test import (
  49. TestCase,
  50. ignore_warnings,
  51. override_settings,
  52. skipIfDBFeature,
  53. skipUnlessDBFeature,
  54. )
  55. from django.utils import timezone
  56. from django.utils.deprecation import RemovedInDjango50Warning
  57. from ..models import Author, DTModel, Fan
  58. HAS_PYTZ = pytz is not None
  59. if not HAS_PYTZ:
  60. needs_pytz = unittest.skip("Test requires pytz")
  61. else:
  62. def needs_pytz(f):
  63. return f
  64. ZONE_CONSTRUCTORS = (zoneinfo.ZoneInfo,)
  65. if HAS_PYTZ:
  66. ZONE_CONSTRUCTORS += (pytz.timezone,)
  67. def truncate_to(value, kind, tzinfo=None):
  68. # Convert to target timezone before truncation
  69. if tzinfo is not None:
  70. value = value.astimezone(tzinfo)
  71. def truncate(value, kind):
  72. if kind == "second":
  73. return value.replace(microsecond=0)
  74. if kind == "minute":
  75. return value.replace(second=0, microsecond=0)
  76. if kind == "hour":
  77. return value.replace(minute=0, second=0, microsecond=0)
  78. if kind == "day":
  79. if isinstance(value, datetime):
  80. return value.replace(hour=0, minute=0, second=0, microsecond=0)
  81. return value
  82. if kind == "week":
  83. if isinstance(value, datetime):
  84. return (value - timedelta(days=value.weekday())).replace(
  85. hour=0, minute=0, second=0, microsecond=0
  86. )
  87. return value - timedelta(days=value.weekday())
  88. if kind == "month":
  89. if isinstance(value, datetime):
  90. return value.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
  91. return value.replace(day=1)
  92. if kind == "quarter":
  93. month_in_quarter = value.month - (value.month - 1) % 3
  94. if isinstance(value, datetime):
  95. return value.replace(
  96. month=month_in_quarter,
  97. day=1,
  98. hour=0,
  99. minute=0,
  100. second=0,
  101. microsecond=0,
  102. )
  103. return value.replace(month=month_in_quarter, day=1)
  104. # otherwise, truncate to year
  105. if isinstance(value, datetime):
  106. return value.replace(
  107. month=1, day=1, hour=0, minute=0, second=0, microsecond=0
  108. )
  109. return value.replace(month=1, day=1)
  110. value = truncate(value, kind)
  111. if tzinfo is not None:
  112. # If there was a daylight saving transition, then reset the timezone.
  113. value = timezone.make_aware(value.replace(tzinfo=None), tzinfo)
  114. return value
  115. @override_settings(USE_TZ=False)
  116. class DateFunctionTests(TestCase):
  117. def create_model(self, start_datetime, end_datetime):
  118. return DTModel.objects.create(
  119. name=start_datetime.isoformat() if start_datetime else "None",
  120. start_datetime=start_datetime,
  121. end_datetime=end_datetime,
  122. start_date=start_datetime.date() if start_datetime else None,
  123. end_date=end_datetime.date() if end_datetime else None,
  124. start_time=start_datetime.time() if start_datetime else None,
  125. end_time=end_datetime.time() if end_datetime else None,
  126. duration=(end_datetime - start_datetime)
  127. if start_datetime and end_datetime
  128. else None,
  129. )
  130. def test_extract_year_exact_lookup(self):
  131. """
  132. Extract year uses a BETWEEN filter to compare the year to allow indexes
  133. to be used.
  134. """
  135. start_datetime = datetime(2015, 6, 15, 14, 10)
  136. end_datetime = datetime(2016, 6, 15, 14, 10)
  137. if settings.USE_TZ:
  138. start_datetime = timezone.make_aware(start_datetime)
  139. end_datetime = timezone.make_aware(end_datetime)
  140. self.create_model(start_datetime, end_datetime)
  141. self.create_model(end_datetime, start_datetime)
  142. for lookup in ("year", "iso_year"):
  143. with self.subTest(lookup):
  144. qs = DTModel.objects.filter(
  145. **{"start_datetime__%s__exact" % lookup: 2015}
  146. )
  147. self.assertEqual(qs.count(), 1)
  148. query_string = str(qs.query).lower()
  149. self.assertEqual(query_string.count(" between "), 1)
  150. self.assertEqual(query_string.count("extract"), 0)
  151. # exact is implied and should be the same
  152. qs = DTModel.objects.filter(**{"start_datetime__%s" % lookup: 2015})
  153. self.assertEqual(qs.count(), 1)
  154. query_string = str(qs.query).lower()
  155. self.assertEqual(query_string.count(" between "), 1)
  156. self.assertEqual(query_string.count("extract"), 0)
  157. # date and datetime fields should behave the same
  158. qs = DTModel.objects.filter(**{"start_date__%s" % lookup: 2015})
  159. self.assertEqual(qs.count(), 1)
  160. query_string = str(qs.query).lower()
  161. self.assertEqual(query_string.count(" between "), 1)
  162. self.assertEqual(query_string.count("extract"), 0)
  163. # an expression rhs cannot use the between optimization.
  164. qs = DTModel.objects.annotate(
  165. start_year=ExtractYear("start_datetime"),
  166. ).filter(end_datetime__year=F("start_year") + 1)
  167. self.assertEqual(qs.count(), 1)
  168. query_string = str(qs.query).lower()
  169. self.assertEqual(query_string.count(" between "), 0)
  170. self.assertEqual(query_string.count("extract"), 3)
  171. def test_extract_year_greaterthan_lookup(self):
  172. start_datetime = datetime(2015, 6, 15, 14, 10)
  173. end_datetime = datetime(2016, 6, 15, 14, 10)
  174. if settings.USE_TZ:
  175. start_datetime = timezone.make_aware(start_datetime)
  176. end_datetime = timezone.make_aware(end_datetime)
  177. self.create_model(start_datetime, end_datetime)
  178. self.create_model(end_datetime, start_datetime)
  179. for lookup in ("year", "iso_year"):
  180. with self.subTest(lookup):
  181. qs = DTModel.objects.filter(**{"start_datetime__%s__gt" % lookup: 2015})
  182. self.assertEqual(qs.count(), 1)
  183. self.assertEqual(str(qs.query).lower().count("extract"), 0)
  184. qs = DTModel.objects.filter(
  185. **{"start_datetime__%s__gte" % lookup: 2015}
  186. )
  187. self.assertEqual(qs.count(), 2)
  188. self.assertEqual(str(qs.query).lower().count("extract"), 0)
  189. qs = DTModel.objects.annotate(
  190. start_year=ExtractYear("start_datetime"),
  191. ).filter(**{"end_datetime__%s__gte" % lookup: F("start_year")})
  192. self.assertEqual(qs.count(), 1)
  193. self.assertGreaterEqual(str(qs.query).lower().count("extract"), 2)
  194. def test_extract_year_lessthan_lookup(self):
  195. start_datetime = datetime(2015, 6, 15, 14, 10)
  196. end_datetime = datetime(2016, 6, 15, 14, 10)
  197. if settings.USE_TZ:
  198. start_datetime = timezone.make_aware(start_datetime)
  199. end_datetime = timezone.make_aware(end_datetime)
  200. self.create_model(start_datetime, end_datetime)
  201. self.create_model(end_datetime, start_datetime)
  202. for lookup in ("year", "iso_year"):
  203. with self.subTest(lookup):
  204. qs = DTModel.objects.filter(**{"start_datetime__%s__lt" % lookup: 2016})
  205. self.assertEqual(qs.count(), 1)
  206. self.assertEqual(str(qs.query).count("extract"), 0)
  207. qs = DTModel.objects.filter(
  208. **{"start_datetime__%s__lte" % lookup: 2016}
  209. )
  210. self.assertEqual(qs.count(), 2)
  211. self.assertEqual(str(qs.query).count("extract"), 0)
  212. qs = DTModel.objects.annotate(
  213. end_year=ExtractYear("end_datetime"),
  214. ).filter(**{"start_datetime__%s__lte" % lookup: F("end_year")})
  215. self.assertEqual(qs.count(), 1)
  216. self.assertGreaterEqual(str(qs.query).lower().count("extract"), 2)
  217. def test_extract_func(self):
  218. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  219. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  220. if settings.USE_TZ:
  221. start_datetime = timezone.make_aware(start_datetime)
  222. end_datetime = timezone.make_aware(end_datetime)
  223. self.create_model(start_datetime, end_datetime)
  224. self.create_model(end_datetime, start_datetime)
  225. with self.assertRaisesMessage(ValueError, "lookup_name must be provided"):
  226. Extract("start_datetime")
  227. msg = (
  228. "Extract input expression must be DateField, DateTimeField, TimeField, or "
  229. "DurationField."
  230. )
  231. with self.assertRaisesMessage(ValueError, msg):
  232. list(DTModel.objects.annotate(extracted=Extract("name", "hour")))
  233. with self.assertRaisesMessage(
  234. ValueError,
  235. "Cannot extract time component 'second' from DateField 'start_date'.",
  236. ):
  237. list(DTModel.objects.annotate(extracted=Extract("start_date", "second")))
  238. self.assertQuerysetEqual(
  239. DTModel.objects.annotate(
  240. extracted=Extract("start_datetime", "year")
  241. ).order_by("start_datetime"),
  242. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  243. lambda m: (m.start_datetime, m.extracted),
  244. )
  245. self.assertQuerysetEqual(
  246. DTModel.objects.annotate(
  247. extracted=Extract("start_datetime", "quarter")
  248. ).order_by("start_datetime"),
  249. [(start_datetime, 2), (end_datetime, 2)],
  250. lambda m: (m.start_datetime, m.extracted),
  251. )
  252. self.assertQuerysetEqual(
  253. DTModel.objects.annotate(
  254. extracted=Extract("start_datetime", "month")
  255. ).order_by("start_datetime"),
  256. [
  257. (start_datetime, start_datetime.month),
  258. (end_datetime, end_datetime.month),
  259. ],
  260. lambda m: (m.start_datetime, m.extracted),
  261. )
  262. self.assertQuerysetEqual(
  263. DTModel.objects.annotate(
  264. extracted=Extract("start_datetime", "day")
  265. ).order_by("start_datetime"),
  266. [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
  267. lambda m: (m.start_datetime, m.extracted),
  268. )
  269. self.assertQuerysetEqual(
  270. DTModel.objects.annotate(
  271. extracted=Extract("start_datetime", "week")
  272. ).order_by("start_datetime"),
  273. [(start_datetime, 25), (end_datetime, 24)],
  274. lambda m: (m.start_datetime, m.extracted),
  275. )
  276. self.assertQuerysetEqual(
  277. DTModel.objects.annotate(
  278. extracted=Extract("start_datetime", "week_day")
  279. ).order_by("start_datetime"),
  280. [
  281. (start_datetime, (start_datetime.isoweekday() % 7) + 1),
  282. (end_datetime, (end_datetime.isoweekday() % 7) + 1),
  283. ],
  284. lambda m: (m.start_datetime, m.extracted),
  285. )
  286. self.assertQuerysetEqual(
  287. DTModel.objects.annotate(
  288. extracted=Extract("start_datetime", "iso_week_day"),
  289. ).order_by("start_datetime"),
  290. [
  291. (start_datetime, start_datetime.isoweekday()),
  292. (end_datetime, end_datetime.isoweekday()),
  293. ],
  294. lambda m: (m.start_datetime, m.extracted),
  295. )
  296. self.assertQuerysetEqual(
  297. DTModel.objects.annotate(
  298. extracted=Extract("start_datetime", "hour")
  299. ).order_by("start_datetime"),
  300. [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
  301. lambda m: (m.start_datetime, m.extracted),
  302. )
  303. self.assertQuerysetEqual(
  304. DTModel.objects.annotate(
  305. extracted=Extract("start_datetime", "minute")
  306. ).order_by("start_datetime"),
  307. [
  308. (start_datetime, start_datetime.minute),
  309. (end_datetime, end_datetime.minute),
  310. ],
  311. lambda m: (m.start_datetime, m.extracted),
  312. )
  313. self.assertQuerysetEqual(
  314. DTModel.objects.annotate(
  315. extracted=Extract("start_datetime", "second")
  316. ).order_by("start_datetime"),
  317. [
  318. (start_datetime, start_datetime.second),
  319. (end_datetime, end_datetime.second),
  320. ],
  321. lambda m: (m.start_datetime, m.extracted),
  322. )
  323. self.assertEqual(
  324. DTModel.objects.filter(
  325. start_datetime__year=Extract("start_datetime", "year")
  326. ).count(),
  327. 2,
  328. )
  329. self.assertEqual(
  330. DTModel.objects.filter(
  331. start_datetime__hour=Extract("start_datetime", "hour")
  332. ).count(),
  333. 2,
  334. )
  335. self.assertEqual(
  336. DTModel.objects.filter(
  337. start_date__month=Extract("start_date", "month")
  338. ).count(),
  339. 2,
  340. )
  341. self.assertEqual(
  342. DTModel.objects.filter(
  343. start_time__hour=Extract("start_time", "hour")
  344. ).count(),
  345. 2,
  346. )
  347. def test_extract_none(self):
  348. self.create_model(None, None)
  349. for t in (
  350. Extract("start_datetime", "year"),
  351. Extract("start_date", "year"),
  352. Extract("start_time", "hour"),
  353. ):
  354. with self.subTest(t):
  355. self.assertIsNone(
  356. DTModel.objects.annotate(extracted=t).first().extracted
  357. )
  358. def test_extract_outerref_validation(self):
  359. inner_qs = DTModel.objects.filter(name=ExtractMonth(OuterRef("name")))
  360. msg = (
  361. "Extract input expression must be DateField, DateTimeField, "
  362. "TimeField, or DurationField."
  363. )
  364. with self.assertRaisesMessage(ValueError, msg):
  365. DTModel.objects.annotate(related_name=Subquery(inner_qs.values("name")[:1]))
  366. @skipUnlessDBFeature("has_native_duration_field")
  367. def test_extract_duration(self):
  368. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  369. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  370. if settings.USE_TZ:
  371. start_datetime = timezone.make_aware(start_datetime)
  372. end_datetime = timezone.make_aware(end_datetime)
  373. self.create_model(start_datetime, end_datetime)
  374. self.create_model(end_datetime, start_datetime)
  375. self.assertQuerysetEqual(
  376. DTModel.objects.annotate(extracted=Extract("duration", "second")).order_by(
  377. "start_datetime"
  378. ),
  379. [
  380. (start_datetime, (end_datetime - start_datetime).seconds % 60),
  381. (end_datetime, (start_datetime - end_datetime).seconds % 60),
  382. ],
  383. lambda m: (m.start_datetime, m.extracted),
  384. )
  385. self.assertEqual(
  386. DTModel.objects.annotate(
  387. duration_days=Extract("duration", "day"),
  388. )
  389. .filter(duration_days__gt=200)
  390. .count(),
  391. 1,
  392. )
  393. @skipIfDBFeature("has_native_duration_field")
  394. def test_extract_duration_without_native_duration_field(self):
  395. msg = "Extract requires native DurationField database support."
  396. with self.assertRaisesMessage(ValueError, msg):
  397. list(DTModel.objects.annotate(extracted=Extract("duration", "second")))
  398. def test_extract_duration_unsupported_lookups(self):
  399. msg = "Cannot extract component '%s' from DurationField 'duration'."
  400. for lookup in (
  401. "year",
  402. "iso_year",
  403. "month",
  404. "week",
  405. "week_day",
  406. "iso_week_day",
  407. "quarter",
  408. ):
  409. with self.subTest(lookup):
  410. with self.assertRaisesMessage(ValueError, msg % lookup):
  411. DTModel.objects.annotate(extracted=Extract("duration", lookup))
  412. def test_extract_year_func(self):
  413. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  414. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  415. if settings.USE_TZ:
  416. start_datetime = timezone.make_aware(start_datetime)
  417. end_datetime = timezone.make_aware(end_datetime)
  418. self.create_model(start_datetime, end_datetime)
  419. self.create_model(end_datetime, start_datetime)
  420. self.assertQuerysetEqual(
  421. DTModel.objects.annotate(extracted=ExtractYear("start_datetime")).order_by(
  422. "start_datetime"
  423. ),
  424. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  425. lambda m: (m.start_datetime, m.extracted),
  426. )
  427. self.assertQuerysetEqual(
  428. DTModel.objects.annotate(extracted=ExtractYear("start_date")).order_by(
  429. "start_datetime"
  430. ),
  431. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  432. lambda m: (m.start_datetime, m.extracted),
  433. )
  434. self.assertEqual(
  435. DTModel.objects.filter(
  436. start_datetime__year=ExtractYear("start_datetime")
  437. ).count(),
  438. 2,
  439. )
  440. def test_extract_iso_year_func(self):
  441. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  442. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  443. if settings.USE_TZ:
  444. start_datetime = timezone.make_aware(start_datetime)
  445. end_datetime = timezone.make_aware(end_datetime)
  446. self.create_model(start_datetime, end_datetime)
  447. self.create_model(end_datetime, start_datetime)
  448. self.assertQuerysetEqual(
  449. DTModel.objects.annotate(
  450. extracted=ExtractIsoYear("start_datetime")
  451. ).order_by("start_datetime"),
  452. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  453. lambda m: (m.start_datetime, m.extracted),
  454. )
  455. self.assertQuerysetEqual(
  456. DTModel.objects.annotate(extracted=ExtractIsoYear("start_date")).order_by(
  457. "start_datetime"
  458. ),
  459. [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
  460. lambda m: (m.start_datetime, m.extracted),
  461. )
  462. # Both dates are from the same week year.
  463. self.assertEqual(
  464. DTModel.objects.filter(
  465. start_datetime__iso_year=ExtractIsoYear("start_datetime")
  466. ).count(),
  467. 2,
  468. )
  469. def test_extract_iso_year_func_boundaries(self):
  470. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  471. if settings.USE_TZ:
  472. end_datetime = timezone.make_aware(end_datetime)
  473. week_52_day_2014 = datetime(2014, 12, 27, 13, 0) # Sunday
  474. week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
  475. week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
  476. if settings.USE_TZ:
  477. week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015)
  478. week_52_day_2014 = timezone.make_aware(week_52_day_2014)
  479. week_53_day_2015 = timezone.make_aware(week_53_day_2015)
  480. days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
  481. obj_1_iso_2014 = self.create_model(week_52_day_2014, end_datetime)
  482. obj_1_iso_2015 = self.create_model(week_1_day_2014_2015, end_datetime)
  483. obj_2_iso_2015 = self.create_model(week_53_day_2015, end_datetime)
  484. qs = (
  485. DTModel.objects.filter(start_datetime__in=days)
  486. .annotate(
  487. extracted=ExtractIsoYear("start_datetime"),
  488. )
  489. .order_by("start_datetime")
  490. )
  491. self.assertQuerysetEqual(
  492. qs,
  493. [
  494. (week_52_day_2014, 2014),
  495. (week_1_day_2014_2015, 2015),
  496. (week_53_day_2015, 2015),
  497. ],
  498. lambda m: (m.start_datetime, m.extracted),
  499. )
  500. qs = DTModel.objects.filter(
  501. start_datetime__iso_year=2015,
  502. ).order_by("start_datetime")
  503. self.assertSequenceEqual(qs, [obj_1_iso_2015, obj_2_iso_2015])
  504. qs = DTModel.objects.filter(
  505. start_datetime__iso_year__gt=2014,
  506. ).order_by("start_datetime")
  507. self.assertSequenceEqual(qs, [obj_1_iso_2015, obj_2_iso_2015])
  508. qs = DTModel.objects.filter(
  509. start_datetime__iso_year__lte=2014,
  510. ).order_by("start_datetime")
  511. self.assertSequenceEqual(qs, [obj_1_iso_2014])
  512. def test_extract_month_func(self):
  513. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  514. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  515. if settings.USE_TZ:
  516. start_datetime = timezone.make_aware(start_datetime)
  517. end_datetime = timezone.make_aware(end_datetime)
  518. self.create_model(start_datetime, end_datetime)
  519. self.create_model(end_datetime, start_datetime)
  520. self.assertQuerysetEqual(
  521. DTModel.objects.annotate(extracted=ExtractMonth("start_datetime")).order_by(
  522. "start_datetime"
  523. ),
  524. [
  525. (start_datetime, start_datetime.month),
  526. (end_datetime, end_datetime.month),
  527. ],
  528. lambda m: (m.start_datetime, m.extracted),
  529. )
  530. self.assertQuerysetEqual(
  531. DTModel.objects.annotate(extracted=ExtractMonth("start_date")).order_by(
  532. "start_datetime"
  533. ),
  534. [
  535. (start_datetime, start_datetime.month),
  536. (end_datetime, end_datetime.month),
  537. ],
  538. lambda m: (m.start_datetime, m.extracted),
  539. )
  540. self.assertEqual(
  541. DTModel.objects.filter(
  542. start_datetime__month=ExtractMonth("start_datetime")
  543. ).count(),
  544. 2,
  545. )
  546. def test_extract_day_func(self):
  547. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  548. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  549. if settings.USE_TZ:
  550. start_datetime = timezone.make_aware(start_datetime)
  551. end_datetime = timezone.make_aware(end_datetime)
  552. self.create_model(start_datetime, end_datetime)
  553. self.create_model(end_datetime, start_datetime)
  554. self.assertQuerysetEqual(
  555. DTModel.objects.annotate(extracted=ExtractDay("start_datetime")).order_by(
  556. "start_datetime"
  557. ),
  558. [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
  559. lambda m: (m.start_datetime, m.extracted),
  560. )
  561. self.assertQuerysetEqual(
  562. DTModel.objects.annotate(extracted=ExtractDay("start_date")).order_by(
  563. "start_datetime"
  564. ),
  565. [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
  566. lambda m: (m.start_datetime, m.extracted),
  567. )
  568. self.assertEqual(
  569. DTModel.objects.filter(
  570. start_datetime__day=ExtractDay("start_datetime")
  571. ).count(),
  572. 2,
  573. )
  574. def test_extract_week_func(self):
  575. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  576. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  577. if settings.USE_TZ:
  578. start_datetime = timezone.make_aware(start_datetime)
  579. end_datetime = timezone.make_aware(end_datetime)
  580. self.create_model(start_datetime, end_datetime)
  581. self.create_model(end_datetime, start_datetime)
  582. self.assertQuerysetEqual(
  583. DTModel.objects.annotate(extracted=ExtractWeek("start_datetime")).order_by(
  584. "start_datetime"
  585. ),
  586. [(start_datetime, 25), (end_datetime, 24)],
  587. lambda m: (m.start_datetime, m.extracted),
  588. )
  589. self.assertQuerysetEqual(
  590. DTModel.objects.annotate(extracted=ExtractWeek("start_date")).order_by(
  591. "start_datetime"
  592. ),
  593. [(start_datetime, 25), (end_datetime, 24)],
  594. lambda m: (m.start_datetime, m.extracted),
  595. )
  596. # both dates are from the same week.
  597. self.assertEqual(
  598. DTModel.objects.filter(
  599. start_datetime__week=ExtractWeek("start_datetime")
  600. ).count(),
  601. 2,
  602. )
  603. def test_extract_quarter_func(self):
  604. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  605. end_datetime = datetime(2016, 8, 15, 14, 10, 50, 123)
  606. if settings.USE_TZ:
  607. start_datetime = timezone.make_aware(start_datetime)
  608. end_datetime = timezone.make_aware(end_datetime)
  609. self.create_model(start_datetime, end_datetime)
  610. self.create_model(end_datetime, start_datetime)
  611. self.assertQuerysetEqual(
  612. DTModel.objects.annotate(
  613. extracted=ExtractQuarter("start_datetime")
  614. ).order_by("start_datetime"),
  615. [(start_datetime, 2), (end_datetime, 3)],
  616. lambda m: (m.start_datetime, m.extracted),
  617. )
  618. self.assertQuerysetEqual(
  619. DTModel.objects.annotate(extracted=ExtractQuarter("start_date")).order_by(
  620. "start_datetime"
  621. ),
  622. [(start_datetime, 2), (end_datetime, 3)],
  623. lambda m: (m.start_datetime, m.extracted),
  624. )
  625. self.assertEqual(
  626. DTModel.objects.filter(
  627. start_datetime__quarter=ExtractQuarter("start_datetime")
  628. ).count(),
  629. 2,
  630. )
  631. def test_extract_quarter_func_boundaries(self):
  632. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  633. if settings.USE_TZ:
  634. end_datetime = timezone.make_aware(end_datetime)
  635. last_quarter_2014 = datetime(2014, 12, 31, 13, 0)
  636. first_quarter_2015 = datetime(2015, 1, 1, 13, 0)
  637. if settings.USE_TZ:
  638. last_quarter_2014 = timezone.make_aware(last_quarter_2014)
  639. first_quarter_2015 = timezone.make_aware(first_quarter_2015)
  640. dates = [last_quarter_2014, first_quarter_2015]
  641. self.create_model(last_quarter_2014, end_datetime)
  642. self.create_model(first_quarter_2015, end_datetime)
  643. qs = (
  644. DTModel.objects.filter(start_datetime__in=dates)
  645. .annotate(
  646. extracted=ExtractQuarter("start_datetime"),
  647. )
  648. .order_by("start_datetime")
  649. )
  650. self.assertQuerysetEqual(
  651. qs,
  652. [
  653. (last_quarter_2014, 4),
  654. (first_quarter_2015, 1),
  655. ],
  656. lambda m: (m.start_datetime, m.extracted),
  657. )
  658. def test_extract_week_func_boundaries(self):
  659. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  660. if settings.USE_TZ:
  661. end_datetime = timezone.make_aware(end_datetime)
  662. week_52_day_2014 = datetime(2014, 12, 27, 13, 0) # Sunday
  663. week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
  664. week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
  665. if settings.USE_TZ:
  666. week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015)
  667. week_52_day_2014 = timezone.make_aware(week_52_day_2014)
  668. week_53_day_2015 = timezone.make_aware(week_53_day_2015)
  669. days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
  670. self.create_model(week_53_day_2015, end_datetime)
  671. self.create_model(week_52_day_2014, end_datetime)
  672. self.create_model(week_1_day_2014_2015, end_datetime)
  673. qs = (
  674. DTModel.objects.filter(start_datetime__in=days)
  675. .annotate(
  676. extracted=ExtractWeek("start_datetime"),
  677. )
  678. .order_by("start_datetime")
  679. )
  680. self.assertQuerysetEqual(
  681. qs,
  682. [
  683. (week_52_day_2014, 52),
  684. (week_1_day_2014_2015, 1),
  685. (week_53_day_2015, 53),
  686. ],
  687. lambda m: (m.start_datetime, m.extracted),
  688. )
  689. def test_extract_weekday_func(self):
  690. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  691. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  692. if settings.USE_TZ:
  693. start_datetime = timezone.make_aware(start_datetime)
  694. end_datetime = timezone.make_aware(end_datetime)
  695. self.create_model(start_datetime, end_datetime)
  696. self.create_model(end_datetime, start_datetime)
  697. self.assertQuerysetEqual(
  698. DTModel.objects.annotate(
  699. extracted=ExtractWeekDay("start_datetime")
  700. ).order_by("start_datetime"),
  701. [
  702. (start_datetime, (start_datetime.isoweekday() % 7) + 1),
  703. (end_datetime, (end_datetime.isoweekday() % 7) + 1),
  704. ],
  705. lambda m: (m.start_datetime, m.extracted),
  706. )
  707. self.assertQuerysetEqual(
  708. DTModel.objects.annotate(extracted=ExtractWeekDay("start_date")).order_by(
  709. "start_datetime"
  710. ),
  711. [
  712. (start_datetime, (start_datetime.isoweekday() % 7) + 1),
  713. (end_datetime, (end_datetime.isoweekday() % 7) + 1),
  714. ],
  715. lambda m: (m.start_datetime, m.extracted),
  716. )
  717. self.assertEqual(
  718. DTModel.objects.filter(
  719. start_datetime__week_day=ExtractWeekDay("start_datetime")
  720. ).count(),
  721. 2,
  722. )
  723. def test_extract_iso_weekday_func(self):
  724. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  725. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  726. if settings.USE_TZ:
  727. start_datetime = timezone.make_aware(start_datetime)
  728. end_datetime = timezone.make_aware(end_datetime)
  729. self.create_model(start_datetime, end_datetime)
  730. self.create_model(end_datetime, start_datetime)
  731. self.assertQuerysetEqual(
  732. DTModel.objects.annotate(
  733. extracted=ExtractIsoWeekDay("start_datetime"),
  734. ).order_by("start_datetime"),
  735. [
  736. (start_datetime, start_datetime.isoweekday()),
  737. (end_datetime, end_datetime.isoweekday()),
  738. ],
  739. lambda m: (m.start_datetime, m.extracted),
  740. )
  741. self.assertQuerysetEqual(
  742. DTModel.objects.annotate(
  743. extracted=ExtractIsoWeekDay("start_date"),
  744. ).order_by("start_datetime"),
  745. [
  746. (start_datetime, start_datetime.isoweekday()),
  747. (end_datetime, end_datetime.isoweekday()),
  748. ],
  749. lambda m: (m.start_datetime, m.extracted),
  750. )
  751. self.assertEqual(
  752. DTModel.objects.filter(
  753. start_datetime__week_day=ExtractWeekDay("start_datetime"),
  754. ).count(),
  755. 2,
  756. )
  757. def test_extract_hour_func(self):
  758. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  759. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  760. if settings.USE_TZ:
  761. start_datetime = timezone.make_aware(start_datetime)
  762. end_datetime = timezone.make_aware(end_datetime)
  763. self.create_model(start_datetime, end_datetime)
  764. self.create_model(end_datetime, start_datetime)
  765. self.assertQuerysetEqual(
  766. DTModel.objects.annotate(extracted=ExtractHour("start_datetime")).order_by(
  767. "start_datetime"
  768. ),
  769. [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
  770. lambda m: (m.start_datetime, m.extracted),
  771. )
  772. self.assertQuerysetEqual(
  773. DTModel.objects.annotate(extracted=ExtractHour("start_time")).order_by(
  774. "start_datetime"
  775. ),
  776. [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
  777. lambda m: (m.start_datetime, m.extracted),
  778. )
  779. self.assertEqual(
  780. DTModel.objects.filter(
  781. start_datetime__hour=ExtractHour("start_datetime")
  782. ).count(),
  783. 2,
  784. )
  785. def test_extract_minute_func(self):
  786. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  787. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  788. if settings.USE_TZ:
  789. start_datetime = timezone.make_aware(start_datetime)
  790. end_datetime = timezone.make_aware(end_datetime)
  791. self.create_model(start_datetime, end_datetime)
  792. self.create_model(end_datetime, start_datetime)
  793. self.assertQuerysetEqual(
  794. DTModel.objects.annotate(
  795. extracted=ExtractMinute("start_datetime")
  796. ).order_by("start_datetime"),
  797. [
  798. (start_datetime, start_datetime.minute),
  799. (end_datetime, end_datetime.minute),
  800. ],
  801. lambda m: (m.start_datetime, m.extracted),
  802. )
  803. self.assertQuerysetEqual(
  804. DTModel.objects.annotate(extracted=ExtractMinute("start_time")).order_by(
  805. "start_datetime"
  806. ),
  807. [
  808. (start_datetime, start_datetime.minute),
  809. (end_datetime, end_datetime.minute),
  810. ],
  811. lambda m: (m.start_datetime, m.extracted),
  812. )
  813. self.assertEqual(
  814. DTModel.objects.filter(
  815. start_datetime__minute=ExtractMinute("start_datetime")
  816. ).count(),
  817. 2,
  818. )
  819. def test_extract_second_func(self):
  820. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  821. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  822. if settings.USE_TZ:
  823. start_datetime = timezone.make_aware(start_datetime)
  824. end_datetime = timezone.make_aware(end_datetime)
  825. self.create_model(start_datetime, end_datetime)
  826. self.create_model(end_datetime, start_datetime)
  827. self.assertQuerysetEqual(
  828. DTModel.objects.annotate(
  829. extracted=ExtractSecond("start_datetime")
  830. ).order_by("start_datetime"),
  831. [
  832. (start_datetime, start_datetime.second),
  833. (end_datetime, end_datetime.second),
  834. ],
  835. lambda m: (m.start_datetime, m.extracted),
  836. )
  837. self.assertQuerysetEqual(
  838. DTModel.objects.annotate(extracted=ExtractSecond("start_time")).order_by(
  839. "start_datetime"
  840. ),
  841. [
  842. (start_datetime, start_datetime.second),
  843. (end_datetime, end_datetime.second),
  844. ],
  845. lambda m: (m.start_datetime, m.extracted),
  846. )
  847. self.assertEqual(
  848. DTModel.objects.filter(
  849. start_datetime__second=ExtractSecond("start_datetime")
  850. ).count(),
  851. 2,
  852. )
  853. def test_extract_second_func_no_fractional(self):
  854. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  855. end_datetime = datetime(2016, 6, 15, 14, 30, 50, 783)
  856. if settings.USE_TZ:
  857. start_datetime = timezone.make_aware(start_datetime)
  858. end_datetime = timezone.make_aware(end_datetime)
  859. obj = self.create_model(start_datetime, end_datetime)
  860. self.assertSequenceEqual(
  861. DTModel.objects.filter(start_datetime__second=F("end_datetime__second")),
  862. [obj],
  863. )
  864. self.assertSequenceEqual(
  865. DTModel.objects.filter(start_time__second=F("end_time__second")),
  866. [obj],
  867. )
  868. def test_trunc_func(self):
  869. start_datetime = datetime(999, 6, 15, 14, 30, 50, 321)
  870. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  871. if settings.USE_TZ:
  872. start_datetime = timezone.make_aware(start_datetime)
  873. end_datetime = timezone.make_aware(end_datetime)
  874. self.create_model(start_datetime, end_datetime)
  875. self.create_model(end_datetime, start_datetime)
  876. def test_datetime_kind(kind):
  877. self.assertQuerysetEqual(
  878. DTModel.objects.annotate(
  879. truncated=Trunc(
  880. "start_datetime", kind, output_field=DateTimeField()
  881. )
  882. ).order_by("start_datetime"),
  883. [
  884. (start_datetime, truncate_to(start_datetime, kind)),
  885. (end_datetime, truncate_to(end_datetime, kind)),
  886. ],
  887. lambda m: (m.start_datetime, m.truncated),
  888. )
  889. def test_date_kind(kind):
  890. self.assertQuerysetEqual(
  891. DTModel.objects.annotate(
  892. truncated=Trunc("start_date", kind, output_field=DateField())
  893. ).order_by("start_datetime"),
  894. [
  895. (start_datetime, truncate_to(start_datetime.date(), kind)),
  896. (end_datetime, truncate_to(end_datetime.date(), kind)),
  897. ],
  898. lambda m: (m.start_datetime, m.truncated),
  899. )
  900. def test_time_kind(kind):
  901. self.assertQuerysetEqual(
  902. DTModel.objects.annotate(
  903. truncated=Trunc("start_time", kind, output_field=TimeField())
  904. ).order_by("start_datetime"),
  905. [
  906. (start_datetime, truncate_to(start_datetime.time(), kind)),
  907. (end_datetime, truncate_to(end_datetime.time(), kind)),
  908. ],
  909. lambda m: (m.start_datetime, m.truncated),
  910. )
  911. def test_datetime_to_time_kind(kind):
  912. self.assertQuerysetEqual(
  913. DTModel.objects.annotate(
  914. truncated=Trunc("start_datetime", kind, output_field=TimeField()),
  915. ).order_by("start_datetime"),
  916. [
  917. (start_datetime, truncate_to(start_datetime.time(), kind)),
  918. (end_datetime, truncate_to(end_datetime.time(), kind)),
  919. ],
  920. lambda m: (m.start_datetime, m.truncated),
  921. )
  922. test_date_kind("year")
  923. test_date_kind("quarter")
  924. test_date_kind("month")
  925. test_date_kind("day")
  926. test_time_kind("hour")
  927. test_time_kind("minute")
  928. test_time_kind("second")
  929. test_datetime_kind("year")
  930. test_datetime_kind("quarter")
  931. test_datetime_kind("month")
  932. test_datetime_kind("day")
  933. test_datetime_kind("hour")
  934. test_datetime_kind("minute")
  935. test_datetime_kind("second")
  936. test_datetime_to_time_kind("hour")
  937. test_datetime_to_time_kind("minute")
  938. test_datetime_to_time_kind("second")
  939. qs = DTModel.objects.filter(
  940. start_datetime__date=Trunc(
  941. "start_datetime", "day", output_field=DateField()
  942. )
  943. )
  944. self.assertEqual(qs.count(), 2)
  945. def _test_trunc_week(self, start_datetime, end_datetime):
  946. if settings.USE_TZ:
  947. start_datetime = timezone.make_aware(start_datetime)
  948. end_datetime = timezone.make_aware(end_datetime)
  949. self.create_model(start_datetime, end_datetime)
  950. self.create_model(end_datetime, start_datetime)
  951. self.assertQuerysetEqual(
  952. DTModel.objects.annotate(
  953. truncated=Trunc("start_datetime", "week", output_field=DateTimeField())
  954. ).order_by("start_datetime"),
  955. [
  956. (start_datetime, truncate_to(start_datetime, "week")),
  957. (end_datetime, truncate_to(end_datetime, "week")),
  958. ],
  959. lambda m: (m.start_datetime, m.truncated),
  960. )
  961. self.assertQuerysetEqual(
  962. DTModel.objects.annotate(
  963. truncated=Trunc("start_date", "week", output_field=DateField())
  964. ).order_by("start_datetime"),
  965. [
  966. (start_datetime, truncate_to(start_datetime.date(), "week")),
  967. (end_datetime, truncate_to(end_datetime.date(), "week")),
  968. ],
  969. lambda m: (m.start_datetime, m.truncated),
  970. )
  971. def test_trunc_week(self):
  972. self._test_trunc_week(
  973. start_datetime=datetime(2015, 6, 15, 14, 30, 50, 321),
  974. end_datetime=datetime(2016, 6, 15, 14, 10, 50, 123),
  975. )
  976. def test_trunc_week_before_1000(self):
  977. self._test_trunc_week(
  978. start_datetime=datetime(999, 6, 15, 14, 30, 50, 321),
  979. end_datetime=datetime(2016, 6, 15, 14, 10, 50, 123),
  980. )
  981. def test_trunc_invalid_arguments(self):
  982. msg = "output_field must be either DateField, TimeField, or DateTimeField"
  983. with self.assertRaisesMessage(ValueError, msg):
  984. list(
  985. DTModel.objects.annotate(
  986. truncated=Trunc(
  987. "start_datetime", "year", output_field=IntegerField()
  988. ),
  989. )
  990. )
  991. msg = "'name' isn't a DateField, TimeField, or DateTimeField."
  992. with self.assertRaisesMessage(TypeError, msg):
  993. list(
  994. DTModel.objects.annotate(
  995. truncated=Trunc("name", "year", output_field=DateTimeField()),
  996. )
  997. )
  998. msg = "Cannot truncate DateField 'start_date' to DateTimeField"
  999. with self.assertRaisesMessage(ValueError, msg):
  1000. list(DTModel.objects.annotate(truncated=Trunc("start_date", "second")))
  1001. with self.assertRaisesMessage(ValueError, msg):
  1002. list(
  1003. DTModel.objects.annotate(
  1004. truncated=Trunc(
  1005. "start_date", "month", output_field=DateTimeField()
  1006. ),
  1007. )
  1008. )
  1009. msg = "Cannot truncate TimeField 'start_time' to DateTimeField"
  1010. with self.assertRaisesMessage(ValueError, msg):
  1011. list(DTModel.objects.annotate(truncated=Trunc("start_time", "month")))
  1012. with self.assertRaisesMessage(ValueError, msg):
  1013. list(
  1014. DTModel.objects.annotate(
  1015. truncated=Trunc(
  1016. "start_time", "second", output_field=DateTimeField()
  1017. ),
  1018. )
  1019. )
  1020. def test_trunc_none(self):
  1021. self.create_model(None, None)
  1022. for t in (
  1023. Trunc("start_datetime", "year"),
  1024. Trunc("start_date", "year"),
  1025. Trunc("start_time", "hour"),
  1026. ):
  1027. with self.subTest(t):
  1028. self.assertIsNone(
  1029. DTModel.objects.annotate(truncated=t).first().truncated
  1030. )
  1031. def test_trunc_year_func(self):
  1032. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1033. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "year")
  1034. if settings.USE_TZ:
  1035. start_datetime = timezone.make_aware(start_datetime)
  1036. end_datetime = timezone.make_aware(end_datetime)
  1037. self.create_model(start_datetime, end_datetime)
  1038. self.create_model(end_datetime, start_datetime)
  1039. self.assertQuerysetEqual(
  1040. DTModel.objects.annotate(extracted=TruncYear("start_datetime")).order_by(
  1041. "start_datetime"
  1042. ),
  1043. [
  1044. (start_datetime, truncate_to(start_datetime, "year")),
  1045. (end_datetime, truncate_to(end_datetime, "year")),
  1046. ],
  1047. lambda m: (m.start_datetime, m.extracted),
  1048. )
  1049. self.assertQuerysetEqual(
  1050. DTModel.objects.annotate(extracted=TruncYear("start_date")).order_by(
  1051. "start_datetime"
  1052. ),
  1053. [
  1054. (start_datetime, truncate_to(start_datetime.date(), "year")),
  1055. (end_datetime, truncate_to(end_datetime.date(), "year")),
  1056. ],
  1057. lambda m: (m.start_datetime, m.extracted),
  1058. )
  1059. self.assertEqual(
  1060. DTModel.objects.filter(start_datetime=TruncYear("start_datetime")).count(),
  1061. 1,
  1062. )
  1063. with self.assertRaisesMessage(
  1064. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1065. ):
  1066. list(DTModel.objects.annotate(truncated=TruncYear("start_time")))
  1067. with self.assertRaisesMessage(
  1068. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1069. ):
  1070. list(
  1071. DTModel.objects.annotate(
  1072. truncated=TruncYear("start_time", output_field=TimeField())
  1073. )
  1074. )
  1075. def test_trunc_quarter_func(self):
  1076. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1077. end_datetime = truncate_to(datetime(2016, 10, 15, 14, 10, 50, 123), "quarter")
  1078. last_quarter_2015 = truncate_to(
  1079. datetime(2015, 12, 31, 14, 10, 50, 123), "quarter"
  1080. )
  1081. first_quarter_2016 = truncate_to(
  1082. datetime(2016, 1, 1, 14, 10, 50, 123), "quarter"
  1083. )
  1084. if settings.USE_TZ:
  1085. start_datetime = timezone.make_aware(start_datetime)
  1086. end_datetime = timezone.make_aware(end_datetime)
  1087. last_quarter_2015 = timezone.make_aware(last_quarter_2015)
  1088. first_quarter_2016 = timezone.make_aware(first_quarter_2016)
  1089. self.create_model(start_datetime=start_datetime, end_datetime=end_datetime)
  1090. self.create_model(start_datetime=end_datetime, end_datetime=start_datetime)
  1091. self.create_model(start_datetime=last_quarter_2015, end_datetime=end_datetime)
  1092. self.create_model(start_datetime=first_quarter_2016, end_datetime=end_datetime)
  1093. self.assertQuerysetEqual(
  1094. DTModel.objects.annotate(extracted=TruncQuarter("start_date")).order_by(
  1095. "start_datetime"
  1096. ),
  1097. [
  1098. (start_datetime, truncate_to(start_datetime.date(), "quarter")),
  1099. (last_quarter_2015, truncate_to(last_quarter_2015.date(), "quarter")),
  1100. (first_quarter_2016, truncate_to(first_quarter_2016.date(), "quarter")),
  1101. (end_datetime, truncate_to(end_datetime.date(), "quarter")),
  1102. ],
  1103. lambda m: (m.start_datetime, m.extracted),
  1104. )
  1105. self.assertQuerysetEqual(
  1106. DTModel.objects.annotate(extracted=TruncQuarter("start_datetime")).order_by(
  1107. "start_datetime"
  1108. ),
  1109. [
  1110. (start_datetime, truncate_to(start_datetime, "quarter")),
  1111. (last_quarter_2015, truncate_to(last_quarter_2015, "quarter")),
  1112. (first_quarter_2016, truncate_to(first_quarter_2016, "quarter")),
  1113. (end_datetime, truncate_to(end_datetime, "quarter")),
  1114. ],
  1115. lambda m: (m.start_datetime, m.extracted),
  1116. )
  1117. with self.assertRaisesMessage(
  1118. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1119. ):
  1120. list(DTModel.objects.annotate(truncated=TruncQuarter("start_time")))
  1121. with self.assertRaisesMessage(
  1122. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1123. ):
  1124. list(
  1125. DTModel.objects.annotate(
  1126. truncated=TruncQuarter("start_time", output_field=TimeField())
  1127. )
  1128. )
  1129. def test_trunc_month_func(self):
  1130. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1131. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "month")
  1132. if settings.USE_TZ:
  1133. start_datetime = timezone.make_aware(start_datetime)
  1134. end_datetime = timezone.make_aware(end_datetime)
  1135. self.create_model(start_datetime, end_datetime)
  1136. self.create_model(end_datetime, start_datetime)
  1137. self.assertQuerysetEqual(
  1138. DTModel.objects.annotate(extracted=TruncMonth("start_datetime")).order_by(
  1139. "start_datetime"
  1140. ),
  1141. [
  1142. (start_datetime, truncate_to(start_datetime, "month")),
  1143. (end_datetime, truncate_to(end_datetime, "month")),
  1144. ],
  1145. lambda m: (m.start_datetime, m.extracted),
  1146. )
  1147. self.assertQuerysetEqual(
  1148. DTModel.objects.annotate(extracted=TruncMonth("start_date")).order_by(
  1149. "start_datetime"
  1150. ),
  1151. [
  1152. (start_datetime, truncate_to(start_datetime.date(), "month")),
  1153. (end_datetime, truncate_to(end_datetime.date(), "month")),
  1154. ],
  1155. lambda m: (m.start_datetime, m.extracted),
  1156. )
  1157. self.assertEqual(
  1158. DTModel.objects.filter(start_datetime=TruncMonth("start_datetime")).count(),
  1159. 1,
  1160. )
  1161. with self.assertRaisesMessage(
  1162. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1163. ):
  1164. list(DTModel.objects.annotate(truncated=TruncMonth("start_time")))
  1165. with self.assertRaisesMessage(
  1166. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1167. ):
  1168. list(
  1169. DTModel.objects.annotate(
  1170. truncated=TruncMonth("start_time", output_field=TimeField())
  1171. )
  1172. )
  1173. def test_trunc_week_func(self):
  1174. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1175. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "week")
  1176. if settings.USE_TZ:
  1177. start_datetime = timezone.make_aware(start_datetime)
  1178. end_datetime = timezone.make_aware(end_datetime)
  1179. self.create_model(start_datetime, end_datetime)
  1180. self.create_model(end_datetime, start_datetime)
  1181. self.assertQuerysetEqual(
  1182. DTModel.objects.annotate(extracted=TruncWeek("start_datetime")).order_by(
  1183. "start_datetime"
  1184. ),
  1185. [
  1186. (start_datetime, truncate_to(start_datetime, "week")),
  1187. (end_datetime, truncate_to(end_datetime, "week")),
  1188. ],
  1189. lambda m: (m.start_datetime, m.extracted),
  1190. )
  1191. self.assertEqual(
  1192. DTModel.objects.filter(start_datetime=TruncWeek("start_datetime")).count(),
  1193. 1,
  1194. )
  1195. with self.assertRaisesMessage(
  1196. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1197. ):
  1198. list(DTModel.objects.annotate(truncated=TruncWeek("start_time")))
  1199. with self.assertRaisesMessage(
  1200. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1201. ):
  1202. list(
  1203. DTModel.objects.annotate(
  1204. truncated=TruncWeek("start_time", output_field=TimeField())
  1205. )
  1206. )
  1207. def test_trunc_date_func(self):
  1208. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1209. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  1210. if settings.USE_TZ:
  1211. start_datetime = timezone.make_aware(start_datetime)
  1212. end_datetime = timezone.make_aware(end_datetime)
  1213. self.create_model(start_datetime, end_datetime)
  1214. self.create_model(end_datetime, start_datetime)
  1215. self.assertQuerysetEqual(
  1216. DTModel.objects.annotate(extracted=TruncDate("start_datetime")).order_by(
  1217. "start_datetime"
  1218. ),
  1219. [
  1220. (start_datetime, start_datetime.date()),
  1221. (end_datetime, end_datetime.date()),
  1222. ],
  1223. lambda m: (m.start_datetime, m.extracted),
  1224. )
  1225. self.assertEqual(
  1226. DTModel.objects.filter(
  1227. start_datetime__date=TruncDate("start_datetime")
  1228. ).count(),
  1229. 2,
  1230. )
  1231. with self.assertRaisesMessage(
  1232. ValueError, "Cannot truncate TimeField 'start_time' to DateField"
  1233. ):
  1234. list(DTModel.objects.annotate(truncated=TruncDate("start_time")))
  1235. with self.assertRaisesMessage(
  1236. ValueError, "Cannot truncate TimeField 'start_time' to DateField"
  1237. ):
  1238. list(
  1239. DTModel.objects.annotate(
  1240. truncated=TruncDate("start_time", output_field=TimeField())
  1241. )
  1242. )
  1243. def test_trunc_date_none(self):
  1244. self.create_model(None, None)
  1245. self.assertIsNone(
  1246. DTModel.objects.annotate(truncated=TruncDate("start_datetime"))
  1247. .first()
  1248. .truncated
  1249. )
  1250. def test_trunc_time_func(self):
  1251. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1252. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  1253. if settings.USE_TZ:
  1254. start_datetime = timezone.make_aware(start_datetime)
  1255. end_datetime = timezone.make_aware(end_datetime)
  1256. self.create_model(start_datetime, end_datetime)
  1257. self.create_model(end_datetime, start_datetime)
  1258. self.assertQuerysetEqual(
  1259. DTModel.objects.annotate(extracted=TruncTime("start_datetime")).order_by(
  1260. "start_datetime"
  1261. ),
  1262. [
  1263. (start_datetime, start_datetime.time()),
  1264. (end_datetime, end_datetime.time()),
  1265. ],
  1266. lambda m: (m.start_datetime, m.extracted),
  1267. )
  1268. self.assertEqual(
  1269. DTModel.objects.filter(
  1270. start_datetime__time=TruncTime("start_datetime")
  1271. ).count(),
  1272. 2,
  1273. )
  1274. with self.assertRaisesMessage(
  1275. ValueError, "Cannot truncate DateField 'start_date' to TimeField"
  1276. ):
  1277. list(DTModel.objects.annotate(truncated=TruncTime("start_date")))
  1278. with self.assertRaisesMessage(
  1279. ValueError, "Cannot truncate DateField 'start_date' to TimeField"
  1280. ):
  1281. list(
  1282. DTModel.objects.annotate(
  1283. truncated=TruncTime("start_date", output_field=DateField())
  1284. )
  1285. )
  1286. def test_trunc_time_none(self):
  1287. self.create_model(None, None)
  1288. self.assertIsNone(
  1289. DTModel.objects.annotate(truncated=TruncTime("start_datetime"))
  1290. .first()
  1291. .truncated
  1292. )
  1293. def test_trunc_time_comparison(self):
  1294. start_datetime = datetime(2015, 6, 15, 14, 30, 26) # 0 microseconds.
  1295. end_datetime = datetime(2015, 6, 15, 14, 30, 26, 321)
  1296. if settings.USE_TZ:
  1297. start_datetime = timezone.make_aware(start_datetime)
  1298. end_datetime = timezone.make_aware(end_datetime)
  1299. self.create_model(start_datetime, end_datetime)
  1300. self.assertIs(
  1301. DTModel.objects.filter(
  1302. start_datetime__time=start_datetime.time(),
  1303. end_datetime__time=end_datetime.time(),
  1304. ).exists(),
  1305. True,
  1306. )
  1307. self.assertIs(
  1308. DTModel.objects.annotate(
  1309. extracted_start=TruncTime("start_datetime"),
  1310. extracted_end=TruncTime("end_datetime"),
  1311. )
  1312. .filter(
  1313. extracted_start=start_datetime.time(),
  1314. extracted_end=end_datetime.time(),
  1315. )
  1316. .exists(),
  1317. True,
  1318. )
  1319. def test_trunc_day_func(self):
  1320. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1321. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "day")
  1322. if settings.USE_TZ:
  1323. start_datetime = timezone.make_aware(start_datetime)
  1324. end_datetime = timezone.make_aware(end_datetime)
  1325. self.create_model(start_datetime, end_datetime)
  1326. self.create_model(end_datetime, start_datetime)
  1327. self.assertQuerysetEqual(
  1328. DTModel.objects.annotate(extracted=TruncDay("start_datetime")).order_by(
  1329. "start_datetime"
  1330. ),
  1331. [
  1332. (start_datetime, truncate_to(start_datetime, "day")),
  1333. (end_datetime, truncate_to(end_datetime, "day")),
  1334. ],
  1335. lambda m: (m.start_datetime, m.extracted),
  1336. )
  1337. self.assertEqual(
  1338. DTModel.objects.filter(start_datetime=TruncDay("start_datetime")).count(), 1
  1339. )
  1340. with self.assertRaisesMessage(
  1341. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1342. ):
  1343. list(DTModel.objects.annotate(truncated=TruncDay("start_time")))
  1344. with self.assertRaisesMessage(
  1345. ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"
  1346. ):
  1347. list(
  1348. DTModel.objects.annotate(
  1349. truncated=TruncDay("start_time", output_field=TimeField())
  1350. )
  1351. )
  1352. def test_trunc_hour_func(self):
  1353. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1354. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "hour")
  1355. if settings.USE_TZ:
  1356. start_datetime = timezone.make_aware(start_datetime)
  1357. end_datetime = timezone.make_aware(end_datetime)
  1358. self.create_model(start_datetime, end_datetime)
  1359. self.create_model(end_datetime, start_datetime)
  1360. self.assertQuerysetEqual(
  1361. DTModel.objects.annotate(extracted=TruncHour("start_datetime")).order_by(
  1362. "start_datetime"
  1363. ),
  1364. [
  1365. (start_datetime, truncate_to(start_datetime, "hour")),
  1366. (end_datetime, truncate_to(end_datetime, "hour")),
  1367. ],
  1368. lambda m: (m.start_datetime, m.extracted),
  1369. )
  1370. self.assertQuerysetEqual(
  1371. DTModel.objects.annotate(extracted=TruncHour("start_time")).order_by(
  1372. "start_datetime"
  1373. ),
  1374. [
  1375. (start_datetime, truncate_to(start_datetime.time(), "hour")),
  1376. (end_datetime, truncate_to(end_datetime.time(), "hour")),
  1377. ],
  1378. lambda m: (m.start_datetime, m.extracted),
  1379. )
  1380. self.assertEqual(
  1381. DTModel.objects.filter(start_datetime=TruncHour("start_datetime")).count(),
  1382. 1,
  1383. )
  1384. with self.assertRaisesMessage(
  1385. ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
  1386. ):
  1387. list(DTModel.objects.annotate(truncated=TruncHour("start_date")))
  1388. with self.assertRaisesMessage(
  1389. ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
  1390. ):
  1391. list(
  1392. DTModel.objects.annotate(
  1393. truncated=TruncHour("start_date", output_field=DateField())
  1394. )
  1395. )
  1396. def test_trunc_minute_func(self):
  1397. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1398. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "minute")
  1399. if settings.USE_TZ:
  1400. start_datetime = timezone.make_aware(start_datetime)
  1401. end_datetime = timezone.make_aware(end_datetime)
  1402. self.create_model(start_datetime, end_datetime)
  1403. self.create_model(end_datetime, start_datetime)
  1404. self.assertQuerysetEqual(
  1405. DTModel.objects.annotate(extracted=TruncMinute("start_datetime")).order_by(
  1406. "start_datetime"
  1407. ),
  1408. [
  1409. (start_datetime, truncate_to(start_datetime, "minute")),
  1410. (end_datetime, truncate_to(end_datetime, "minute")),
  1411. ],
  1412. lambda m: (m.start_datetime, m.extracted),
  1413. )
  1414. self.assertQuerysetEqual(
  1415. DTModel.objects.annotate(extracted=TruncMinute("start_time")).order_by(
  1416. "start_datetime"
  1417. ),
  1418. [
  1419. (start_datetime, truncate_to(start_datetime.time(), "minute")),
  1420. (end_datetime, truncate_to(end_datetime.time(), "minute")),
  1421. ],
  1422. lambda m: (m.start_datetime, m.extracted),
  1423. )
  1424. self.assertEqual(
  1425. DTModel.objects.filter(
  1426. start_datetime=TruncMinute("start_datetime")
  1427. ).count(),
  1428. 1,
  1429. )
  1430. with self.assertRaisesMessage(
  1431. ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
  1432. ):
  1433. list(DTModel.objects.annotate(truncated=TruncMinute("start_date")))
  1434. with self.assertRaisesMessage(
  1435. ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
  1436. ):
  1437. list(
  1438. DTModel.objects.annotate(
  1439. truncated=TruncMinute("start_date", output_field=DateField())
  1440. )
  1441. )
  1442. def test_trunc_second_func(self):
  1443. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1444. end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), "second")
  1445. if settings.USE_TZ:
  1446. start_datetime = timezone.make_aware(start_datetime)
  1447. end_datetime = timezone.make_aware(end_datetime)
  1448. self.create_model(start_datetime, end_datetime)
  1449. self.create_model(end_datetime, start_datetime)
  1450. self.assertQuerysetEqual(
  1451. DTModel.objects.annotate(extracted=TruncSecond("start_datetime")).order_by(
  1452. "start_datetime"
  1453. ),
  1454. [
  1455. (start_datetime, truncate_to(start_datetime, "second")),
  1456. (end_datetime, truncate_to(end_datetime, "second")),
  1457. ],
  1458. lambda m: (m.start_datetime, m.extracted),
  1459. )
  1460. self.assertQuerysetEqual(
  1461. DTModel.objects.annotate(extracted=TruncSecond("start_time")).order_by(
  1462. "start_datetime"
  1463. ),
  1464. [
  1465. (start_datetime, truncate_to(start_datetime.time(), "second")),
  1466. (end_datetime, truncate_to(end_datetime.time(), "second")),
  1467. ],
  1468. lambda m: (m.start_datetime, m.extracted),
  1469. )
  1470. self.assertEqual(
  1471. DTModel.objects.filter(
  1472. start_datetime=TruncSecond("start_datetime")
  1473. ).count(),
  1474. 1,
  1475. )
  1476. with self.assertRaisesMessage(
  1477. ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
  1478. ):
  1479. list(DTModel.objects.annotate(truncated=TruncSecond("start_date")))
  1480. with self.assertRaisesMessage(
  1481. ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"
  1482. ):
  1483. list(
  1484. DTModel.objects.annotate(
  1485. truncated=TruncSecond("start_date", output_field=DateField())
  1486. )
  1487. )
  1488. def test_trunc_subquery_with_parameters(self):
  1489. author_1 = Author.objects.create(name="J. R. R. Tolkien")
  1490. author_2 = Author.objects.create(name="G. R. R. Martin")
  1491. fan_since_1 = datetime(2016, 2, 3, 15, 0, 0)
  1492. fan_since_2 = datetime(2015, 2, 3, 15, 0, 0)
  1493. fan_since_3 = datetime(2017, 2, 3, 15, 0, 0)
  1494. if settings.USE_TZ:
  1495. fan_since_1 = timezone.make_aware(fan_since_1)
  1496. fan_since_2 = timezone.make_aware(fan_since_2)
  1497. fan_since_3 = timezone.make_aware(fan_since_3)
  1498. Fan.objects.create(author=author_1, name="Tom", fan_since=fan_since_1)
  1499. Fan.objects.create(author=author_1, name="Emma", fan_since=fan_since_2)
  1500. Fan.objects.create(author=author_2, name="Isabella", fan_since=fan_since_3)
  1501. inner = (
  1502. Fan.objects.filter(
  1503. author=OuterRef("pk"), name__in=("Emma", "Isabella", "Tom")
  1504. )
  1505. .values("author")
  1506. .annotate(newest_fan=Max("fan_since"))
  1507. .values("newest_fan")
  1508. )
  1509. outer = Author.objects.annotate(
  1510. newest_fan_year=TruncYear(Subquery(inner, output_field=DateTimeField()))
  1511. )
  1512. tz = datetime_timezone.utc if settings.USE_TZ else None
  1513. self.assertSequenceEqual(
  1514. outer.order_by("name").values("name", "newest_fan_year"),
  1515. [
  1516. {
  1517. "name": "G. R. R. Martin",
  1518. "newest_fan_year": datetime(2017, 1, 1, 0, 0, tzinfo=tz),
  1519. },
  1520. {
  1521. "name": "J. R. R. Tolkien",
  1522. "newest_fan_year": datetime(2016, 1, 1, 0, 0, tzinfo=tz),
  1523. },
  1524. ],
  1525. )
  1526. def test_extract_outerref(self):
  1527. datetime_1 = datetime(2000, 1, 1)
  1528. datetime_2 = datetime(2001, 3, 5)
  1529. datetime_3 = datetime(2002, 1, 3)
  1530. if settings.USE_TZ:
  1531. datetime_1 = timezone.make_aware(datetime_1)
  1532. datetime_2 = timezone.make_aware(datetime_2)
  1533. datetime_3 = timezone.make_aware(datetime_3)
  1534. obj_1 = self.create_model(datetime_1, datetime_3)
  1535. obj_2 = self.create_model(datetime_2, datetime_1)
  1536. obj_3 = self.create_model(datetime_3, datetime_2)
  1537. inner_qs = DTModel.objects.filter(
  1538. start_datetime__year=2000,
  1539. start_datetime__month=ExtractMonth(OuterRef("end_datetime")),
  1540. )
  1541. qs = DTModel.objects.annotate(
  1542. related_pk=Subquery(inner_qs.values("pk")[:1]),
  1543. )
  1544. self.assertSequenceEqual(
  1545. qs.order_by("name").values("pk", "related_pk"),
  1546. [
  1547. {"pk": obj_1.pk, "related_pk": obj_1.pk},
  1548. {"pk": obj_2.pk, "related_pk": obj_1.pk},
  1549. {"pk": obj_3.pk, "related_pk": None},
  1550. ],
  1551. )
  1552. @override_settings(USE_TZ=True, TIME_ZONE="UTC")
  1553. class DateFunctionWithTimeZoneTests(DateFunctionTests):
  1554. def get_timezones(self, key):
  1555. for constructor in ZONE_CONSTRUCTORS:
  1556. yield constructor(key)
  1557. def test_extract_func_with_timezone(self):
  1558. start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
  1559. end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
  1560. start_datetime = timezone.make_aware(start_datetime)
  1561. end_datetime = timezone.make_aware(end_datetime)
  1562. self.create_model(start_datetime, end_datetime)
  1563. delta_tzinfo_pos = datetime_timezone(timedelta(hours=5))
  1564. delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17))
  1565. for melb in self.get_timezones("Australia/Melbourne"):
  1566. with self.subTest(repr(melb)):
  1567. qs = DTModel.objects.annotate(
  1568. day=Extract("start_datetime", "day"),
  1569. day_melb=Extract("start_datetime", "day", tzinfo=melb),
  1570. week=Extract("start_datetime", "week", tzinfo=melb),
  1571. isoyear=ExtractIsoYear("start_datetime", tzinfo=melb),
  1572. weekday=ExtractWeekDay("start_datetime"),
  1573. weekday_melb=ExtractWeekDay("start_datetime", tzinfo=melb),
  1574. isoweekday=ExtractIsoWeekDay("start_datetime"),
  1575. isoweekday_melb=ExtractIsoWeekDay("start_datetime", tzinfo=melb),
  1576. quarter=ExtractQuarter("start_datetime", tzinfo=melb),
  1577. hour=ExtractHour("start_datetime"),
  1578. hour_melb=ExtractHour("start_datetime", tzinfo=melb),
  1579. hour_with_delta_pos=ExtractHour(
  1580. "start_datetime", tzinfo=delta_tzinfo_pos
  1581. ),
  1582. hour_with_delta_neg=ExtractHour(
  1583. "start_datetime", tzinfo=delta_tzinfo_neg
  1584. ),
  1585. minute_with_delta_neg=ExtractMinute(
  1586. "start_datetime", tzinfo=delta_tzinfo_neg
  1587. ),
  1588. ).order_by("start_datetime")
  1589. utc_model = qs.get()
  1590. self.assertEqual(utc_model.day, 15)
  1591. self.assertEqual(utc_model.day_melb, 16)
  1592. self.assertEqual(utc_model.week, 25)
  1593. self.assertEqual(utc_model.isoyear, 2015)
  1594. self.assertEqual(utc_model.weekday, 2)
  1595. self.assertEqual(utc_model.weekday_melb, 3)
  1596. self.assertEqual(utc_model.isoweekday, 1)
  1597. self.assertEqual(utc_model.isoweekday_melb, 2)
  1598. self.assertEqual(utc_model.quarter, 2)
  1599. self.assertEqual(utc_model.hour, 23)
  1600. self.assertEqual(utc_model.hour_melb, 9)
  1601. self.assertEqual(utc_model.hour_with_delta_pos, 4)
  1602. self.assertEqual(utc_model.hour_with_delta_neg, 18)
  1603. self.assertEqual(utc_model.minute_with_delta_neg, 47)
  1604. with timezone.override(melb):
  1605. melb_model = qs.get()
  1606. self.assertEqual(melb_model.day, 16)
  1607. self.assertEqual(melb_model.day_melb, 16)
  1608. self.assertEqual(melb_model.week, 25)
  1609. self.assertEqual(melb_model.isoyear, 2015)
  1610. self.assertEqual(melb_model.weekday, 3)
  1611. self.assertEqual(melb_model.isoweekday, 2)
  1612. self.assertEqual(melb_model.quarter, 2)
  1613. self.assertEqual(melb_model.weekday_melb, 3)
  1614. self.assertEqual(melb_model.isoweekday_melb, 2)
  1615. self.assertEqual(melb_model.hour, 9)
  1616. self.assertEqual(melb_model.hour_melb, 9)
  1617. def test_extract_func_with_timezone_minus_no_offset(self):
  1618. start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
  1619. end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
  1620. start_datetime = timezone.make_aware(start_datetime)
  1621. end_datetime = timezone.make_aware(end_datetime)
  1622. self.create_model(start_datetime, end_datetime)
  1623. for ust_nera in self.get_timezones("Asia/Ust-Nera"):
  1624. with self.subTest(repr(ust_nera)):
  1625. qs = DTModel.objects.annotate(
  1626. hour=ExtractHour("start_datetime"),
  1627. hour_tz=ExtractHour("start_datetime", tzinfo=ust_nera),
  1628. ).order_by("start_datetime")
  1629. utc_model = qs.get()
  1630. self.assertEqual(utc_model.hour, 23)
  1631. self.assertEqual(utc_model.hour_tz, 9)
  1632. with timezone.override(ust_nera):
  1633. ust_nera_model = qs.get()
  1634. self.assertEqual(ust_nera_model.hour, 9)
  1635. self.assertEqual(ust_nera_model.hour_tz, 9)
  1636. def test_extract_func_explicit_timezone_priority(self):
  1637. start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
  1638. end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
  1639. start_datetime = timezone.make_aware(start_datetime)
  1640. end_datetime = timezone.make_aware(end_datetime)
  1641. self.create_model(start_datetime, end_datetime)
  1642. for melb in self.get_timezones("Australia/Melbourne"):
  1643. with self.subTest(repr(melb)):
  1644. with timezone.override(melb):
  1645. model = (
  1646. DTModel.objects.annotate(
  1647. day_melb=Extract("start_datetime", "day"),
  1648. day_utc=Extract(
  1649. "start_datetime", "day", tzinfo=datetime_timezone.utc
  1650. ),
  1651. )
  1652. .order_by("start_datetime")
  1653. .get()
  1654. )
  1655. self.assertEqual(model.day_melb, 16)
  1656. self.assertEqual(model.day_utc, 15)
  1657. def test_extract_invalid_field_with_timezone(self):
  1658. for melb in self.get_timezones("Australia/Melbourne"):
  1659. with self.subTest(repr(melb)):
  1660. msg = "tzinfo can only be used with DateTimeField."
  1661. with self.assertRaisesMessage(ValueError, msg):
  1662. DTModel.objects.annotate(
  1663. day_melb=Extract("start_date", "day", tzinfo=melb),
  1664. ).get()
  1665. with self.assertRaisesMessage(ValueError, msg):
  1666. DTModel.objects.annotate(
  1667. hour_melb=Extract("start_time", "hour", tzinfo=melb),
  1668. ).get()
  1669. def test_trunc_timezone_applied_before_truncation(self):
  1670. start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
  1671. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  1672. start_datetime = timezone.make_aware(start_datetime)
  1673. end_datetime = timezone.make_aware(end_datetime)
  1674. self.create_model(start_datetime, end_datetime)
  1675. for melb, pacific in zip(
  1676. self.get_timezones("Australia/Melbourne"),
  1677. self.get_timezones("America/Los_Angeles"),
  1678. ):
  1679. with self.subTest((repr(melb), repr(pacific))):
  1680. model = (
  1681. DTModel.objects.annotate(
  1682. melb_year=TruncYear("start_datetime", tzinfo=melb),
  1683. pacific_year=TruncYear("start_datetime", tzinfo=pacific),
  1684. melb_date=TruncDate("start_datetime", tzinfo=melb),
  1685. pacific_date=TruncDate("start_datetime", tzinfo=pacific),
  1686. melb_time=TruncTime("start_datetime", tzinfo=melb),
  1687. pacific_time=TruncTime("start_datetime", tzinfo=pacific),
  1688. )
  1689. .order_by("start_datetime")
  1690. .get()
  1691. )
  1692. melb_start_datetime = start_datetime.astimezone(melb)
  1693. pacific_start_datetime = start_datetime.astimezone(pacific)
  1694. self.assertEqual(model.start_datetime, start_datetime)
  1695. self.assertEqual(
  1696. model.melb_year, truncate_to(start_datetime, "year", melb)
  1697. )
  1698. self.assertEqual(
  1699. model.pacific_year, truncate_to(start_datetime, "year", pacific)
  1700. )
  1701. self.assertEqual(model.start_datetime.year, 2016)
  1702. self.assertEqual(model.melb_year.year, 2016)
  1703. self.assertEqual(model.pacific_year.year, 2015)
  1704. self.assertEqual(model.melb_date, melb_start_datetime.date())
  1705. self.assertEqual(model.pacific_date, pacific_start_datetime.date())
  1706. self.assertEqual(model.melb_time, melb_start_datetime.time())
  1707. self.assertEqual(model.pacific_time, pacific_start_datetime.time())
  1708. @needs_pytz
  1709. @ignore_warnings(category=RemovedInDjango50Warning)
  1710. def test_trunc_ambiguous_and_invalid_times(self):
  1711. sao = pytz.timezone("America/Sao_Paulo")
  1712. start_datetime = datetime(2016, 10, 16, 13, tzinfo=datetime_timezone.utc)
  1713. end_datetime = datetime(2016, 2, 21, 1, tzinfo=datetime_timezone.utc)
  1714. self.create_model(start_datetime, end_datetime)
  1715. with timezone.override(sao):
  1716. with self.assertRaisesMessage(
  1717. pytz.NonExistentTimeError, "2016-10-16 00:00:00"
  1718. ):
  1719. model = DTModel.objects.annotate(
  1720. truncated_start=TruncDay("start_datetime")
  1721. ).get()
  1722. with self.assertRaisesMessage(
  1723. pytz.AmbiguousTimeError, "2016-02-20 23:00:00"
  1724. ):
  1725. model = DTModel.objects.annotate(
  1726. truncated_end=TruncHour("end_datetime")
  1727. ).get()
  1728. model = DTModel.objects.annotate(
  1729. truncated_start=TruncDay("start_datetime", is_dst=False),
  1730. truncated_end=TruncHour("end_datetime", is_dst=False),
  1731. ).get()
  1732. self.assertEqual(model.truncated_start.dst(), timedelta(0))
  1733. self.assertEqual(model.truncated_end.dst(), timedelta(0))
  1734. model = DTModel.objects.annotate(
  1735. truncated_start=TruncDay("start_datetime", is_dst=True),
  1736. truncated_end=TruncHour("end_datetime", is_dst=True),
  1737. ).get()
  1738. self.assertEqual(model.truncated_start.dst(), timedelta(0, 3600))
  1739. self.assertEqual(model.truncated_end.dst(), timedelta(0, 3600))
  1740. def test_trunc_func_with_timezone(self):
  1741. """
  1742. If the truncated datetime transitions to a different offset (daylight
  1743. saving) then the returned value will have that new timezone/offset.
  1744. """
  1745. start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
  1746. end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
  1747. start_datetime = timezone.make_aware(start_datetime)
  1748. end_datetime = timezone.make_aware(end_datetime)
  1749. self.create_model(start_datetime, end_datetime)
  1750. self.create_model(end_datetime, start_datetime)
  1751. for melb in self.get_timezones("Australia/Melbourne"):
  1752. with self.subTest(repr(melb)):
  1753. def test_datetime_kind(kind):
  1754. self.assertQuerysetEqual(
  1755. DTModel.objects.annotate(
  1756. truncated=Trunc(
  1757. "start_datetime",
  1758. kind,
  1759. output_field=DateTimeField(),
  1760. tzinfo=melb,
  1761. )
  1762. ).order_by("start_datetime"),
  1763. [
  1764. (
  1765. start_datetime,
  1766. truncate_to(
  1767. start_datetime.astimezone(melb), kind, melb
  1768. ),
  1769. ),
  1770. (
  1771. end_datetime,
  1772. truncate_to(end_datetime.astimezone(melb), kind, melb),
  1773. ),
  1774. ],
  1775. lambda m: (m.start_datetime, m.truncated),
  1776. )
  1777. def test_datetime_to_date_kind(kind):
  1778. self.assertQuerysetEqual(
  1779. DTModel.objects.annotate(
  1780. truncated=Trunc(
  1781. "start_datetime",
  1782. kind,
  1783. output_field=DateField(),
  1784. tzinfo=melb,
  1785. ),
  1786. ).order_by("start_datetime"),
  1787. [
  1788. (
  1789. start_datetime,
  1790. truncate_to(
  1791. start_datetime.astimezone(melb).date(), kind
  1792. ),
  1793. ),
  1794. (
  1795. end_datetime,
  1796. truncate_to(end_datetime.astimezone(melb).date(), kind),
  1797. ),
  1798. ],
  1799. lambda m: (m.start_datetime, m.truncated),
  1800. )
  1801. def test_datetime_to_time_kind(kind):
  1802. self.assertQuerysetEqual(
  1803. DTModel.objects.annotate(
  1804. truncated=Trunc(
  1805. "start_datetime",
  1806. kind,
  1807. output_field=TimeField(),
  1808. tzinfo=melb,
  1809. )
  1810. ).order_by("start_datetime"),
  1811. [
  1812. (
  1813. start_datetime,
  1814. truncate_to(
  1815. start_datetime.astimezone(melb).time(), kind
  1816. ),
  1817. ),
  1818. (
  1819. end_datetime,
  1820. truncate_to(end_datetime.astimezone(melb).time(), kind),
  1821. ),
  1822. ],
  1823. lambda m: (m.start_datetime, m.truncated),
  1824. )
  1825. test_datetime_to_date_kind("year")
  1826. test_datetime_to_date_kind("quarter")
  1827. test_datetime_to_date_kind("month")
  1828. test_datetime_to_date_kind("week")
  1829. test_datetime_to_date_kind("day")
  1830. test_datetime_to_time_kind("hour")
  1831. test_datetime_to_time_kind("minute")
  1832. test_datetime_to_time_kind("second")
  1833. test_datetime_kind("year")
  1834. test_datetime_kind("quarter")
  1835. test_datetime_kind("month")
  1836. test_datetime_kind("week")
  1837. test_datetime_kind("day")
  1838. test_datetime_kind("hour")
  1839. test_datetime_kind("minute")
  1840. test_datetime_kind("second")
  1841. qs = DTModel.objects.filter(
  1842. start_datetime__date=Trunc(
  1843. "start_datetime", "day", output_field=DateField()
  1844. )
  1845. )
  1846. self.assertEqual(qs.count(), 2)
  1847. def test_trunc_invalid_field_with_timezone(self):
  1848. for melb in self.get_timezones("Australia/Melbourne"):
  1849. with self.subTest(repr(melb)):
  1850. msg = "tzinfo can only be used with DateTimeField."
  1851. with self.assertRaisesMessage(ValueError, msg):
  1852. DTModel.objects.annotate(
  1853. day_melb=Trunc("start_date", "day", tzinfo=melb),
  1854. ).get()
  1855. with self.assertRaisesMessage(ValueError, msg):
  1856. DTModel.objects.annotate(
  1857. hour_melb=Trunc("start_time", "hour", tzinfo=melb),
  1858. ).get()