__init__.py 96 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994
  1. # Copyright (c) "Neo4j"
  2. # Neo4j Sweden AB [https://neo4j.com]
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # https://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """
  16. Fundamental temporal types for interchange with the DBMS.
  17. This module contains the fundamental types used for temporal accounting as well
  18. as a number of utility functions.
  19. """
  20. from __future__ import annotations
  21. import re
  22. import typing as t
  23. from datetime import (
  24. date,
  25. datetime,
  26. time,
  27. timedelta,
  28. timezone,
  29. tzinfo as _tzinfo,
  30. )
  31. from functools import total_ordering
  32. from re import compile as re_compile
  33. from time import (
  34. gmtime,
  35. mktime,
  36. struct_time,
  37. )
  38. if t.TYPE_CHECKING:
  39. import typing_extensions as te
  40. from ._arithmetic import (
  41. round_half_to_even,
  42. symmetric_divmod,
  43. )
  44. from ._metaclasses import (
  45. DateTimeType,
  46. DateType,
  47. TimeType,
  48. )
  49. __all__ = [
  50. "MAX_INT64",
  51. "MAX_YEAR",
  52. "MIN_INT64",
  53. "MIN_YEAR",
  54. "Date",
  55. "DateTime",
  56. "Duration",
  57. "Midday",
  58. "Midnight",
  59. "Never",
  60. "Time",
  61. "UnixEpoch",
  62. "ZeroDate",
  63. ]
  64. MIN_INT64 = -(2**63)
  65. MAX_INT64 = (2**63) - 1
  66. #: The smallest year number allowed in a :class:`.Date` or :class:`.DateTime`
  67. #: object to be compatible with :class:`datetime.date` and
  68. #: :class:`datetime.datetime`.
  69. MIN_YEAR: te.Final[int] = 1
  70. #: The largest year number allowed in a :class:`.Date` or :class:`.DateTime`
  71. #: object to be compatible with :class:`datetime.date` and
  72. #: :class:`datetime.datetime`.
  73. MAX_YEAR: te.Final[int] = 9999
  74. DATE_ISO_PATTERN = re_compile(r"^(\d{4})-(\d{2})-(\d{2})$")
  75. TIME_ISO_PATTERN = re_compile(
  76. r"^(\d{2})(:(\d{2})(:((\d{2})"
  77. r"(\.\d*)?))?)?(([+-])(\d{2}):(\d{2})(:((\d{2})(\.\d*)?))?)?$"
  78. )
  79. DURATION_ISO_PATTERN = re_compile(
  80. r"^P((\d+)Y)?((\d+)M)?((\d+)D)?"
  81. r"(T((\d+)H)?((\d+)M)?(((\d+)(\.\d+)?)?S)?)?$"
  82. )
  83. NANO_SECONDS = 1000000000
  84. AVERAGE_SECONDS_IN_MONTH = 2629746
  85. AVERAGE_SECONDS_IN_DAY = 86400
  86. FORMAT_F_REPLACE = re.compile(r"(?<!%)%f")
  87. def _is_leap_year(year):
  88. if year % 4 != 0:
  89. return False
  90. if year % 100 != 0:
  91. return True
  92. return year % 400 == 0
  93. IS_LEAP_YEAR = {
  94. year: _is_leap_year(year) for year in range(MIN_YEAR, MAX_YEAR + 1)
  95. }
  96. def _days_in_year(year):
  97. return 366 if IS_LEAP_YEAR[year] else 365
  98. DAYS_IN_YEAR = {
  99. year: _days_in_year(year) for year in range(MIN_YEAR, MAX_YEAR + 1)
  100. }
  101. def _days_in_month(year, month):
  102. if month in {9, 4, 6, 11}:
  103. return 30
  104. elif month != 2:
  105. return 31
  106. else:
  107. return 29 if IS_LEAP_YEAR[year] else 28
  108. DAYS_IN_MONTH = {
  109. (year, month): _days_in_month(year, month)
  110. for year in range(MIN_YEAR, MAX_YEAR + 1)
  111. for month in range(1, 13)
  112. }
  113. def _normalize_day(year, month, day):
  114. """
  115. Coerce the day of the month to an internal value.
  116. That value may or may not match the "public" value.
  117. Except for the last three days of every month, all days are stored as-is.
  118. The last three days are instead stored as -1 (the last), -2 (the second to
  119. last) and -3 (the third to last).
  120. Therefore, for a 28-day month, the last week is as follows:
  121. Day | 22 23 24 25 26 27 28
  122. Value | 22 23 24 25 -3 -2 -1
  123. For a 29-day month, the last week is as follows:
  124. Day | 23 24 25 26 27 28 29
  125. Value | 23 24 25 26 -3 -2 -1
  126. For a 30-day month, the last week is as follows:
  127. Day | 24 25 26 27 28 29 30
  128. Value | 24 25 26 27 -3 -2 -1
  129. For a 31-day month, the last week is as follows:
  130. Day | 25 26 27 28 29 30 31
  131. Value | 25 26 27 28 -3 -2 -1
  132. This slightly unintuitive system makes some temporal arithmetic
  133. produce a more desirable outcome.
  134. :param year:
  135. :param month:
  136. :param day:
  137. :returns:
  138. """
  139. if year < MIN_YEAR or year > MAX_YEAR:
  140. raise ValueError(f"Year out of range ({MIN_YEAR}..{MAX_YEAR})")
  141. if month < 1 or month > 12:
  142. raise ValueError("Month out of range (1..12)")
  143. days_in_month = DAYS_IN_MONTH[year, month]
  144. if day in {days_in_month, -1}:
  145. return year, month, -1
  146. if day in {days_in_month - 1, -2}:
  147. return year, month, -2
  148. if day in {days_in_month - 2, -3}:
  149. return year, month, -3
  150. if 1 <= day <= days_in_month - 3:
  151. return year, month, int(day)
  152. # TODO improve this error message
  153. raise ValueError(
  154. f"Day {day} out of range (1..{days_in_month}, -1, -2 ,-3)"
  155. )
  156. class ClockTime(tuple):
  157. """
  158. A count of `seconds` and `nanoseconds`.
  159. This class can be used to mark a particular point in time, relative to an
  160. externally-specified epoch.
  161. The `seconds` and `nanoseconds` values provided to the constructor can
  162. can have any sign but will be normalized internally into a positive or
  163. negative `seconds` value along with a positive `nanoseconds` value
  164. between `0` and `999,999,999`. Therefore, ``ClockTime(-1, -1)`` is
  165. normalized to ``ClockTime(-2, 999999999)``.
  166. Note that the structure of a :class:`.ClockTime` object is similar to
  167. the ``timespec`` struct in C.
  168. """
  169. def __new__(cls, seconds: float = 0, nanoseconds: int = 0) -> ClockTime:
  170. seconds, nanoseconds = divmod(
  171. int(NANO_SECONDS * seconds) + int(nanoseconds), NANO_SECONDS
  172. )
  173. return tuple.__new__(cls, (seconds, nanoseconds))
  174. def __add__(self, other):
  175. if isinstance(other, (int, float)):
  176. other = ClockTime(other)
  177. if isinstance(other, ClockTime):
  178. return ClockTime(
  179. self.seconds + other.seconds,
  180. self.nanoseconds + other.nanoseconds,
  181. )
  182. if isinstance(other, Duration):
  183. if other.months or other.days:
  184. raise ValueError("Cannot add Duration with months or days")
  185. return ClockTime(
  186. self.seconds + other.seconds,
  187. self.nanoseconds + int(other.nanoseconds),
  188. )
  189. return NotImplemented
  190. def __sub__(self, other):
  191. if isinstance(other, (int, float)):
  192. other = ClockTime(other)
  193. if isinstance(other, ClockTime):
  194. return ClockTime(
  195. self.seconds - other.seconds,
  196. self.nanoseconds - other.nanoseconds,
  197. )
  198. if isinstance(other, Duration):
  199. if other.months or other.days:
  200. raise ValueError(
  201. "Cannot subtract Duration with months or days"
  202. )
  203. return ClockTime(
  204. self.seconds - other.seconds,
  205. self.nanoseconds - int(other.nanoseconds),
  206. )
  207. return NotImplemented
  208. def __repr__(self):
  209. s, ns = self
  210. return f"ClockTime(seconds={s!r}, nanoseconds={ns!r})"
  211. @property
  212. def seconds(self):
  213. return self[0]
  214. @property
  215. def nanoseconds(self):
  216. return self[1]
  217. class Clock:
  218. """
  219. Accessor for time values.
  220. This class is fulfilled by implementations
  221. that subclass :class:`.Clock`. These implementations are contained within
  222. the ``neo4j.time.clock_implementations`` module, and are not intended to be
  223. accessed directly.
  224. Creating a new :class:`.Clock` instance will produce the highest
  225. precision clock implementation available.
  226. >>> clock = Clock()
  227. >>> type(clock) # doctest: +SKIP
  228. neo4j.time.clock_implementations.LibCClock
  229. >>> clock.local_time() # doctest: +SKIP
  230. ClockTime(seconds=1525265942, nanoseconds=506844026)
  231. """
  232. __implementations = None
  233. def __new__(cls):
  234. if cls.__implementations is None:
  235. # Find an available clock with the best precision
  236. import neo4j.time._clock_implementations # noqa: F401 needed to make subclasses available
  237. cls.__implementations = sorted(
  238. (
  239. clock
  240. for clock in Clock.__subclasses__()
  241. if clock.available()
  242. ),
  243. key=lambda clock: clock.precision(),
  244. reverse=True,
  245. )
  246. if not cls.__implementations:
  247. raise RuntimeError("No clock implementations available")
  248. return object.__new__(cls.__implementations[0])
  249. @classmethod
  250. def precision(cls):
  251. """
  252. Return precision (decimal places) of this clock implementation.
  253. The precision of this clock implementation, represented as a
  254. number of decimal places. Therefore, for a nanosecond precision
  255. clock, this function returns `9`.
  256. """
  257. raise NotImplementedError("No clock implementation selected")
  258. @classmethod
  259. def available(cls):
  260. """
  261. Indicate whether this clock implementation is available.
  262. A boolean flag to indicate whether this clock implementation is
  263. available on this platform.
  264. """
  265. raise NotImplementedError("No clock implementation selected")
  266. @classmethod
  267. def local_offset(cls):
  268. """
  269. Get the UTC offset of the local time.
  270. The offset from UTC for local time read from this clock.
  271. This may raise OverflowError if not supported, because of platform
  272. dependent C libraries.
  273. :returns:
  274. :rtype:
  275. :raises OverflowError:
  276. """
  277. # Adding and subtracting two days to avoid passing a pre-epoch time to
  278. # `mktime`, which can cause a `OverflowError` on some platforms (e.g.,
  279. # Windows).
  280. return ClockTime(-int(mktime(gmtime(172800))) + 172800)
  281. def local_time(self):
  282. """
  283. Get the current local time relative to the Unix Epoch.
  284. Read and return the current local time from this clock, measured
  285. relative to the Unix Epoch. This may raise OverflowError if not
  286. supported, because of platform dependent C libraries.
  287. :returns:
  288. :rtype:
  289. :raises OverflowError:
  290. """
  291. return self.utc_time() + self.local_offset()
  292. def utc_time(self):
  293. """
  294. Read and return the current UTC time from this clock.
  295. Time is measured relative to the Unix Epoch.
  296. """
  297. raise NotImplementedError("No clock implementation selected")
  298. if t.TYPE_CHECKING:
  299. # make typechecker believe that Duration subclasses datetime.timedelta
  300. # https://github.com/python/typeshed/issues/8409#issuecomment-1197704527
  301. duration_base_class = timedelta
  302. else:
  303. duration_base_class = object
  304. class Duration( # type: ignore[misc]
  305. t.Tuple[int, int, int, int], duration_base_class
  306. ):
  307. r"""
  308. A difference between two points in time.
  309. A :class:`.Duration` represents the difference between two points in time.
  310. Duration objects store a composite value of `months`, `days`, `seconds`,
  311. and `nanoseconds`. Unlike :class:`datetime.timedelta` however, days, and
  312. seconds/nanoseconds are never interchanged. All values except seconds and
  313. nanoseconds are applied separately in calculations (element-wise).
  314. A :class:`.Duration` stores four primary instance attributes internally:
  315. ``months``, ``days``, ``seconds`` and ``nanoseconds``. These are maintained
  316. as individual values and are immutable. Each of these four attributes can
  317. carry its own sign, except for ``nanoseconds``, which always has the same
  318. sign as ``seconds``. The constructor will establish this state, should the
  319. duration be initialized with conflicting ``seconds`` and ``nanoseconds``
  320. signs. This structure allows the modelling of durations such as
  321. ``3 months minus 2 days``.
  322. To determine if a :class:`Duration` ``d`` is overflowing the accepted
  323. values of the database, first, all ``nanoseconds`` outside the range
  324. -999_999_999 and 999_999_999 are transferred into the seconds field. Then,
  325. ``months``, ``days``, and ``seconds`` are summed up like so:
  326. ``months * 2629746 + days * 86400 + d.seconds
  327. + d.nanoseconds // 1000000000``.
  328. (Like the integer division in Python, this one is to be understood as
  329. rounding down rather than towards 0.)
  330. This value must be between -(2\ :sup:`63`) and (2\ :sup:`63` - 1)
  331. inclusive.
  332. :param years: will be added times 12 to `months`
  333. :param months: will be truncated to :class:`int` (`int(months)`)
  334. :param weeks: will be added times 7 to `days`
  335. :param days: will be truncated to :class:`int` (`int(days)`)
  336. :param hours: will be added times 3,600,000,000,000 to `nanoseconds`
  337. :param minutes: will be added times 60,000,000,000 to `nanoseconds`
  338. :param seconds: will be added times 1,000,000,000 to `nanoseconds``
  339. :param milliseconds: will be added times 1,000,000 to `nanoseconds`
  340. :param microseconds: will be added times 1,000 to `nanoseconds`
  341. :param nanoseconds: will be truncated to :class:`int` (`int(nanoseconds)`)
  342. :raises ValueError: the components exceed the limits as described above.
  343. """
  344. # i64: i64:i64: i32
  345. min: te.Final[Duration] = None # type: ignore
  346. """The lowest duration value possible."""
  347. max: te.Final[Duration] = None # type: ignore
  348. """The highest duration value possible."""
  349. def __new__(
  350. cls,
  351. years: float = 0,
  352. months: float = 0,
  353. weeks: float = 0,
  354. days: float = 0,
  355. hours: float = 0,
  356. minutes: float = 0,
  357. seconds: float = 0,
  358. milliseconds: float = 0,
  359. microseconds: float = 0,
  360. nanoseconds: float = 0,
  361. ) -> Duration:
  362. mo = int(12 * years + months)
  363. if mo < MIN_INT64 or mo > MAX_INT64:
  364. raise ValueError("Months value out of range")
  365. d = int(7 * weeks + days)
  366. ns = (
  367. int(3600000000000 * hours)
  368. + int(60000000000 * minutes)
  369. + int(1000000000 * seconds)
  370. + int(1000000 * milliseconds)
  371. + int(1000 * microseconds)
  372. + int(nanoseconds)
  373. )
  374. s, ns = symmetric_divmod(ns, NANO_SECONDS)
  375. tuple_ = (mo, d, s, ns)
  376. avg_total_seconds = (
  377. mo * AVERAGE_SECONDS_IN_MONTH
  378. + d * AVERAGE_SECONDS_IN_DAY
  379. + s
  380. - (1 if ns < 0 else 0)
  381. )
  382. if not MIN_INT64 <= avg_total_seconds <= MAX_INT64:
  383. raise ValueError(f"Duration value out of range: {tuple_!r}")
  384. # TODO: 6.0 - remove type ignore when support for Python 3.7 is dropped
  385. return tuple.__new__(cls, tuple_) # type: ignore[type-var]
  386. def __bool__(self) -> bool:
  387. """Falsy if all primary instance attributes are."""
  388. return any(map(bool, self))
  389. __nonzero__ = __bool__
  390. def __add__( # type: ignore[override]
  391. self, other: Duration | timedelta
  392. ) -> Duration:
  393. """Add a :class:`.Duration` or :class:`datetime.timedelta`."""
  394. if isinstance(other, Duration):
  395. return Duration(
  396. months=self[0] + int(other.months),
  397. days=self[1] + int(other.days),
  398. seconds=self[2] + int(other.seconds),
  399. nanoseconds=self[3] + int(other.nanoseconds),
  400. )
  401. if isinstance(other, timedelta):
  402. return Duration(
  403. months=self[0],
  404. days=self[1] + other.days,
  405. seconds=self[2] + other.seconds,
  406. nanoseconds=self[3] + other.microseconds * 1000,
  407. )
  408. return NotImplemented
  409. def __sub__(self, other: Duration | timedelta) -> Duration:
  410. """Subtract a :class:`.Duration` or :class:`datetime.timedelta`."""
  411. if isinstance(other, Duration):
  412. return Duration(
  413. months=self[0] - int(other.months),
  414. days=self[1] - int(other.days),
  415. seconds=self[2] - int(other.seconds),
  416. nanoseconds=self[3] - int(other.nanoseconds),
  417. )
  418. if isinstance(other, timedelta):
  419. return Duration(
  420. months=self[0],
  421. days=self[1] - other.days,
  422. seconds=self[2] - other.seconds,
  423. nanoseconds=self[3] - other.microseconds * 1000,
  424. )
  425. return NotImplemented
  426. def __mul__(self, other: float) -> Duration: # type: ignore[override]
  427. """
  428. Multiply by an :class:`int` or :class:`float`.
  429. The operation is performed element-wise on
  430. ``(months, days, nanaoseconds)`` where
  431. * years go into months,
  432. * weeks go into days,
  433. * seconds and all sub-second units go into nanoseconds.
  434. Each element will be rounded to the nearest integer (.5 towards even).
  435. """
  436. if isinstance(other, (int, float)):
  437. return Duration(
  438. months=round_half_to_even(self[0] * other),
  439. days=round_half_to_even(self[1] * other),
  440. nanoseconds=round_half_to_even(
  441. self[2] * NANO_SECONDS * other + self[3] * other
  442. ),
  443. )
  444. return NotImplemented
  445. def __floordiv__(self, other: int) -> Duration: # type: ignore[override]
  446. """
  447. Integer division by an :class:`int`.
  448. The operation is performed element-wise on
  449. ``(months, days, nanaoseconds)`` where
  450. * years go into months,
  451. * weeks go into days,
  452. * seconds and all sub-second units go into nanoseconds.
  453. Each element will be rounded towards -inf.
  454. """
  455. if isinstance(other, int):
  456. return Duration(
  457. months=self[0] // other,
  458. days=self[1] // other,
  459. nanoseconds=(self[2] * NANO_SECONDS + self[3]) // other,
  460. )
  461. return NotImplemented
  462. def __mod__(self, other: int) -> Duration: # type: ignore[override]
  463. """
  464. Modulo operation by an :class:`int`.
  465. The operation is performed element-wise on
  466. ``(months, days, nanaoseconds)`` where
  467. * years go into months,
  468. * weeks go into days,
  469. * seconds and all sub-second units go into nanoseconds.
  470. """
  471. if isinstance(other, int):
  472. return Duration(
  473. months=self[0] % other,
  474. days=self[1] % other,
  475. nanoseconds=(self[2] * NANO_SECONDS + self[3]) % other,
  476. )
  477. return NotImplemented
  478. def __divmod__( # type: ignore[override]
  479. self, other: int
  480. ) -> tuple[Duration, Duration]:
  481. """
  482. Division and modulo operation by an :class:`int`.
  483. See :meth:`__floordiv__` and :meth:`__mod__`.
  484. """
  485. if isinstance(other, int):
  486. return self.__floordiv__(other), self.__mod__(other)
  487. return NotImplemented
  488. def __truediv__(self, other: float) -> Duration: # type: ignore[override]
  489. """
  490. Division by an :class:`int` or :class:`float`.
  491. The operation is performed element-wise on
  492. ``(months, days, nanaoseconds)`` where
  493. * years go into months,
  494. * weeks go into days,
  495. * seconds and all sub-second units go into nanoseconds.
  496. Each element will be rounded to the nearest integer (.5 towards even).
  497. """
  498. if isinstance(other, (int, float)):
  499. return Duration(
  500. months=round_half_to_even(self[0] / other),
  501. days=round_half_to_even(self[1] / other),
  502. nanoseconds=round_half_to_even(
  503. self[2] * NANO_SECONDS / other + self[3] / other
  504. ),
  505. )
  506. return NotImplemented
  507. def __pos__(self) -> Duration:
  508. return self
  509. def __neg__(self) -> Duration:
  510. return Duration(
  511. months=-self[0],
  512. days=-self[1],
  513. seconds=-self[2],
  514. nanoseconds=-self[3],
  515. )
  516. def __abs__(self) -> Duration:
  517. return Duration(
  518. months=abs(self[0]),
  519. days=abs(self[1]),
  520. seconds=abs(self[2]),
  521. nanoseconds=abs(self[3]),
  522. )
  523. def __repr__(self) -> str:
  524. mo, day, sec, ns = self
  525. return (
  526. f"Duration(months={mo!r}, days={day!r}, seconds={sec!r}, "
  527. f"nanoseconds={ns!r})"
  528. )
  529. def __str__(self) -> str:
  530. return self.iso_format()
  531. def __reduce__(self):
  532. return type(self)._restore, (tuple(self), self.__dict__)
  533. @classmethod
  534. def _restore(cls, elements, dict_):
  535. instance = tuple.__new__(cls, elements)
  536. if dict_:
  537. instance.__dict__.update(dict_)
  538. return instance
  539. @classmethod
  540. def from_iso_format(cls, s: str) -> Duration:
  541. """
  542. Parse a ISO formatted duration string.
  543. Accepted formats (all lowercase letters are placeholders):
  544. 'P', a zero length duration
  545. 'PyY', y being a number of years
  546. 'PmM', m being a number of months
  547. 'PdD', d being a number of days
  548. Any combination of the above, e.g., 'P25Y1D' for 25 years and 1
  549. day.
  550. 'PThH', h being a number of hours
  551. 'PTmM', h being a number of minutes
  552. 'PTsS', h being a number of seconds
  553. 'PTs.sss...S', h being a fractional number of seconds
  554. Any combination of the above, e.g. 'PT5H1.2S' for 5 hours and 1.2
  555. seconds.
  556. Any combination of all options, e.g. 'P13MT100M' for 13 months and
  557. 100 minutes.
  558. :param s: String to parse
  559. :raises ValueError: if the string does not match the required format.
  560. """
  561. match = DURATION_ISO_PATTERN.match(s)
  562. if match:
  563. ns = 0
  564. if match.group(15):
  565. ns = int(match.group(15)[1:10].ljust(9, "0"))
  566. return cls(
  567. years=int(match.group(2) or 0),
  568. months=int(match.group(4) or 0),
  569. days=int(match.group(6) or 0),
  570. hours=int(match.group(9) or 0),
  571. minutes=int(match.group(11) or 0),
  572. seconds=int(match.group(14) or 0),
  573. nanoseconds=ns,
  574. )
  575. raise ValueError("Duration string must be in ISO format")
  576. fromisoformat = from_iso_format
  577. def iso_format(self, sep: str = "T") -> str:
  578. """
  579. Return the :class:`Duration` as ISO formatted string.
  580. :param sep: the separator before the time components.
  581. """
  582. parts = []
  583. hours, minutes, seconds, nanoseconds = (
  584. self.hours_minutes_seconds_nanoseconds
  585. )
  586. if hours:
  587. parts.append(f"{hours}H")
  588. if minutes:
  589. parts.append(f"{minutes}M")
  590. if nanoseconds:
  591. if seconds >= 0 and nanoseconds >= 0:
  592. ns_str = str(nanoseconds).rjust(9, "0").rstrip("0")
  593. parts.append(f"{seconds}.{ns_str}S")
  594. elif seconds <= 0 and nanoseconds <= 0:
  595. ns_str = str(abs(nanoseconds)).rjust(9, "0").rstrip("0")
  596. parts.append(f"-{abs(seconds)}.{ns_str}S")
  597. else:
  598. raise AssertionError("Please report this issue")
  599. elif seconds:
  600. parts.append(f"{seconds}S")
  601. if parts:
  602. parts.insert(0, sep)
  603. years, months, days = self.years_months_days
  604. if days:
  605. parts.insert(0, f"{days}D")
  606. if months:
  607. parts.insert(0, f"{months}M")
  608. if years:
  609. parts.insert(0, f"{years}Y")
  610. if parts:
  611. parts.insert(0, "P")
  612. return "".join(parts)
  613. else:
  614. return "PT0S"
  615. @property
  616. def months(self) -> int:
  617. """The months of the :class:`Duration`."""
  618. return self[0]
  619. @property
  620. def days(self) -> int:
  621. """The days of the :class:`Duration`."""
  622. return self[1]
  623. @property
  624. def seconds(self) -> int:
  625. """The seconds of the :class:`Duration`."""
  626. return self[2]
  627. @property
  628. def nanoseconds(self) -> int:
  629. """The nanoseconds of the :class:`Duration`."""
  630. return self[3]
  631. @property
  632. def years_months_days(self) -> tuple[int, int, int]:
  633. """
  634. Months and days components as a 3-tuple.
  635. tuple of years, months and days.
  636. """
  637. years, months = symmetric_divmod(self[0], 12)
  638. return years, months, self[1]
  639. @property
  640. def hours_minutes_seconds_nanoseconds(self) -> tuple[int, int, int, int]:
  641. """
  642. Seconds and nanoseconds components as a 4-tuple.
  643. tuple of hours, minutes, seconds and nanoseconds.
  644. """
  645. minutes, seconds = symmetric_divmod(self[2], 60)
  646. hours, minutes = symmetric_divmod(minutes, 60)
  647. return hours, minutes, seconds, self[3]
  648. Duration.min = Duration( # type: ignore
  649. seconds=MIN_INT64, nanoseconds=0
  650. )
  651. Duration.max = Duration( # type: ignore
  652. seconds=MAX_INT64, nanoseconds=999999999
  653. )
  654. if t.TYPE_CHECKING:
  655. # make typechecker believe that Date subclasses datetime.date
  656. # https://github.com/python/typeshed/issues/8409#issuecomment-1197704527
  657. date_base_class = date
  658. else:
  659. date_base_class = object
  660. class Date(date_base_class, metaclass=DateType):
  661. """
  662. Idealized date representation.
  663. A :class:`.Date` object represents a date (year, month, and day) in the
  664. `proleptic Gregorian Calendar
  665. <https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar>`_.
  666. Years between `0001` and `9999` are supported, with additional support for
  667. the "zero date" used in some contexts.
  668. Each date is based on a proleptic Gregorian ordinal, which models
  669. 1 Jan 0001 as `day 1` and counts each subsequent day up to, and including,
  670. 31 Dec 9999. The standard `year`, `month` and `day` value of each date is
  671. also available.
  672. Internally, the day of the month is always stored as-is, except for the
  673. last three days of that month. These are always stored as -1, -2 and -3
  674. (counting from the last day). This system allows some temporal
  675. arithmetic (particularly adding or subtracting months) to produce a more
  676. desirable outcome than would otherwise be produced. Externally, the day
  677. number is always the same as would be written on a calendar.
  678. :param year: the year. Minimum :data:`.MIN_YEAR` (0001), maximum
  679. :data:`.MAX_YEAR` (9999).
  680. :type year: int
  681. :param month: the month. Minimum 1, maximum 12.
  682. :type month: int
  683. :param day: the day. Minimum 1, maximum
  684. :attr:`Date.days_in_month(year, month) <Date.days_in_month>`.
  685. :type day: int
  686. A zero date can also be acquired by passing all zeroes to the
  687. :class:`.Date` constructor or by using the :data:`ZeroDate`
  688. constant.
  689. """
  690. # CONSTRUCTOR #
  691. def __new__(cls, year: int, month: int, day: int) -> Date:
  692. # TODO: 6.0 - remove the __new__ magic and ZeroDate being a singleton.
  693. # It's fine to remain as constant. Instead, simply use
  694. # __init__ and simplify pickle/copy (remove __reduce__).
  695. # N.B. this is a breaking change and must be treated as
  696. # such. Also consider introducing __slots__. Potentially
  697. # apply similar treatment to other temporal types as well
  698. # as spatial types.
  699. if year == month == day == 0:
  700. return ZeroDate
  701. year, month, day = _normalize_day(year, month, day)
  702. ordinal = cls.__calc_ordinal(year, month, day)
  703. return cls.__new(ordinal, year, month, day)
  704. @classmethod
  705. def __new(cls, ordinal: int, year: int, month: int, day: int) -> Date:
  706. instance = object.__new__(cls)
  707. instance.__ordinal = int(ordinal)
  708. instance.__year = int(year)
  709. instance.__month = int(month)
  710. instance.__day = int(day)
  711. return instance
  712. # CLASS METHODS #
  713. @classmethod
  714. def today(cls, tz: _tzinfo | None = None) -> Date:
  715. """
  716. Get the current date.
  717. :param tz: timezone or None to get the local :class:`.Date`.
  718. :raises OverflowError: if the timestamp is out of the range of values
  719. supported by the platform C localtime() function. It’s common for
  720. this to be restricted to years from 1970 through 2038.
  721. """
  722. if tz is None:
  723. return cls.from_clock_time(Clock().local_time(), UnixEpoch)
  724. else:
  725. return (
  726. DateTime.utc_now()
  727. .replace(tzinfo=timezone.utc)
  728. .astimezone(tz)
  729. .date()
  730. )
  731. @classmethod
  732. def utc_today(cls) -> Date:
  733. """Get the current date as UTC local date."""
  734. return cls.from_clock_time(Clock().utc_time(), UnixEpoch)
  735. @classmethod
  736. def from_timestamp(
  737. cls, timestamp: float, tz: _tzinfo | None = None
  738. ) -> Date:
  739. """
  740. Construct :class:`.Date` from a time stamp (seconds since unix epoch).
  741. :param timestamp: the unix timestamp (seconds since unix epoch).
  742. :param tz: timezone. Set to None to create a local :class:`.Date`.
  743. :raises OverflowError: if the timestamp is out of the range of values
  744. supported by the platform C localtime() function. It’s common for
  745. this to be restricted to years from 1970 through 2038.
  746. """
  747. return cls.from_native(datetime.fromtimestamp(timestamp, tz))
  748. @classmethod
  749. def utc_from_timestamp(cls, timestamp: float) -> Date:
  750. """
  751. Construct :class:`.Date` from a time stamp (seconds since unix epoch).
  752. :returns: the `Date` as local date `Date` in UTC.
  753. """
  754. return cls.from_clock_time((timestamp, 0), UnixEpoch)
  755. @classmethod
  756. def from_ordinal(cls, ordinal: int) -> Date:
  757. """
  758. Construct :class:`.Date` from the proleptic Gregorian ordinal.
  759. `0001-01-01` has ordinal 1 and `9999-12-31` has ordinal 3,652,059.
  760. Values outside of this range trigger a :exc:`ValueError`.
  761. The corresponding instance method for the reverse date-to-ordinal
  762. transformation is :meth:`.to_ordinal`.
  763. The ordinal 0 has a special semantic and will return :attr:`ZeroDate`.
  764. :raises ValueError: if the ordinal is outside the range [0, 3652059]
  765. (both values included).
  766. """
  767. if ordinal == 0:
  768. return ZeroDate
  769. elif ordinal < 0 or ordinal > 3652059:
  770. raise ValueError("Ordinal out of range (0..3652059)")
  771. d = datetime.fromordinal(ordinal)
  772. year, month, day = _normalize_day(d.year, d.month, d.day)
  773. return cls.__new(ordinal, year, month, day)
  774. @classmethod
  775. def parse(cls, s: str) -> Date:
  776. """
  777. Parse a string to produce a :class:`.Date`.
  778. Accepted formats:
  779. 'Y-M-D'
  780. :param s: the string to be parsed.
  781. :raises ValueError: if the string could not be parsed.
  782. """
  783. try:
  784. numbers = list(map(int, s.split("-")))
  785. except (ValueError, AttributeError):
  786. raise ValueError(
  787. "Date string must be in format YYYY-MM-DD"
  788. ) from None
  789. else:
  790. if len(numbers) == 3:
  791. return cls(*numbers)
  792. raise ValueError("Date string must be in format YYYY-MM-DD")
  793. @classmethod
  794. def from_iso_format(cls, s: str) -> Date:
  795. """
  796. Parse a ISO formatted Date string.
  797. Accepted formats:
  798. 'YYYY-MM-DD'
  799. :param s: the string to be parsed.
  800. :raises ValueError: if the string could not be parsed.
  801. """
  802. m = DATE_ISO_PATTERN.match(s)
  803. if m:
  804. year = int(m.group(1))
  805. month = int(m.group(2))
  806. day = int(m.group(3))
  807. return cls(year, month, day)
  808. raise ValueError("Date string must be in format YYYY-MM-DD")
  809. @classmethod
  810. def from_native(cls, d: date) -> Date:
  811. """
  812. Convert from a native Python `datetime.date` value.
  813. :param d: the date to convert.
  814. """
  815. return Date.from_ordinal(d.toordinal())
  816. @classmethod
  817. def from_clock_time(
  818. cls,
  819. clock_time: ClockTime | tuple[float, int],
  820. epoch: DateTime,
  821. ) -> Date:
  822. """
  823. Convert from a ClockTime relative to a given epoch.
  824. :param clock_time: the clock time as :class:`.ClockTime` or as tuple of
  825. (seconds, nanoseconds)
  826. :param epoch: the epoch to which `clock_time` is relative
  827. """
  828. try:
  829. clock_time = ClockTime(*clock_time)
  830. except (TypeError, ValueError):
  831. raise ValueError(
  832. "Clock time must be a 2-tuple of (s, ns)"
  833. ) from None
  834. else:
  835. ordinal = clock_time.seconds // 86400
  836. return Date.from_ordinal(ordinal + epoch.date().to_ordinal())
  837. @classmethod
  838. def is_leap_year(cls, year: int) -> bool:
  839. """
  840. Indicate whether `year` is a leap year.
  841. :param year: the year to look up
  842. :raises ValueError: if `year` is out of range:
  843. :attr:`MIN_YEAR` <= year <= :attr:`MAX_YEAR`
  844. """
  845. if year < MIN_YEAR or year > MAX_YEAR:
  846. raise ValueError(f"Year out of range ({MIN_YEAR}..{MAX_YEAR})")
  847. return IS_LEAP_YEAR[year]
  848. @classmethod
  849. def days_in_year(cls, year: int) -> int:
  850. """
  851. Return the number of days in `year`.
  852. :param year: the year to look up
  853. :raises ValueError: if `year` is out of range:
  854. :attr:`MIN_YEAR` <= year <= :attr:`MAX_YEAR`
  855. """
  856. if year < MIN_YEAR or year > MAX_YEAR:
  857. raise ValueError(f"Year out of range ({MIN_YEAR}..{MAX_YEAR})")
  858. return DAYS_IN_YEAR[year]
  859. @classmethod
  860. def days_in_month(cls, year: int, month: int) -> int:
  861. """
  862. Return the number of days in `month` of `year`.
  863. :param year: the year to look up
  864. :param month: the month to look up
  865. :raises ValueError: if `year` or `month` is out of range:
  866. :attr:`MIN_YEAR` <= year <= :attr:`MAX_YEAR`;
  867. 1 <= year <= 12
  868. """
  869. if year < MIN_YEAR or year > MAX_YEAR:
  870. raise ValueError(f"Year out of range ({MIN_YEAR}..{MAX_YEAR})")
  871. if month < 1 or month > 12:
  872. raise ValueError("Month out of range (1..12)")
  873. return DAYS_IN_MONTH[year, month]
  874. @classmethod
  875. def __calc_ordinal(cls, year, month, day):
  876. if day < 0:
  877. day = cls.days_in_month(year, month) + int(day) + 1
  878. # The built-in date class does this faster than a
  879. # long-hand pure Python algorithm could
  880. return date(year, month, day).toordinal()
  881. # CLASS METHOD ALIASES #
  882. if t.TYPE_CHECKING:
  883. @classmethod
  884. def fromisoformat(cls, s: str) -> Date: ...
  885. @classmethod
  886. def fromordinal(cls, ordinal: int) -> Date: ...
  887. @classmethod
  888. def fromtimestamp(
  889. cls, timestamp: float, tz: _tzinfo | None = None
  890. ) -> Date: ...
  891. @classmethod
  892. def utcfromtimestamp(cls, timestamp: float) -> Date: ...
  893. # CLASS ATTRIBUTES #
  894. min: te.Final[Date] = None # type: ignore
  895. """The earliest date value possible."""
  896. max: te.Final[Date] = None # type: ignore
  897. """The latest date value possible."""
  898. resolution: te.Final[Duration] = None # type: ignore
  899. """The minimum resolution supported."""
  900. # INSTANCE ATTRIBUTES #
  901. __ordinal = 0
  902. __year = 0
  903. __month = 0
  904. __day = 0
  905. @property
  906. def year(self) -> int:
  907. """
  908. The year of the date.
  909. :type: int
  910. """
  911. return self.__year
  912. @property
  913. def month(self) -> int:
  914. """
  915. The month of the date.
  916. :type: int
  917. """
  918. return self.__month
  919. @property
  920. def day(self) -> int:
  921. """
  922. The day of the date.
  923. :type: int
  924. """
  925. if self.__day == 0:
  926. return 0
  927. if self.__day >= 1:
  928. return self.__day
  929. return self.days_in_month(self.__year, self.__month) + self.__day + 1
  930. @property
  931. def year_month_day(self) -> tuple[int, int, int]:
  932. """3-tuple of (year, month, day) describing the date."""
  933. return self.year, self.month, self.day
  934. @property
  935. def year_week_day(self) -> tuple[int, int, int]:
  936. """
  937. 3-tuple of (year, week_of_year, day_of_week) describing the date.
  938. `day_of_week` will be 1 for Monday and 7 for Sunday.
  939. """
  940. ordinal = self.__ordinal
  941. year = self.__year
  942. def day_of_week(o):
  943. return ((o - 1) % 7) + 1
  944. def iso_week_1(y):
  945. j4 = Date(y, 1, 4)
  946. return j4 + Duration(days=(1 - day_of_week(j4.to_ordinal())))
  947. if ordinal >= Date(year, 12, 29).to_ordinal():
  948. week1 = iso_week_1(year + 1)
  949. if ordinal < week1.to_ordinal():
  950. week1 = iso_week_1(year)
  951. else:
  952. year += 1
  953. else:
  954. week1 = iso_week_1(year)
  955. if ordinal < week1.to_ordinal():
  956. year -= 1
  957. week1 = iso_week_1(year)
  958. return (
  959. year,
  960. int((ordinal - week1.to_ordinal()) / 7 + 1),
  961. day_of_week(ordinal),
  962. )
  963. @property
  964. def year_day(self) -> tuple[int, int]:
  965. """
  966. 2-tuple of (year, day_of_the_year) describing the date.
  967. This is the number of the day relative to the start of the year,
  968. with `1 Jan` corresponding to `1`.
  969. """
  970. return (
  971. self.__year,
  972. self.toordinal() - Date(self.__year, 1, 1).toordinal() + 1,
  973. )
  974. # OPERATIONS #
  975. def __hash__(self):
  976. return hash(self.toordinal())
  977. def __eq__(self, other: object) -> bool:
  978. """``==`` comparison with :class:`.Date` or :class:`datetime.date`."""
  979. if not isinstance(other, (Date, date)):
  980. # TODO: 6.0 - return NotImplemented for non-Date objects
  981. # return NotImplemented
  982. return False
  983. return self.toordinal() == other.toordinal()
  984. def __ne__(self, other: object) -> bool:
  985. """``!=`` comparison with :class:`.Date` or :class:`datetime.date`."""
  986. # TODO: 6.0 - return NotImplemented for non-Date objects
  987. # if not isinstance(other, (Date, date)):
  988. # return NotImplemented
  989. return not self.__eq__(other)
  990. def __lt__(self, other: Date | date) -> bool:
  991. """``<`` comparison with :class:`.Date` or :class:`datetime.date`."""
  992. if not isinstance(other, (Date, date)):
  993. # TODO: 6.0 - return NotImplemented for non-Date objects
  994. # return NotImplemented
  995. raise TypeError(
  996. "'<' not supported between instances of 'Date' and "
  997. f"{type(other).__name__!r}"
  998. )
  999. return self.toordinal() < other.toordinal()
  1000. def __le__(self, other: Date | date) -> bool:
  1001. """``<=`` comparison with :class:`.Date` or :class:`datetime.date`."""
  1002. if not isinstance(other, (Date, date)):
  1003. # TODO: 6.0 - return NotImplemented for non-Date objects
  1004. # return NotImplemented
  1005. raise TypeError(
  1006. "'<=' not supported between instances of 'Date' and "
  1007. f"{type(other).__name__!r}"
  1008. )
  1009. return self.toordinal() <= other.toordinal()
  1010. def __ge__(self, other: Date | date) -> bool:
  1011. """``>=`` comparison with :class:`.Date` or :class:`datetime.date`."""
  1012. if not isinstance(other, (Date, date)):
  1013. # TODO: 6.0 - return NotImplemented for non-Date objects
  1014. # return NotImplemented
  1015. raise TypeError(
  1016. "'>=' not supported between instances of 'Date' and "
  1017. f"{type(other).__name__!r}"
  1018. )
  1019. return self.toordinal() >= other.toordinal()
  1020. def __gt__(self, other: Date | date) -> bool:
  1021. """``>`` comparison with :class:`.Date` or :class:`datetime.date`."""
  1022. if not isinstance(other, (Date, date)):
  1023. # TODO: 6.0 - return NotImplemented for non-Date objects
  1024. # return NotImplemented
  1025. raise TypeError(
  1026. "'>' not supported between instances of 'Date' and "
  1027. f"{type(other).__name__!r}"
  1028. )
  1029. return self.toordinal() > other.toordinal()
  1030. def __add__(self, other: Duration) -> Date: # type: ignore[override]
  1031. """
  1032. Add a :class:`.Duration`.
  1033. :raises ValueError: if the added duration has a time component.
  1034. """
  1035. def add_months(d, months):
  1036. overflow_years, month = divmod(months + d.__month - 1, 12)
  1037. d.__year += overflow_years
  1038. d.__month = month + 1
  1039. def add_days(d, days):
  1040. assert 1 <= d.__day <= 28 or -28 <= d.__day <= -1
  1041. if d.__day >= 1:
  1042. new_days = d.__day + days
  1043. if 1 <= new_days <= 27:
  1044. d.__day = new_days
  1045. return
  1046. d0 = Date.from_ordinal(d.__ordinal + days)
  1047. d.__year, d.__month, d.__day = d0.__year, d0.__month, d0.__day
  1048. if isinstance(other, Duration):
  1049. if other.seconds or other.nanoseconds:
  1050. raise ValueError(
  1051. "Cannot add a Duration with seconds or nanoseconds to a "
  1052. "Date"
  1053. )
  1054. if other.months == other.days == 0:
  1055. return self
  1056. new_date = self.replace()
  1057. # Add days before months as the former sometimes
  1058. # requires the current ordinal to be correct.
  1059. if other.days:
  1060. add_days(new_date, other.days)
  1061. if other.months:
  1062. add_months(new_date, other.months)
  1063. new_date.__ordinal = self.__calc_ordinal(
  1064. new_date.year, new_date.month, new_date.day
  1065. )
  1066. return new_date
  1067. return NotImplemented
  1068. @t.overload # type: ignore[override]
  1069. def __sub__(self, other: Date | date) -> Duration: ...
  1070. @t.overload
  1071. def __sub__(self, other: Duration) -> Date: ...
  1072. def __sub__(self, other):
  1073. """
  1074. Subtract a :class:`.Date` or :class:`.Duration`.
  1075. :returns: If a :class:`.Date` is subtracted, the time between the two
  1076. dates is returned as :class:`.Duration`. If a :class:`.Duration` is
  1077. subtracted, a new :class:`.Date` is returned.
  1078. :rtype: Date or Duration
  1079. :raises ValueError: if the added duration has a time component.
  1080. """
  1081. if isinstance(other, (Date, date)):
  1082. return Duration(days=(self.toordinal() - other.toordinal()))
  1083. try:
  1084. return self.__add__(-other)
  1085. except TypeError:
  1086. return NotImplemented
  1087. def __reduce__(self):
  1088. if self is ZeroDate:
  1089. return "ZeroDate"
  1090. return type(self)._restore, (self.__dict__,)
  1091. @classmethod
  1092. def _restore(cls, dict_) -> Date:
  1093. instance = object.__new__(cls)
  1094. if dict_:
  1095. instance.__dict__.update(dict_)
  1096. return instance
  1097. # INSTANCE METHODS #
  1098. if t.TYPE_CHECKING:
  1099. def replace(
  1100. self,
  1101. year: te.SupportsIndex = ...,
  1102. month: te.SupportsIndex = ...,
  1103. day: te.SupportsIndex = ...,
  1104. **kwargs: object,
  1105. ) -> Date: ...
  1106. else:
  1107. def replace(self, **kwargs) -> Date:
  1108. """
  1109. Return a :class:`.Date` with one or more components replaced.
  1110. :Keyword Arguments:
  1111. * **year** (:class:`typing.SupportsIndex`):
  1112. overwrite the year - default: `self.year`
  1113. * **month** (:class:`typing.SupportsIndex`):
  1114. overwrite the month - default: `self.month`
  1115. * **day** (:class:`typing.SupportsIndex`):
  1116. overwrite the day - default: `self.day`
  1117. """
  1118. return Date(
  1119. int(kwargs.get("year", self.__year)),
  1120. int(kwargs.get("month", self.__month)),
  1121. int(kwargs.get("day", self.__day)),
  1122. )
  1123. def time_tuple(self) -> struct_time:
  1124. """Convert the date to :class:`time.struct_time`."""
  1125. _, _, day_of_week = self.year_week_day
  1126. _, day_of_year = self.year_day
  1127. return struct_time(
  1128. (
  1129. self.year,
  1130. self.month,
  1131. self.day,
  1132. 0,
  1133. 0,
  1134. 0,
  1135. day_of_week - 1,
  1136. day_of_year,
  1137. -1,
  1138. )
  1139. )
  1140. def to_ordinal(self) -> int:
  1141. """
  1142. Get the date's proleptic Gregorian ordinal.
  1143. The corresponding class method for the reverse ordinal-to-date
  1144. transformation is :meth:`.Date.from_ordinal`.
  1145. """
  1146. return self.__ordinal
  1147. def to_clock_time(self, epoch: Date | DateTime) -> ClockTime:
  1148. """
  1149. Convert the date to :class:`ClockTime` relative to `epoch`.
  1150. :param epoch: the epoch to which the date is relative
  1151. """
  1152. try:
  1153. return ClockTime(86400 * (self.to_ordinal() - epoch.to_ordinal()))
  1154. except AttributeError:
  1155. raise TypeError("Epoch has no ordinal value") from None
  1156. def to_native(self) -> date:
  1157. """Convert to a native Python :class:`datetime.date` value."""
  1158. return date.fromordinal(self.to_ordinal())
  1159. def weekday(self) -> int:
  1160. """Get the day of the week where Monday is 0 and Sunday is 6."""
  1161. return self.year_week_day[2] - 1
  1162. def iso_weekday(self) -> int:
  1163. """Get the day of the week where Monday is 1 and Sunday is 7."""
  1164. return self.year_week_day[2]
  1165. def iso_calendar(self) -> tuple[int, int, int]:
  1166. """Return :attr:`.year_week_day`."""
  1167. return self.year_week_day
  1168. def iso_format(self) -> str:
  1169. """Return the :class:`.Date` as ISO formatted string."""
  1170. if self.__ordinal == 0:
  1171. return "0000-00-00"
  1172. year, month, day = self.year_month_day
  1173. return f"{year:04d}-{month:02d}-{day:02d}"
  1174. def __repr__(self) -> str:
  1175. if self.__ordinal == 0:
  1176. return "neo4j.time.ZeroDate"
  1177. year, month, day = self.year_month_day
  1178. return f"neo4j.time.Date({year!r}, {month!r}, {day!r})"
  1179. def __str__(self) -> str:
  1180. return self.iso_format()
  1181. def __format__(self, format_spec):
  1182. if not format_spec:
  1183. return self.iso_format()
  1184. format_spec = FORMAT_F_REPLACE.sub("000000000", format_spec)
  1185. return self.to_native().__format__(format_spec)
  1186. # INSTANCE METHOD ALIASES #
  1187. def __getattr__(self, name):
  1188. """
  1189. Get attribute by name.
  1190. Map standard library attribute names to local attribute names,
  1191. for compatibility.
  1192. """
  1193. try:
  1194. return {
  1195. "isocalendar": self.iso_calendar,
  1196. "isoformat": self.iso_format,
  1197. "isoweekday": self.iso_weekday,
  1198. "strftime": self.__format__,
  1199. "toordinal": self.to_ordinal,
  1200. "timetuple": self.time_tuple,
  1201. }[name]
  1202. except KeyError:
  1203. raise AttributeError(f"Date has no attribute {name!r}") from None
  1204. if t.TYPE_CHECKING:
  1205. def iso_calendar(self) -> tuple[int, int, int]: ...
  1206. isoformat = iso_format
  1207. isoweekday = iso_weekday
  1208. strftime = __format__
  1209. toordinal = to_ordinal
  1210. timetuple = time_tuple
  1211. Date.min = Date.from_ordinal(1) # type: ignore
  1212. Date.max = Date.from_ordinal(3652059) # type: ignore
  1213. Date.resolution = Duration(days=1) # type: ignore
  1214. #: A :class:`neo4j.time.Date` instance set to `0000-00-00`.
  1215. #: This has an ordinal value of `0`.
  1216. ZeroDate = object.__new__(Date)
  1217. if t.TYPE_CHECKING:
  1218. # make typechecker believe that Time subclasses datetime.time
  1219. # https://github.com/python/typeshed/issues/8409#issuecomment-1197704527
  1220. time_base_class = time
  1221. else:
  1222. time_base_class = object
  1223. def _dst(
  1224. tz: _tzinfo | None = None, dt: DateTime | None = None
  1225. ) -> timedelta | None:
  1226. if tz is None:
  1227. return None
  1228. try:
  1229. value = tz.dst(dt)
  1230. except TypeError:
  1231. if dt is None:
  1232. raise
  1233. # For timezone implementations not compatible with the custom
  1234. # datetime implementations, we can't do better than this.
  1235. value = tz.dst(dt.to_native()) # type: ignore
  1236. if value is None:
  1237. return None
  1238. if isinstance(value, timedelta):
  1239. if value.days != 0:
  1240. raise ValueError("dst must be less than a day")
  1241. if value.seconds % 60 != 0 or value.microseconds != 0:
  1242. raise ValueError("dst must be a whole number of minutes")
  1243. return value
  1244. raise TypeError("dst must be a timedelta")
  1245. def _tz_name(tz: _tzinfo | None, dt: DateTime | None) -> str | None:
  1246. if tz is None:
  1247. return None
  1248. try:
  1249. return tz.tzname(dt)
  1250. except TypeError:
  1251. if dt is None:
  1252. raise
  1253. # For timezone implementations not compatible with the custom
  1254. # datetime implementations, we can't do better than this.
  1255. return tz.tzname(dt.to_native())
  1256. class Time(time_base_class, metaclass=TimeType):
  1257. """
  1258. Time of day.
  1259. The :class:`.Time` class is a nanosecond-precision drop-in replacement for
  1260. the standard library :class:`datetime.time` class.
  1261. A high degree of API compatibility with the standard library classes is
  1262. provided.
  1263. :class:`neo4j.time.Time` objects introduce the concept of ``ticks``.
  1264. This is simply a count of the number of nanoseconds since midnight,
  1265. in many ways analogous to the :class:`neo4j.time.Date` ordinal.
  1266. `ticks` values are integers, with a minimum value of `0` and a maximum
  1267. of `86_399_999_999_999`.
  1268. Local times are represented by :class:`.Time` with no ``tzinfo``.
  1269. :param hour: the hour of the time. Must be in range 0 <= hour < 24.
  1270. :param minute: the minute of the time. Must be in range 0 <= minute < 60.
  1271. :param second: the second of the time. Must be in range 0 <= second < 60.
  1272. :param nanosecond: the nanosecond of the time.
  1273. Must be in range 0 <= nanosecond < 999999999.
  1274. :param tzinfo: timezone or None to get a local :class:`.Time`.
  1275. :raises ValueError: if one of the parameters is out of range.
  1276. .. versionchanged:: 5.0
  1277. The parameter ``second`` no longer accepts :class:`float` values.
  1278. """
  1279. # CONSTRUCTOR #
  1280. def __init__(
  1281. self,
  1282. hour: int = 0,
  1283. minute: int = 0,
  1284. second: int = 0,
  1285. nanosecond: int = 0,
  1286. tzinfo: _tzinfo | None = None,
  1287. ) -> None:
  1288. hour, minute, second, nanosecond = self.__normalize_nanosecond(
  1289. hour, minute, second, nanosecond
  1290. )
  1291. ticks = (
  1292. 3600000000000 * hour
  1293. + 60000000000 * minute
  1294. + 1000000000 * second
  1295. + nanosecond
  1296. )
  1297. self.__unchecked_init(ticks, hour, minute, second, nanosecond, tzinfo)
  1298. @classmethod
  1299. def __unchecked_new(
  1300. cls,
  1301. ticks: int,
  1302. hour: int,
  1303. minutes: int,
  1304. second: int,
  1305. nano: int,
  1306. tz: _tzinfo | None,
  1307. ) -> Time:
  1308. instance = object.__new__(Time)
  1309. instance.__unchecked_init(ticks, hour, minutes, second, nano, tz)
  1310. return instance
  1311. def __unchecked_init(
  1312. self,
  1313. ticks: int,
  1314. hour: int,
  1315. minutes: int,
  1316. second: int,
  1317. nano: int,
  1318. tz: _tzinfo | None,
  1319. ) -> None:
  1320. self.__ticks = ticks
  1321. self.__hour = hour
  1322. self.__minute = minutes
  1323. self.__second = second
  1324. self.__nanosecond = nano
  1325. self.__tzinfo = tz
  1326. # CLASS METHODS #
  1327. @classmethod
  1328. def now(cls, tz: _tzinfo | None = None) -> Time:
  1329. """
  1330. Get the current time.
  1331. :param tz: optional timezone
  1332. :raises OverflowError: if the timestamp is out of the range of values
  1333. supported by the platform C localtime() function. It’s common for
  1334. this to be restricted to years from 1970 through 2038.
  1335. """
  1336. if tz is None:
  1337. return cls.from_clock_time(Clock().local_time(), UnixEpoch)
  1338. else:
  1339. return (
  1340. DateTime.utc_now()
  1341. .replace(tzinfo=timezone.utc)
  1342. .astimezone(tz)
  1343. .timetz()
  1344. )
  1345. @classmethod
  1346. def utc_now(cls) -> Time:
  1347. """Get the current time as UTC local time."""
  1348. return cls.from_clock_time(Clock().utc_time(), UnixEpoch)
  1349. @classmethod
  1350. def from_iso_format(cls, s: str) -> Time:
  1351. """
  1352. Parse a ISO formatted time string.
  1353. Accepted formats:
  1354. Local times:
  1355. 'hh'
  1356. 'hh:mm'
  1357. 'hh:mm:ss'
  1358. 'hh:mm:ss.ssss...'
  1359. Times with timezones (UTC offset):
  1360. '<local time>+hh:mm'
  1361. '<local time>+hh:mm:ss'
  1362. '<local time>+hh:mm:ss.ssss....'
  1363. '<local time>-hh:mm'
  1364. '<local time>-hh:mm:ss'
  1365. '<local time>-hh:mm:ss.ssss....'
  1366. Where the UTC offset will only respect hours and minutes.
  1367. Seconds and sub-seconds are ignored.
  1368. :param s: String to parse
  1369. :raises ValueError: if the string does not match the required format.
  1370. """
  1371. from pytz import FixedOffset # type: ignore
  1372. m = TIME_ISO_PATTERN.match(s)
  1373. if m:
  1374. hour = int(m.group(1))
  1375. minute = int(m.group(3) or 0)
  1376. second = int(m.group(6) or 0)
  1377. nanosecond = m.group(7)
  1378. if nanosecond:
  1379. nanosecond = int(nanosecond[1:10].ljust(9, "0"))
  1380. else:
  1381. nanosecond = 0
  1382. if m.group(8) is None:
  1383. return cls(hour, minute, second, nanosecond)
  1384. else:
  1385. offset_multiplier = 1 if m.group(9) == "+" else -1
  1386. offset_hour = int(m.group(10))
  1387. offset_minute = int(m.group(11))
  1388. # pytz only supports offsets of minute resolution
  1389. # so we can ignore this part
  1390. # offset_second = float(m.group(13) or 0.0)
  1391. offset = 60 * offset_hour + offset_minute
  1392. tz = FixedOffset(offset_multiplier * offset)
  1393. return cls(hour, minute, second, nanosecond, tzinfo=tz)
  1394. raise ValueError("Time string is not in ISO format")
  1395. @classmethod
  1396. def from_ticks(cls, ticks: int, tz: _tzinfo | None = None) -> Time:
  1397. """
  1398. Create a time from ticks (nanoseconds since midnight).
  1399. :param ticks: nanoseconds since midnight
  1400. :param tz: optional timezone
  1401. :raises ValueError: if ticks is out of bounds
  1402. (0 <= ticks < 86400000000000)
  1403. .. versionchanged:: 5.0
  1404. The parameter ``ticks`` no longer accepts :class:`float` values
  1405. but only :class:`int`. It's now nanoseconds since midnight instead
  1406. of seconds.
  1407. """
  1408. if not isinstance(ticks, int):
  1409. raise TypeError("Ticks must be int")
  1410. if 0 <= ticks < 86400000000000:
  1411. second, nanosecond = divmod(ticks, NANO_SECONDS)
  1412. minute, second = divmod(second, 60)
  1413. hour, minute = divmod(minute, 60)
  1414. return cls.__unchecked_new(
  1415. ticks, hour, minute, second, nanosecond, tz
  1416. )
  1417. raise ValueError("Ticks out of range (0..86400000000000)")
  1418. @classmethod
  1419. def from_native(cls, t: time) -> Time:
  1420. """
  1421. Convert from a native Python :class:`datetime.time` value.
  1422. :param t: time to convert from
  1423. """
  1424. nanosecond = t.microsecond * 1000
  1425. return Time(t.hour, t.minute, t.second, nanosecond, t.tzinfo)
  1426. @classmethod
  1427. def from_clock_time(
  1428. cls,
  1429. clock_time: ClockTime | tuple[float, int],
  1430. epoch: DateTime,
  1431. ) -> Time:
  1432. """
  1433. Convert from a :class:`.ClockTime` relative to a given epoch.
  1434. This method, in contrast to most others of this package, assumes days
  1435. of exactly 24 hours.
  1436. :param clock_time: the clock time as :class:`.ClockTime` or as tuple of
  1437. (seconds, nanoseconds)
  1438. :param epoch: the epoch to which `clock_time` is relative
  1439. """
  1440. clock_time = ClockTime(*clock_time)
  1441. ts = clock_time.seconds % 86400
  1442. nanoseconds = int(NANO_SECONDS * ts + clock_time.nanoseconds)
  1443. ticks = (epoch.time().ticks + nanoseconds) % (86400 * NANO_SECONDS)
  1444. return Time.from_ticks(ticks)
  1445. @classmethod
  1446. def __normalize_hour(cls, hour):
  1447. hour = int(hour)
  1448. if 0 <= hour < 24:
  1449. return hour
  1450. raise ValueError("Hour out of range (0..23)")
  1451. @classmethod
  1452. def __normalize_minute(cls, hour, minute):
  1453. hour = cls.__normalize_hour(hour)
  1454. minute = int(minute)
  1455. if 0 <= minute < 60:
  1456. return hour, minute
  1457. raise ValueError("Minute out of range (0..59)")
  1458. @classmethod
  1459. def __normalize_second(cls, hour, minute, second):
  1460. hour, minute = cls.__normalize_minute(hour, minute)
  1461. second = int(second)
  1462. if 0 <= second < 60:
  1463. return hour, minute, second
  1464. raise ValueError("Second out of range (0..59)")
  1465. @classmethod
  1466. def __normalize_nanosecond(cls, hour, minute, second, nanosecond):
  1467. hour, minute, second = cls.__normalize_second(hour, minute, second)
  1468. if 0 <= nanosecond < NANO_SECONDS:
  1469. return hour, minute, second, nanosecond
  1470. raise ValueError(f"Nanosecond out of range (0..{NANO_SECONDS - 1})")
  1471. # CLASS METHOD ALIASES #
  1472. if t.TYPE_CHECKING:
  1473. @classmethod
  1474. def from_iso_format(cls, s: str) -> Time: ...
  1475. @classmethod
  1476. def utc_now(cls) -> Time: ...
  1477. # CLASS ATTRIBUTES #
  1478. min: te.Final[Time] = None # type: ignore
  1479. """The earliest time value possible."""
  1480. max: te.Final[Time] = None # type: ignore
  1481. """The latest time value possible."""
  1482. resolution: te.Final[Duration] = None # type: ignore
  1483. """The minimum resolution supported."""
  1484. # INSTANCE ATTRIBUTES #
  1485. __ticks = 0
  1486. __hour = 0
  1487. __minute = 0
  1488. __second = 0
  1489. __nanosecond = 0
  1490. __tzinfo: _tzinfo | None = None
  1491. @property
  1492. def ticks(self) -> int:
  1493. """
  1494. The total number of nanoseconds since midnight.
  1495. .. versionchanged:: 5.0
  1496. The property's type changed from :class:`float` to :class:`int`.
  1497. It's now nanoseconds since midnight instead of seconds.
  1498. """
  1499. return self.__ticks
  1500. @property
  1501. def hour(self) -> int:
  1502. """The hours of the time."""
  1503. return self.__hour
  1504. @property
  1505. def minute(self) -> int:
  1506. """The minutes of the time."""
  1507. return self.__minute
  1508. @property
  1509. def second(self) -> int:
  1510. """
  1511. The seconds of the time.
  1512. .. versionchanged:: 4.4
  1513. The property's type changed from :class:`float` to
  1514. :class:`decimal.Decimal` to mitigate rounding issues.
  1515. .. versionchanged:: 5.0
  1516. The property's type changed from :class:`decimal.Decimal` to
  1517. :class:`int`. It does not longer cary sub-second information.
  1518. Use :attr:`nanosecond` instead.
  1519. """
  1520. return self.__second
  1521. @property
  1522. def nanosecond(self) -> int:
  1523. """The nanoseconds of the time."""
  1524. return self.__nanosecond
  1525. @property
  1526. def hour_minute_second_nanosecond(self) -> tuple[int, int, int, int]:
  1527. """The time as a tuple of (hour, minute, second, nanosecond)."""
  1528. return self.__hour, self.__minute, self.__second, self.__nanosecond
  1529. @property
  1530. def tzinfo(self) -> _tzinfo | None:
  1531. """The timezone of this time."""
  1532. return self.__tzinfo
  1533. # OPERATIONS #
  1534. def _get_both_normalized_ticks(self, other: object, strict=True):
  1535. if isinstance(other, (time, Time)) and (
  1536. (self.utc_offset() is None) ^ (other.utcoffset() is None)
  1537. ):
  1538. if strict:
  1539. raise TypeError(
  1540. "can't compare offset-naive and offset-aware times"
  1541. )
  1542. else:
  1543. return None, None
  1544. other_ticks: int
  1545. if isinstance(other, Time):
  1546. other_ticks = other.__ticks
  1547. elif isinstance(other, time):
  1548. other_ticks = int(
  1549. 3600000000000 * other.hour
  1550. + 60000000000 * other.minute
  1551. + NANO_SECONDS * other.second
  1552. + 1000 * other.microsecond
  1553. )
  1554. else:
  1555. return None, None
  1556. assert isinstance(other, (Time, time))
  1557. utc_offset: timedelta | None = other.utcoffset()
  1558. if utc_offset is not None:
  1559. other_ticks -= int(utc_offset.total_seconds() * NANO_SECONDS)
  1560. self_ticks = self.__ticks
  1561. utc_offset = self.utc_offset()
  1562. if utc_offset is not None:
  1563. self_ticks -= int(utc_offset.total_seconds() * NANO_SECONDS)
  1564. return self_ticks, other_ticks
  1565. def __hash__(self):
  1566. if self.__nanosecond % 1000 == 0:
  1567. return hash(self.to_native())
  1568. self_ticks = self.__ticks
  1569. if self.utc_offset() is not None:
  1570. self_ticks -= self.utc_offset().total_seconds() * NANO_SECONDS
  1571. return hash(self_ticks)
  1572. def __eq__(self, other: object) -> bool:
  1573. """`==` comparison with :class:`.Time` or :class:`datetime.time`."""
  1574. self_ticks, other_ticks = self._get_both_normalized_ticks(
  1575. other, strict=False
  1576. )
  1577. if self_ticks is None:
  1578. return False
  1579. return self_ticks == other_ticks
  1580. def __ne__(self, other: object) -> bool:
  1581. """`!=` comparison with :class:`.Time` or :class:`datetime.time`."""
  1582. return not self.__eq__(other)
  1583. def __lt__(self, other: Time | time) -> bool:
  1584. """`<` comparison with :class:`.Time` or :class:`datetime.time`."""
  1585. self_ticks, other_ticks = self._get_both_normalized_ticks(other)
  1586. if self_ticks is None:
  1587. return NotImplemented
  1588. return self_ticks < other_ticks
  1589. def __le__(self, other: Time | time) -> bool:
  1590. """`<=` comparison with :class:`.Time` or :class:`datetime.time`."""
  1591. self_ticks, other_ticks = self._get_both_normalized_ticks(other)
  1592. if self_ticks is None:
  1593. return NotImplemented
  1594. return self_ticks <= other_ticks
  1595. def __ge__(self, other: Time | time) -> bool:
  1596. """`>=` comparison with :class:`.Time` or :class:`datetime.time`."""
  1597. self_ticks, other_ticks = self._get_both_normalized_ticks(other)
  1598. if self_ticks is None:
  1599. return NotImplemented
  1600. return self_ticks >= other_ticks
  1601. def __gt__(self, other: Time | time) -> bool:
  1602. """`>` comparison with :class:`.Time` or :class:`datetime.time`."""
  1603. self_ticks, other_ticks = self._get_both_normalized_ticks(other)
  1604. if self_ticks is None:
  1605. return NotImplemented
  1606. return self_ticks > other_ticks
  1607. # INSTANCE METHODS #
  1608. if t.TYPE_CHECKING:
  1609. def replace( # type: ignore[override]
  1610. self,
  1611. hour: te.SupportsIndex = ...,
  1612. minute: te.SupportsIndex = ...,
  1613. second: te.SupportsIndex = ...,
  1614. nanosecond: te.SupportsIndex = ...,
  1615. tzinfo: _tzinfo | None = ...,
  1616. **kwargs: object,
  1617. ) -> Time: ...
  1618. else:
  1619. def replace(self, **kwargs) -> Time:
  1620. """
  1621. Return a :class:`.Time` with one or more components replaced.
  1622. :Keyword Arguments:
  1623. * **hour** (:class:`typing.SupportsIndex`):
  1624. overwrite the hour - default: `self.hour`
  1625. * **minute** (:class:`typing.SupportsIndex`):
  1626. overwrite the minute - default: `self.minute`
  1627. * **second** (:class:`typing.SupportsIndex`):
  1628. overwrite the second - default: `int(self.second)`
  1629. * **nanosecond** (:class:`typing.SupportsIndex`):
  1630. overwrite the nanosecond - default: `self.nanosecond`
  1631. * **tzinfo** (:class:`datetime.tzinfo` or `None`):
  1632. overwrite the timezone - default: `self.tzinfo`
  1633. """
  1634. return Time(
  1635. hour=int(kwargs.get("hour", self.__hour)),
  1636. minute=int(kwargs.get("minute", self.__minute)),
  1637. second=int(kwargs.get("second", self.__second)),
  1638. nanosecond=int(kwargs.get("nanosecond", self.__nanosecond)),
  1639. tzinfo=kwargs.get("tzinfo", self.__tzinfo),
  1640. )
  1641. def _utc_offset(self, dt=None):
  1642. if self.tzinfo is None:
  1643. return None
  1644. try:
  1645. value = self.tzinfo.utcoffset(dt)
  1646. except TypeError:
  1647. # For timezone implementations not compatible with the custom
  1648. # datetime implementations, we can't do better than this.
  1649. value = self.tzinfo.utcoffset(dt.to_native())
  1650. if value is None:
  1651. return None
  1652. if isinstance(value, timedelta):
  1653. s = value.total_seconds()
  1654. if not (-86400 < s < 86400):
  1655. raise ValueError("utcoffset must be less than a day")
  1656. if s % 60 != 0 or value.microseconds != 0:
  1657. raise ValueError("utcoffset must be a whole number of minutes")
  1658. return value
  1659. raise TypeError("utcoffset must be a timedelta")
  1660. def utc_offset(self) -> timedelta | None:
  1661. """
  1662. Return the UTC offset of this time.
  1663. :returns: None if this is a local time (:attr:`.tzinfo` is None), else
  1664. returns `self.tzinfo.utcoffset(self)`.
  1665. :raises ValueError: if `self.tzinfo.utcoffset(self)` is not None and a
  1666. :class:`timedelta` with a magnitude greater equal 1 day or that is
  1667. not a whole number of minutes.
  1668. :raises TypeError: if `self.tzinfo.utcoffset(self)` does return
  1669. anything but :data:`None` or a :class:`datetime.timedelta`.
  1670. """
  1671. return self._utc_offset()
  1672. def dst(self) -> timedelta | None:
  1673. """
  1674. Get the daylight saving time adjustment (DST).
  1675. :returns: None if this is a local time (:attr:`.tzinfo` is None), else
  1676. returns `self.tzinfo.dst(self)`.
  1677. :raises ValueError: if `self.tzinfo.dst(self)` is not None and a
  1678. :class:`timedelta` with a magnitude greater equal 1 day or that is
  1679. not a whole number of minutes.
  1680. :raises TypeError: if `self.tzinfo.dst(self)` does return anything but
  1681. None or a :class:`datetime.timedelta`.
  1682. """
  1683. return _dst(self.tzinfo, None)
  1684. def tzname(self) -> str | None:
  1685. """
  1686. Get the name of the :class:`.Time`'s timezone.
  1687. :returns: None if the time is local (i.e., has no timezone), else
  1688. return `self.tzinfo.tzname(self)`
  1689. """
  1690. return _tz_name(self.tzinfo, None)
  1691. def to_clock_time(self) -> ClockTime:
  1692. """Convert to :class:`.ClockTime`."""
  1693. seconds, nanoseconds = divmod(self.ticks, NANO_SECONDS)
  1694. return ClockTime(seconds, nanoseconds)
  1695. def to_native(self) -> time:
  1696. """
  1697. Convert to a native Python `datetime.time` value.
  1698. This conversion is lossy as the native time implementation only
  1699. supports a resolution of microseconds instead of nanoseconds.
  1700. """
  1701. h, m, s, ns = self.hour_minute_second_nanosecond
  1702. µs = round_half_to_even(ns / 1000)
  1703. tz = self.tzinfo
  1704. return time(h, m, s, µs, tz)
  1705. def iso_format(self) -> str:
  1706. """Return the :class:`.Time` as ISO formatted string."""
  1707. h, m, s, ns = self.hour_minute_second_nanosecond
  1708. res = f"{h:02d}:{m:02d}:{s:02d}.{ns:09d}"
  1709. offset = self.utc_offset()
  1710. if offset is not None:
  1711. offset_hours, offset_minutes = divmod(
  1712. int(offset.total_seconds() // 60), 60
  1713. )
  1714. res += f"{offset_hours:+03d}:{offset_minutes:02d}"
  1715. return res
  1716. def __repr__(self) -> str:
  1717. fields: tuple[str, ...]
  1718. if self.tzinfo is None:
  1719. fields = tuple(map(repr, self.hour_minute_second_nanosecond))
  1720. else:
  1721. fields = (
  1722. *map(repr, self.hour_minute_second_nanosecond),
  1723. f"tzinfo={self.tzinfo!r}",
  1724. )
  1725. return f"neo4j.time.Time({', '.join(fields)})"
  1726. def __str__(self) -> str:
  1727. return self.iso_format()
  1728. def __format__(self, format_spec):
  1729. if not format_spec:
  1730. return self.iso_format()
  1731. format_spec = FORMAT_F_REPLACE.sub(
  1732. f"{self.__nanosecond:09}", format_spec
  1733. )
  1734. return self.to_native().__format__(format_spec)
  1735. # INSTANCE METHOD ALIASES #
  1736. def __getattr__(self, name):
  1737. """
  1738. Get attribute by name.
  1739. Map standard library attribute names to local attribute names,
  1740. for compatibility.
  1741. """
  1742. try:
  1743. return {
  1744. "isoformat": self.iso_format,
  1745. "utcoffset": self.utc_offset,
  1746. }[name]
  1747. except KeyError:
  1748. raise AttributeError(f"Date has no attribute {name!r}") from None
  1749. if t.TYPE_CHECKING:
  1750. def isoformat(self) -> str: # type: ignore[override]
  1751. ...
  1752. utcoffset = utc_offset
  1753. Time.min = Time( # type: ignore
  1754. hour=0, minute=0, second=0, nanosecond=0
  1755. )
  1756. Time.max = Time( # type: ignore
  1757. hour=23, minute=59, second=59, nanosecond=999999999
  1758. )
  1759. Time.resolution = Duration( # type: ignore
  1760. nanoseconds=1
  1761. )
  1762. #: A :class:`.Time` instance set to `00:00:00`.
  1763. #: This has a :attr:`.ticks` value of `0`.
  1764. Midnight: te.Final[Time] = Time.min
  1765. #: A :class:`.Time` instance set to `12:00:00`.
  1766. #: This has a :attr:`.ticks` value of `43200000000000`.
  1767. Midday: te.Final[Time] = Time(hour=12)
  1768. if t.TYPE_CHECKING:
  1769. # make typechecker believe that DateTime subclasses datetime.datetime
  1770. # https://github.com/python/typeshed/issues/8409#issuecomment-1197704527
  1771. date_time_base_class = datetime
  1772. else:
  1773. date_time_base_class = object
  1774. @total_ordering
  1775. class DateTime(date_time_base_class, metaclass=DateTimeType):
  1776. """
  1777. A point in time represented as a date and a time.
  1778. The :class:`.DateTime` class is a nanosecond-precision drop-in replacement
  1779. for the standard library :class:`datetime.datetime` class.
  1780. As such, it contains both :class:`.Date` and :class:`.Time` information and
  1781. draws functionality from those individual classes.
  1782. A :class:`.DateTime` object is fully compatible with the Python time zone
  1783. library `pytz <https://pypi.org/project/pytz/>`_. Functions such as
  1784. `normalize` and `localize` can be used in the same way as they are with the
  1785. standard library classes.
  1786. Regular construction of a :class:`.DateTime` object requires at
  1787. least the `year`, `month` and `day` arguments to be supplied. The
  1788. optional `hour`, `minute` and `second` arguments default to zero and
  1789. `tzinfo` defaults to :data:`None`.
  1790. `year`, `month`, and `day` are passed to the constructor of :class:`.Date`.
  1791. `hour`, `minute`, `second`, `nanosecond`, and `tzinfo` are passed to the
  1792. constructor of :class:`.Time`. See their documentation for more details.
  1793. >>> dt = DateTime(2018, 4, 30, 12, 34, 56, 789123456); dt
  1794. neo4j.time.DateTime(2018, 4, 30, 12, 34, 56, 789123456)
  1795. >>> dt.second
  1796. 56
  1797. """
  1798. __date: Date
  1799. __time: Time
  1800. # CONSTRUCTOR #
  1801. def __new__(
  1802. cls,
  1803. year: int,
  1804. month: int,
  1805. day: int,
  1806. hour: int = 0,
  1807. minute: int = 0,
  1808. second: int = 0,
  1809. nanosecond: int = 0,
  1810. tzinfo: _tzinfo | None = None,
  1811. ) -> DateTime:
  1812. return cls.combine(
  1813. Date(year, month, day),
  1814. Time(hour, minute, second, nanosecond, tzinfo),
  1815. )
  1816. # CLASS METHODS #
  1817. @classmethod
  1818. def now(cls, tz: _tzinfo | None = None) -> DateTime:
  1819. """
  1820. Get the current date and time.
  1821. :param tz: timezone. Set to None to create a local :class:`.DateTime`.
  1822. :raises OverflowError: if the timestamp is out of the range of values
  1823. supported by the platform C localtime() function. It’s common for
  1824. this to be restricted to years from 1970 through 2038.
  1825. """
  1826. if tz is None:
  1827. return cls.from_clock_time(Clock().local_time(), UnixEpoch)
  1828. else:
  1829. utc_now = cls.from_clock_time(
  1830. Clock().utc_time(), UnixEpoch
  1831. ).replace(tzinfo=tz)
  1832. try:
  1833. return tz.fromutc(utc_now) # type: ignore
  1834. except TypeError:
  1835. # For timezone implementations not compatible with the custom
  1836. # datetime implementations, we can't do better than this.
  1837. utc_now_native = utc_now.to_native()
  1838. now_native = tz.fromutc(utc_now_native)
  1839. now = cls.from_native(now_native)
  1840. return now.replace(
  1841. nanosecond=(
  1842. now.nanosecond
  1843. + utc_now.nanosecond
  1844. - utc_now_native.microsecond * 1000
  1845. )
  1846. )
  1847. @classmethod
  1848. def utc_now(cls) -> DateTime:
  1849. """Get the current date and time in UTC."""
  1850. return cls.from_clock_time(Clock().utc_time(), UnixEpoch)
  1851. @classmethod
  1852. def from_iso_format(cls, s) -> DateTime:
  1853. """
  1854. Parse a ISO formatted date with time string.
  1855. :param s: String to parse
  1856. :raises ValueError: if the string does not match the ISO format.
  1857. """
  1858. try:
  1859. return cls.combine(
  1860. Date.from_iso_format(s[0:10]), Time.from_iso_format(s[11:])
  1861. )
  1862. except ValueError as e:
  1863. raise ValueError("DateTime string is not in ISO format") from e
  1864. @classmethod
  1865. def from_timestamp(
  1866. cls, timestamp: float, tz: _tzinfo | None = None
  1867. ) -> DateTime:
  1868. """
  1869. :class:`.DateTime` from a time stamp (seconds since unix epoch).
  1870. :param timestamp: the unix timestamp (seconds since unix epoch).
  1871. :param tz: timezone. Set to None to create a local :class:`.DateTime`.
  1872. :raises OverflowError: if the timestamp is out of the range of values
  1873. supported by the platform C localtime() function. It’s common for
  1874. this to be restricted to years from 1970 through 2038.
  1875. """
  1876. if tz is None:
  1877. return cls.from_clock_time(
  1878. ClockTime(timestamp) + Clock().local_offset(), UnixEpoch
  1879. )
  1880. else:
  1881. return (
  1882. cls.utc_from_timestamp(timestamp)
  1883. .replace(tzinfo=timezone.utc)
  1884. .astimezone(tz)
  1885. )
  1886. @classmethod
  1887. def utc_from_timestamp(cls, timestamp: float) -> DateTime:
  1888. """
  1889. :class:`.DateTime` from a time stamp (seconds since unix epoch).
  1890. Returns the `DateTime` as local date `DateTime` in UTC.
  1891. """
  1892. return cls.from_clock_time((timestamp, 0), UnixEpoch)
  1893. @classmethod
  1894. def from_ordinal(cls, ordinal: int) -> DateTime:
  1895. """
  1896. :class:`.DateTime` from an ordinal.
  1897. For more info about ordinals see :meth:`.Date.from_ordinal`.
  1898. """
  1899. return cls.combine(Date.from_ordinal(ordinal), Midnight)
  1900. @classmethod
  1901. def combine( # type: ignore[override]
  1902. cls, date: Date, time: Time
  1903. ) -> DateTime:
  1904. """
  1905. Combine a :class:`.Date` and a :class:`.Time` to a :class:`DateTime`.
  1906. :param date: the date
  1907. :param time: the time
  1908. :raises AssertionError: if the parameter types don't match.
  1909. """
  1910. assert isinstance(date, Date)
  1911. assert isinstance(time, Time)
  1912. return cls._combine(date, time)
  1913. @classmethod
  1914. def _combine(cls, date: Date, time: Time) -> DateTime:
  1915. instance = object.__new__(cls)
  1916. instance.__date = date
  1917. instance.__time = time
  1918. return instance
  1919. @classmethod
  1920. def parse(cls, date_string, format):
  1921. raise NotImplementedError
  1922. @classmethod
  1923. def from_native(cls, dt: datetime) -> DateTime:
  1924. """
  1925. Convert from a native Python :class:`datetime.datetime` value.
  1926. :param dt: the datetime to convert
  1927. """
  1928. return cls.combine(
  1929. Date.from_native(dt.date()), Time.from_native(dt.timetz())
  1930. )
  1931. @classmethod
  1932. def from_clock_time(
  1933. cls,
  1934. clock_time: ClockTime | tuple[float, int],
  1935. epoch: DateTime,
  1936. ) -> DateTime:
  1937. """
  1938. Convert from a :class:`ClockTime` relative to a given epoch.
  1939. :param clock_time: the clock time as :class:`.ClockTime` or as tuple of
  1940. (seconds, nanoseconds)
  1941. :param epoch: the epoch to which `clock_time` is relative
  1942. :raises ValueError: if `clock_time` is invalid.
  1943. """
  1944. try:
  1945. seconds, nanoseconds = ClockTime(*clock_time)
  1946. except (TypeError, ValueError) as e:
  1947. raise ValueError("Clock time must be a 2-tuple of (s, ns)") from e
  1948. else:
  1949. ordinal, seconds = divmod(seconds, 86400)
  1950. ticks = epoch.time().ticks + seconds * NANO_SECONDS + nanoseconds
  1951. days, ticks = divmod(ticks, 86400 * NANO_SECONDS)
  1952. ordinal += days
  1953. date_ = Date.from_ordinal(ordinal + epoch.date().to_ordinal())
  1954. time_ = Time.from_ticks(ticks)
  1955. return cls.combine(date_, time_)
  1956. # CLASS METHOD ALIASES #
  1957. if t.TYPE_CHECKING:
  1958. @classmethod
  1959. def fromisoformat(cls, s) -> DateTime: ...
  1960. @classmethod
  1961. def fromordinal(cls, ordinal: int) -> DateTime: ...
  1962. @classmethod
  1963. def fromtimestamp(
  1964. cls, timestamp: float, tz: _tzinfo | None = None
  1965. ) -> DateTime: ...
  1966. # alias of parse
  1967. @classmethod
  1968. def strptime(cls, date_string, format): ...
  1969. # alias of now
  1970. @classmethod
  1971. def today(cls, tz: _tzinfo | None = None) -> DateTime: ...
  1972. @classmethod
  1973. def utcfromtimestamp(cls, timestamp: float) -> DateTime: ...
  1974. @classmethod
  1975. def utcnow(cls) -> DateTime: ...
  1976. # CLASS ATTRIBUTES #
  1977. min: te.Final[DateTime] = None # type: ignore
  1978. """The earliest date time value possible."""
  1979. max: te.Final[DateTime] = None # type: ignore
  1980. """The latest date time value possible."""
  1981. resolution: te.Final[Duration] = None # type: ignore
  1982. """The minimum resolution supported."""
  1983. # INSTANCE ATTRIBUTES #
  1984. @property
  1985. def year(self) -> int:
  1986. """
  1987. The year of the :class:`.DateTime`.
  1988. See :attr:`.Date.year`.
  1989. """
  1990. return self.__date.year
  1991. @property
  1992. def month(self) -> int:
  1993. """
  1994. The year of the :class:`.DateTime`.
  1995. See :attr:`.Date.year`.
  1996. """
  1997. return self.__date.month
  1998. @property
  1999. def day(self) -> int:
  2000. """
  2001. The day of the :class:`.DateTime`'s date.
  2002. See :attr:`.Date.day`.
  2003. """
  2004. return self.__date.day
  2005. @property
  2006. def year_month_day(self) -> tuple[int, int, int]:
  2007. """
  2008. The year_month_day of the :class:`.DateTime`'s date.
  2009. See :attr:`.Date.year_month_day`.
  2010. """
  2011. return self.__date.year_month_day
  2012. @property
  2013. def year_week_day(self) -> tuple[int, int, int]:
  2014. """
  2015. The year_week_day of the :class:`.DateTime`'s date.
  2016. See :attr:`.Date.year_week_day`.
  2017. """
  2018. return self.__date.year_week_day
  2019. @property
  2020. def year_day(self) -> tuple[int, int]:
  2021. """
  2022. The year_day of the :class:`.DateTime`'s date.
  2023. See :attr:`.Date.year_day`.
  2024. """
  2025. return self.__date.year_day
  2026. @property
  2027. def hour(self) -> int:
  2028. """
  2029. The hour of the :class:`.DateTime`'s time.
  2030. See :attr:`.Time.hour`.
  2031. """
  2032. return self.__time.hour
  2033. @property
  2034. def minute(self) -> int:
  2035. """
  2036. The minute of the :class:`.DateTime`'s time.
  2037. See :attr:`.Time.minute`.
  2038. """
  2039. return self.__time.minute
  2040. @property
  2041. def second(self) -> int:
  2042. """
  2043. The second of the :class:`.DateTime`'s time.
  2044. See :attr:`.Time.second`.
  2045. """
  2046. return self.__time.second
  2047. @property
  2048. def nanosecond(self) -> int:
  2049. """
  2050. The nanosecond of the :class:`.DateTime`'s time.
  2051. See :attr:`.Time.nanosecond`.
  2052. """
  2053. return self.__time.nanosecond
  2054. @property
  2055. def tzinfo(self) -> _tzinfo | None:
  2056. """
  2057. The tzinfo of the :class:`.DateTime`'s time.
  2058. See :attr:`.Time.tzinfo`.
  2059. """
  2060. return self.__time.tzinfo
  2061. @property
  2062. def hour_minute_second_nanosecond(self) -> tuple[int, int, int, int]:
  2063. """
  2064. The hour_minute_second_nanosecond of the :class:`.DateTime`'s time.
  2065. See :attr:`.Time.hour_minute_second_nanosecond`.
  2066. """
  2067. return self.__time.hour_minute_second_nanosecond
  2068. # OPERATIONS #
  2069. def _get_both_normalized(self, other, strict=True):
  2070. if isinstance(other, (datetime, DateTime)) and (
  2071. (self.utc_offset() is None) ^ (other.utcoffset() is None)
  2072. ):
  2073. if strict:
  2074. raise TypeError(
  2075. "can't compare offset-naive and offset-aware datetimes"
  2076. )
  2077. else:
  2078. return None, None
  2079. self_norm = self
  2080. utc_offset = self.utc_offset()
  2081. if utc_offset is not None:
  2082. self_norm -= utc_offset
  2083. self_norm = self_norm.replace(tzinfo=None)
  2084. other_norm = other
  2085. if isinstance(other, (datetime, DateTime)):
  2086. utc_offset = other.utcoffset()
  2087. if utc_offset is not None:
  2088. other_norm -= utc_offset
  2089. other_norm = other_norm.replace(tzinfo=None)
  2090. else:
  2091. return None, None
  2092. return self_norm, other_norm
  2093. def __hash__(self):
  2094. if self.nanosecond % 1000 == 0:
  2095. return hash(self.to_native())
  2096. self_norm = self
  2097. utc_offset = self.utc_offset()
  2098. if utc_offset is not None:
  2099. self_norm -= utc_offset
  2100. return hash(self_norm.date()) ^ hash(self_norm.time())
  2101. def __eq__(self, other: object) -> bool:
  2102. """
  2103. ``==`` comparison with another datetime.
  2104. Accepts :class:`.DateTime` and :class:`datetime.datetime`.
  2105. """
  2106. if not isinstance(other, (datetime, DateTime)):
  2107. return NotImplemented
  2108. if self.utc_offset() == other.utcoffset():
  2109. return self.date() == other.date() and self.time() == other.time()
  2110. self_norm, other_norm = self._get_both_normalized(other, strict=False)
  2111. if self_norm is None:
  2112. return False
  2113. return self_norm == other_norm
  2114. def __ne__(self, other: object) -> bool:
  2115. """
  2116. ``!=`` comparison with another datetime.
  2117. Accepts :class:`.DateTime` and :class:`datetime.datetime`.
  2118. """
  2119. if not isinstance(other, (DateTime, datetime)):
  2120. return NotImplemented
  2121. return not self.__eq__(other)
  2122. def __lt__( # type: ignore[override]
  2123. self, other: datetime | DateTime
  2124. ) -> bool:
  2125. """
  2126. ``<`` comparison with another datetime.
  2127. Accepts :class:`.DateTime` and :class:`datetime.datetime`.
  2128. """
  2129. if not isinstance(other, (datetime, DateTime)):
  2130. return NotImplemented
  2131. if self.utc_offset() == other.utcoffset():
  2132. if self.date() == other.date():
  2133. return self.time() < other.time()
  2134. return self.date() < other.date()
  2135. self_norm, other_norm = self._get_both_normalized(other)
  2136. return (
  2137. self_norm.date() < other_norm.date()
  2138. or self_norm.time() < other_norm.time()
  2139. )
  2140. def __le__( # type: ignore[override]
  2141. self, other: datetime | DateTime
  2142. ) -> bool:
  2143. """
  2144. ``<=`` comparison with another datetime.
  2145. Accepts :class:`.DateTime` and :class:`datetime.datetime`.
  2146. """
  2147. if not isinstance(other, (datetime, DateTime)):
  2148. return NotImplemented
  2149. if self.utc_offset() == other.utcoffset():
  2150. if self.date() == other.date():
  2151. return self.time() <= other.time()
  2152. return self.date() <= other.date()
  2153. self_norm, other_norm = self._get_both_normalized(other)
  2154. return self_norm <= other_norm
  2155. def __ge__( # type: ignore[override]
  2156. self, other: datetime | DateTime
  2157. ) -> bool:
  2158. """
  2159. ``>=`` comparison with another datetime.
  2160. Accepts :class:`.DateTime` and :class:`datetime.datetime`.
  2161. """
  2162. if not isinstance(other, (datetime, DateTime)):
  2163. return NotImplemented
  2164. if self.utc_offset() == other.utcoffset():
  2165. if self.date() == other.date():
  2166. return self.time() >= other.time()
  2167. return self.date() >= other.date()
  2168. self_norm, other_norm = self._get_both_normalized(other)
  2169. return self_norm >= other_norm
  2170. def __gt__( # type: ignore[override]
  2171. self, other: object
  2172. ) -> bool:
  2173. """
  2174. ``>`` comparison with another datetime.
  2175. Accepts :class:`.DateTime` and :class:`datetime.datetime`.
  2176. """
  2177. if not isinstance(other, (datetime, DateTime)):
  2178. return NotImplemented
  2179. if self.utc_offset() == other.utcoffset():
  2180. if self.date() == other.date():
  2181. return self.time() > other.time()
  2182. return self.date() > other.date()
  2183. self_norm, other_norm = self._get_both_normalized(other)
  2184. return (
  2185. self_norm.date() > other_norm.date()
  2186. or self_norm.time() > other_norm.time()
  2187. )
  2188. def __add__(self, other: timedelta | Duration) -> DateTime:
  2189. """Add a :class:`datetime.timedelta`."""
  2190. if isinstance(other, Duration):
  2191. if other == (0, 0, 0, 0):
  2192. return self
  2193. t = self.time().to_clock_time() + ClockTime(
  2194. other.seconds, other.nanoseconds
  2195. )
  2196. days, seconds = symmetric_divmod(t.seconds, 86400)
  2197. date_ = self.date() + Duration(
  2198. months=other.months, days=days + other.days
  2199. )
  2200. time_ = Time.from_ticks(seconds * NANO_SECONDS + t.nanoseconds)
  2201. return self.combine(date_, time_).replace(tzinfo=self.tzinfo)
  2202. if isinstance(other, timedelta):
  2203. if other.total_seconds() == 0:
  2204. return self
  2205. t = self.to_clock_time() + ClockTime(
  2206. 86400 * other.days + other.seconds,
  2207. other.microseconds * 1000,
  2208. )
  2209. days, seconds = symmetric_divmod(t.seconds, 86400)
  2210. date_ = Date.from_ordinal(days + 1)
  2211. time_ = Time.from_ticks(
  2212. round_half_to_even(seconds * NANO_SECONDS + t.nanoseconds)
  2213. )
  2214. return self.combine(date_, time_).replace(tzinfo=self.tzinfo)
  2215. return NotImplemented
  2216. @t.overload # type: ignore[override]
  2217. def __sub__(self, other: DateTime) -> Duration: ...
  2218. @t.overload
  2219. def __sub__(self, other: datetime) -> timedelta: ...
  2220. @t.overload
  2221. def __sub__(self, other: Duration | timedelta) -> DateTime: ...
  2222. def __sub__(self, other):
  2223. """
  2224. Subtract a datetime/DateTime or a timedelta/Duration.
  2225. Subtracting a :class:`.DateTime` yields the duration between the two
  2226. as a :class:`.Duration`.
  2227. Subtracting a :class:`datetime.datetime` yields the duration between
  2228. the two as a :class:`datetime.timedelta`.
  2229. Subtracting a :class:`datetime.timedelta` or a :class:`.Duration`
  2230. yields the :class:`.DateTime` that's the given duration away.
  2231. """
  2232. if isinstance(other, DateTime):
  2233. self_month_ordinal = 12 * (self.year - 1) + self.month
  2234. other_month_ordinal = 12 * (other.year - 1) + other.month
  2235. months = self_month_ordinal - other_month_ordinal
  2236. days = self.day - other.day
  2237. t = self.time().to_clock_time() - other.time().to_clock_time()
  2238. return Duration(
  2239. months=months,
  2240. days=days,
  2241. seconds=t.seconds,
  2242. nanoseconds=t.nanoseconds,
  2243. )
  2244. if isinstance(other, datetime):
  2245. days = self.to_ordinal() - other.toordinal()
  2246. t = self.time().to_clock_time() - ClockTime(
  2247. 3600 * other.hour + 60 * other.minute + other.second,
  2248. other.microsecond * 1000,
  2249. )
  2250. return timedelta(
  2251. days=days,
  2252. seconds=t.seconds,
  2253. microseconds=(t.nanoseconds // 1000),
  2254. )
  2255. if isinstance(other, Duration):
  2256. return self.__add__(-other)
  2257. if isinstance(other, timedelta):
  2258. return self.__add__(-other)
  2259. return NotImplemented
  2260. def __reduce__(self):
  2261. return type(self)._restore, (self.__dict__,)
  2262. @classmethod
  2263. def _restore(cls, dict_):
  2264. instance = object.__new__(cls)
  2265. if dict_:
  2266. instance.__dict__.update(dict_)
  2267. return instance
  2268. # INSTANCE METHODS #
  2269. def date(self) -> Date:
  2270. """Get the date."""
  2271. return self.__date
  2272. def time(self) -> Time:
  2273. """Get the time without timezone info."""
  2274. return self.__time.replace(tzinfo=None)
  2275. def timetz(self) -> Time:
  2276. """Get the time with timezone info."""
  2277. return self.__time
  2278. if t.TYPE_CHECKING:
  2279. def replace( # type: ignore[override]
  2280. self,
  2281. year: te.SupportsIndex = ...,
  2282. month: te.SupportsIndex = ...,
  2283. day: te.SupportsIndex = ...,
  2284. hour: te.SupportsIndex = ...,
  2285. minute: te.SupportsIndex = ...,
  2286. second: te.SupportsIndex = ...,
  2287. nanosecond: te.SupportsIndex = ...,
  2288. tzinfo: _tzinfo | None = ...,
  2289. **kwargs: object,
  2290. ) -> DateTime: ...
  2291. else:
  2292. def replace(self, **kwargs) -> DateTime:
  2293. """
  2294. Return a ``DateTime`` with one or more components replaced.
  2295. See :meth:`.Date.replace` and :meth:`.Time.replace` for available
  2296. arguments.
  2297. """
  2298. date_ = self.__date.replace(**kwargs)
  2299. time_ = self.__time.replace(**kwargs)
  2300. return self.combine(date_, time_)
  2301. def as_timezone(self, tz: _tzinfo) -> DateTime:
  2302. """
  2303. Convert this :class:`.DateTime` to another timezone.
  2304. :param tz: the new timezone
  2305. :returns: the same object if ``tz`` is :data:``None``.
  2306. Else, a new :class:`.DateTime` that's the same point in time but in
  2307. a different timezone.
  2308. """
  2309. if self.tzinfo is None:
  2310. return self
  2311. offset = t.cast(timedelta, self.utcoffset())
  2312. utc = (self - offset).replace(tzinfo=tz)
  2313. try:
  2314. return tz.fromutc(utc) # type: ignore
  2315. except TypeError:
  2316. # For timezone implementations not compatible with the custom
  2317. # datetime implementations, we can't do better than this.
  2318. native_utc = utc.to_native()
  2319. native_res = tz.fromutc(native_utc)
  2320. res = self.from_native(native_res)
  2321. ns = native_res.microsecond * 1000 + self.nanosecond % 1000
  2322. return res.replace(nanosecond=ns)
  2323. def utc_offset(self) -> timedelta | None:
  2324. """
  2325. Get the date times utc offset.
  2326. See :meth:`.Time.utc_offset`.
  2327. """
  2328. return self.__time._utc_offset(self)
  2329. def dst(self) -> timedelta | None:
  2330. """
  2331. Get the daylight saving time adjustment (DST).
  2332. See :meth:`.Time.dst`.
  2333. """
  2334. return _dst(self.tzinfo, self)
  2335. def tzname(self) -> str | None:
  2336. """
  2337. Get the timezone name.
  2338. See :meth:`.Time.tzname`.
  2339. """
  2340. return _tz_name(self.tzinfo, self)
  2341. def time_tuple(self):
  2342. raise NotImplementedError
  2343. def utc_time_tuple(self):
  2344. raise NotImplementedError
  2345. def to_ordinal(self) -> int:
  2346. """
  2347. Get the ordinal of the :class:`.DateTime`'s date.
  2348. See :meth:`.Date.to_ordinal`
  2349. """
  2350. return self.__date.to_ordinal()
  2351. def to_clock_time(self) -> ClockTime:
  2352. """Convert to :class:`.ClockTime`."""
  2353. ordinal_seconds = 86400 * (self.__date.to_ordinal() - 1)
  2354. time_seconds, nanoseconds = divmod(self.__time.ticks, NANO_SECONDS)
  2355. return ClockTime(ordinal_seconds + time_seconds, nanoseconds)
  2356. def to_native(self) -> datetime:
  2357. """
  2358. Convert to a native Python :class:`datetime.datetime` value.
  2359. This conversion is lossy as the native time implementation only
  2360. supports a resolution of microseconds instead of nanoseconds.
  2361. """
  2362. y, mo, d = self.year_month_day
  2363. h, m, s, ns = self.hour_minute_second_nanosecond
  2364. ms = int(ns / 1000)
  2365. tz = self.tzinfo
  2366. return datetime(y, mo, d, h, m, s, ms, tz)
  2367. def weekday(self) -> int:
  2368. """
  2369. Get the weekday.
  2370. See :meth:`.Date.weekday`
  2371. """
  2372. return self.__date.weekday()
  2373. def iso_weekday(self) -> int:
  2374. """
  2375. Get the ISO weekday.
  2376. See :meth:`.Date.iso_weekday`
  2377. """
  2378. return self.__date.iso_weekday()
  2379. def iso_calendar(self) -> tuple[int, int, int]:
  2380. """
  2381. Get date as ISO tuple.
  2382. See :meth:`.Date.iso_calendar`
  2383. """
  2384. return self.__date.iso_calendar()
  2385. def iso_format(self, sep: str = "T") -> str:
  2386. """
  2387. Return the :class:`.DateTime` as ISO formatted string.
  2388. This method joins `self.date().iso_format()` (see
  2389. :meth:`.Date.iso_format`) and `self.timetz().iso_format()` (see
  2390. :meth:`.Time.iso_format`) with `sep` in between.
  2391. :param sep: the separator between the formatted date and time.
  2392. """
  2393. s_date = self.date().iso_format()
  2394. s_time = self.timetz().iso_format()
  2395. s = f"{s_date}{sep}{s_time}"
  2396. time_tz = self.timetz()
  2397. offset = time_tz.utc_offset()
  2398. if offset is not None:
  2399. # the time component will have taken care of formatting the offset
  2400. return s
  2401. offset = self.utc_offset()
  2402. if offset is not None:
  2403. offset_hours, offset_minutes = divmod(
  2404. int(offset.total_seconds() // 60), 60
  2405. )
  2406. s += f"{offset_hours:+03d}:{offset_minutes:02d}"
  2407. return s
  2408. def __repr__(self) -> str:
  2409. fields: list[str] = [
  2410. *map(repr, self.year_month_day),
  2411. *map(repr, self.hour_minute_second_nanosecond),
  2412. ]
  2413. if self.tzinfo is not None:
  2414. fields.append(f"tzinfo={self.tzinfo!r}")
  2415. return f"neo4j.time.DateTime({', '.join(fields)})"
  2416. def __str__(self) -> str:
  2417. return self.iso_format()
  2418. def __format__(self, format_spec):
  2419. if not format_spec:
  2420. return self.iso_format()
  2421. format_spec = FORMAT_F_REPLACE.sub(
  2422. f"{self.__time.nanosecond:09}", format_spec
  2423. )
  2424. return self.to_native().__format__(format_spec)
  2425. # INSTANCE METHOD ALIASES #
  2426. def __getattr__(self, name):
  2427. """
  2428. Get attribute by name.
  2429. Map standard library attribute names to local attribute names,
  2430. for compatibility.
  2431. """
  2432. try:
  2433. return {
  2434. "astimezone": self.as_timezone,
  2435. "isocalendar": self.iso_calendar,
  2436. "isoformat": self.iso_format,
  2437. "isoweekday": self.iso_weekday,
  2438. "strftime": self.__format__,
  2439. "toordinal": self.to_ordinal,
  2440. "timetuple": self.time_tuple,
  2441. "utcoffset": self.utc_offset,
  2442. "utctimetuple": self.utc_time_tuple,
  2443. }[name]
  2444. except KeyError:
  2445. raise AttributeError(
  2446. f"DateTime has no attribute {name!r}"
  2447. ) from None
  2448. if t.TYPE_CHECKING:
  2449. def astimezone( # type: ignore[override]
  2450. self, tz: _tzinfo
  2451. ) -> DateTime: ...
  2452. def isocalendar( # type: ignore[override]
  2453. self,
  2454. ) -> tuple[int, int, int]: ...
  2455. def iso_format(self, sep: str = "T") -> str: # type: ignore[override]
  2456. ...
  2457. isoweekday = iso_weekday
  2458. strftime = __format__
  2459. toordinal = to_ordinal
  2460. timetuple = time_tuple
  2461. utcoffset = utc_offset
  2462. utctimetuple = utc_time_tuple
  2463. DateTime.min = DateTime.combine(Date.min, Time.min) # type: ignore
  2464. DateTime.max = DateTime.combine(Date.max, Time.max) # type: ignore
  2465. DateTime.resolution = Time.resolution # type: ignore
  2466. #: A :class:`.DateTime` instance set to `0000-00-00T00:00:00`.
  2467. #: This has a :class:`.Date` component equal to :attr:`ZeroDate` and a
  2468. Never = DateTime.combine(ZeroDate, Midnight)
  2469. #: A :class:`.DateTime` instance set to `1970-01-01T00:00:00`.
  2470. UnixEpoch = DateTime(1970, 1, 1, 0, 0, 0)