arrow.py 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903
  1. """
  2. Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime``
  3. replacement.
  4. """
  5. import calendar
  6. import re
  7. import sys
  8. from datetime import date
  9. from datetime import datetime as dt_datetime
  10. from datetime import time as dt_time
  11. from datetime import timedelta, timezone
  12. from datetime import tzinfo as dt_tzinfo
  13. from math import trunc
  14. from time import struct_time
  15. from typing import (
  16. Any,
  17. ClassVar,
  18. Final,
  19. Generator,
  20. Iterable,
  21. List,
  22. Literal,
  23. Mapping,
  24. Optional,
  25. Tuple,
  26. Union,
  27. cast,
  28. overload,
  29. )
  30. from dateutil import tz as dateutil_tz
  31. from dateutil.relativedelta import relativedelta
  32. from arrow import formatter, locales, parser, util
  33. from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES
  34. from arrow.locales import TimeFrameLiteral
  35. TZ_EXPR = Union[dt_tzinfo, str]
  36. _T_FRAMES = Literal[
  37. "year",
  38. "years",
  39. "month",
  40. "months",
  41. "day",
  42. "days",
  43. "hour",
  44. "hours",
  45. "minute",
  46. "minutes",
  47. "second",
  48. "seconds",
  49. "microsecond",
  50. "microseconds",
  51. "week",
  52. "weeks",
  53. "quarter",
  54. "quarters",
  55. ]
  56. _BOUNDS = Literal["[)", "()", "(]", "[]"]
  57. _GRANULARITY = Literal[
  58. "auto",
  59. "second",
  60. "minute",
  61. "hour",
  62. "day",
  63. "week",
  64. "month",
  65. "quarter",
  66. "year",
  67. ]
  68. class Arrow:
  69. """An :class:`Arrow <arrow.arrow.Arrow>` object.
  70. Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing
  71. additional functionality.
  72. :param year: the calendar year.
  73. :param month: the calendar month.
  74. :param day: the calendar day.
  75. :param hour: (optional) the hour. Defaults to 0.
  76. :param minute: (optional) the minute, Defaults to 0.
  77. :param second: (optional) the second, Defaults to 0.
  78. :param microsecond: (optional) the microsecond. Defaults to 0.
  79. :param tzinfo: (optional) A timezone expression. Defaults to UTC.
  80. :param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0.
  81. .. _tz-expr:
  82. Recognized timezone expressions:
  83. - A ``tzinfo`` object.
  84. - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
  85. - A ``str`` in ISO 8601 style, as in '+07:00'.
  86. - A ``str``, one of the following: 'local', 'utc', 'UTC'.
  87. Usage::
  88. >>> import arrow
  89. >>> arrow.Arrow(2013, 5, 5, 12, 30, 45)
  90. <Arrow [2013-05-05T12:30:45+00:00]>
  91. """
  92. resolution: ClassVar[timedelta] = dt_datetime.resolution
  93. min: ClassVar["Arrow"]
  94. max: ClassVar["Arrow"]
  95. _ATTRS: Final[List[str]] = [
  96. "year",
  97. "month",
  98. "day",
  99. "hour",
  100. "minute",
  101. "second",
  102. "microsecond",
  103. ]
  104. _ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS]
  105. _MONTHS_PER_QUARTER: Final[int] = 3
  106. _MONTHS_PER_YEAR: Final[int] = 12
  107. _SECS_PER_MINUTE: Final[int] = 60
  108. _SECS_PER_HOUR: Final[int] = 60 * 60
  109. _SECS_PER_DAY: Final[int] = 60 * 60 * 24
  110. _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7
  111. _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5
  112. _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3
  113. _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365
  114. _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = {
  115. "second": 1.0,
  116. "minute": _SECS_PER_MINUTE,
  117. "hour": _SECS_PER_HOUR,
  118. "day": _SECS_PER_DAY,
  119. "week": _SECS_PER_WEEK,
  120. "month": _SECS_PER_MONTH,
  121. "quarter": _SECS_PER_QUARTER,
  122. "year": _SECS_PER_YEAR,
  123. }
  124. _datetime: dt_datetime
  125. def __init__(
  126. self,
  127. year: int,
  128. month: int,
  129. day: int,
  130. hour: int = 0,
  131. minute: int = 0,
  132. second: int = 0,
  133. microsecond: int = 0,
  134. tzinfo: Optional[TZ_EXPR] = None,
  135. **kwargs: Any,
  136. ) -> None:
  137. if tzinfo is None:
  138. tzinfo = timezone.utc
  139. # detect that tzinfo is a pytz object (issue #626)
  140. elif (
  141. isinstance(tzinfo, dt_tzinfo)
  142. and hasattr(tzinfo, "localize")
  143. and hasattr(tzinfo, "zone")
  144. and tzinfo.zone
  145. ):
  146. tzinfo = parser.TzinfoParser.parse(tzinfo.zone)
  147. elif isinstance(tzinfo, str):
  148. tzinfo = parser.TzinfoParser.parse(tzinfo)
  149. fold = kwargs.get("fold", 0)
  150. self._datetime = dt_datetime(
  151. year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold
  152. )
  153. # factories: single object, both original and from datetime.
  154. @classmethod
  155. def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow":
  156. """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given
  157. timezone.
  158. :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
  159. Usage::
  160. >>> arrow.now('Asia/Baku')
  161. <Arrow [2019-01-24T20:26:31.146412+04:00]>
  162. """
  163. if tzinfo is None:
  164. tzinfo = dt_datetime.now().astimezone().tzinfo
  165. dt = dt_datetime.now(tzinfo)
  166. return cls(
  167. dt.year,
  168. dt.month,
  169. dt.day,
  170. dt.hour,
  171. dt.minute,
  172. dt.second,
  173. dt.microsecond,
  174. dt.tzinfo,
  175. fold=getattr(dt, "fold", 0),
  176. )
  177. @classmethod
  178. def utcnow(cls) -> "Arrow":
  179. """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC
  180. time.
  181. Usage::
  182. >>> arrow.utcnow()
  183. <Arrow [2019-01-24T16:31:40.651108+00:00]>
  184. """
  185. dt = dt_datetime.now(timezone.utc)
  186. return cls(
  187. dt.year,
  188. dt.month,
  189. dt.day,
  190. dt.hour,
  191. dt.minute,
  192. dt.second,
  193. dt.microsecond,
  194. dt.tzinfo,
  195. fold=getattr(dt, "fold", 0),
  196. )
  197. @classmethod
  198. def fromtimestamp(
  199. cls,
  200. timestamp: Union[int, float, str],
  201. tzinfo: Optional[TZ_EXPR] = None,
  202. ) -> "Arrow":
  203. """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, converted to
  204. the given timezone.
  205. :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
  206. :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
  207. """
  208. if tzinfo is None:
  209. tzinfo = dt_datetime.now().astimezone().tzinfo
  210. elif isinstance(tzinfo, str):
  211. tzinfo = parser.TzinfoParser.parse(tzinfo)
  212. if not util.is_timestamp(timestamp):
  213. raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")
  214. timestamp = util.normalize_timestamp(float(timestamp))
  215. dt = dt_datetime.fromtimestamp(timestamp, tzinfo)
  216. return cls(
  217. dt.year,
  218. dt.month,
  219. dt.day,
  220. dt.hour,
  221. dt.minute,
  222. dt.second,
  223. dt.microsecond,
  224. dt.tzinfo,
  225. fold=getattr(dt, "fold", 0),
  226. )
  227. @classmethod
  228. def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow":
  229. """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, in UTC time.
  230. :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
  231. """
  232. if not util.is_timestamp(timestamp):
  233. raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")
  234. timestamp = util.normalize_timestamp(float(timestamp))
  235. dt = dt_datetime.fromtimestamp(timestamp, timezone.utc)
  236. return cls(
  237. dt.year,
  238. dt.month,
  239. dt.day,
  240. dt.hour,
  241. dt.minute,
  242. dt.second,
  243. dt.microsecond,
  244. timezone.utc,
  245. fold=getattr(dt, "fold", 0),
  246. )
  247. @classmethod
  248. def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
  249. """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and
  250. optional replacement timezone.
  251. :param dt: the ``datetime``
  252. :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to ``dt``'s
  253. timezone, or UTC if naive.
  254. Usage::
  255. >>> dt
  256. datetime.datetime(2021, 4, 7, 13, 48, tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific'))
  257. >>> arrow.Arrow.fromdatetime(dt)
  258. <Arrow [2021-04-07T13:48:00-07:00]>
  259. """
  260. if tzinfo is None:
  261. if dt.tzinfo is None:
  262. tzinfo = timezone.utc
  263. else:
  264. tzinfo = dt.tzinfo
  265. return cls(
  266. dt.year,
  267. dt.month,
  268. dt.day,
  269. dt.hour,
  270. dt.minute,
  271. dt.second,
  272. dt.microsecond,
  273. tzinfo,
  274. fold=getattr(dt, "fold", 0),
  275. )
  276. @classmethod
  277. def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
  278. """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional
  279. replacement timezone. All time values are set to 0.
  280. :param date: the ``date``
  281. :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to UTC.
  282. """
  283. if tzinfo is None:
  284. tzinfo = timezone.utc
  285. return cls(date.year, date.month, date.day, tzinfo=tzinfo)
  286. @classmethod
  287. def strptime(
  288. cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None
  289. ) -> "Arrow":
  290. """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a date string and format,
  291. in the style of ``datetime.strptime``. Optionally replaces the parsed timezone.
  292. :param date_str: the date string.
  293. :param fmt: the format string using datetime format codes.
  294. :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to the parsed
  295. timezone if ``fmt`` contains a timezone directive, otherwise UTC.
  296. Usage::
  297. >>> arrow.Arrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S')
  298. <Arrow [2019-01-20T15:49:10+00:00]>
  299. """
  300. dt = dt_datetime.strptime(date_str, fmt)
  301. if tzinfo is None:
  302. tzinfo = dt.tzinfo
  303. return cls(
  304. dt.year,
  305. dt.month,
  306. dt.day,
  307. dt.hour,
  308. dt.minute,
  309. dt.second,
  310. dt.microsecond,
  311. tzinfo,
  312. fold=getattr(dt, "fold", 0),
  313. )
  314. @classmethod
  315. def fromordinal(cls, ordinal: int) -> "Arrow":
  316. """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object corresponding
  317. to the Gregorian Ordinal.
  318. :param ordinal: an ``int`` corresponding to a Gregorian Ordinal.
  319. Usage::
  320. >>> arrow.fromordinal(737741)
  321. <Arrow [2020-11-12T00:00:00+00:00]>
  322. """
  323. util.validate_ordinal(ordinal)
  324. dt = dt_datetime.fromordinal(ordinal)
  325. return cls(
  326. dt.year,
  327. dt.month,
  328. dt.day,
  329. dt.hour,
  330. dt.minute,
  331. dt.second,
  332. dt.microsecond,
  333. dt.tzinfo,
  334. fold=getattr(dt, "fold", 0),
  335. )
  336. # factories: ranges and spans
  337. @classmethod
  338. def range(
  339. cls,
  340. frame: _T_FRAMES,
  341. start: Union["Arrow", dt_datetime],
  342. end: Union["Arrow", dt_datetime, None] = None,
  343. tz: Optional[TZ_EXPR] = None,
  344. limit: Optional[int] = None,
  345. ) -> Generator["Arrow", None, None]:
  346. """Returns an iterator of :class:`Arrow <arrow.arrow.Arrow>` objects, representing
  347. points in time between two inputs.
  348. :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
  349. :param start: A datetime expression, the start of the range.
  350. :param end: (optional) A datetime expression, the end of the range.
  351. :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to
  352. ``start``'s timezone, or UTC if ``start`` is naive.
  353. :param limit: (optional) A maximum number of tuples to return.
  354. **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to
  355. return the entire range. Call with ``limit`` alone to return a maximum # of results from
  356. the start. Call with both to cap a range at a maximum # of results.
  357. **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
  358. iterating. As such, either call with naive objects and ``tz``, or aware objects from the
  359. same timezone and no ``tz``.
  360. Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.
  361. Recognized datetime expressions:
  362. - An :class:`Arrow <arrow.arrow.Arrow>` object.
  363. - A ``datetime`` object.
  364. Usage::
  365. >>> start = datetime(2013, 5, 5, 12, 30)
  366. >>> end = datetime(2013, 5, 5, 17, 15)
  367. >>> for r in arrow.Arrow.range('hour', start, end):
  368. ... print(repr(r))
  369. ...
  370. <Arrow [2013-05-05T12:30:00+00:00]>
  371. <Arrow [2013-05-05T13:30:00+00:00]>
  372. <Arrow [2013-05-05T14:30:00+00:00]>
  373. <Arrow [2013-05-05T15:30:00+00:00]>
  374. <Arrow [2013-05-05T16:30:00+00:00]>
  375. **NOTE**: Unlike Python's ``range``, ``end`` *may* be included in the returned iterator::
  376. >>> start = datetime(2013, 5, 5, 12, 30)
  377. >>> end = datetime(2013, 5, 5, 13, 30)
  378. >>> for r in arrow.Arrow.range('hour', start, end):
  379. ... print(repr(r))
  380. ...
  381. <Arrow [2013-05-05T12:30:00+00:00]>
  382. <Arrow [2013-05-05T13:30:00+00:00]>
  383. """
  384. _, frame_relative, relative_steps = cls._get_frames(frame)
  385. tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
  386. start = cls._get_datetime(start).replace(tzinfo=tzinfo)
  387. end, limit = cls._get_iteration_params(end, limit)
  388. end = cls._get_datetime(end).replace(tzinfo=tzinfo)
  389. current = cls.fromdatetime(start)
  390. original_day = start.day
  391. day_is_clipped = False
  392. i = 0
  393. while current <= end and i < limit:
  394. i += 1
  395. yield current
  396. values = [getattr(current, f) for f in cls._ATTRS]
  397. current = cls(*values, tzinfo=tzinfo).shift( # type: ignore[misc]
  398. check_imaginary=True, **{frame_relative: relative_steps}
  399. )
  400. if frame in ["month", "quarter", "year"] and current.day < original_day:
  401. day_is_clipped = True
  402. if day_is_clipped and not cls._is_last_day_of_month(current):
  403. current = current.replace(day=original_day)
  404. def span(
  405. self,
  406. frame: _T_FRAMES,
  407. count: int = 1,
  408. bounds: _BOUNDS = "[)",
  409. exact: bool = False,
  410. week_start: int = 1,
  411. ) -> Tuple["Arrow", "Arrow"]:
  412. """Returns a tuple of two new :class:`Arrow <arrow.arrow.Arrow>` objects, representing the timespan
  413. of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
  414. :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
  415. :param count: (optional) the number of frames to span.
  416. :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
  417. whether to include or exclude the start and end values in the span. '(' excludes
  418. the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
  419. If the bounds are not specified, the default bound '[)' is used.
  420. :param exact: (optional) whether to have the start of the timespan begin exactly
  421. at the time specified by ``start`` and the end of the timespan truncated
  422. so as not to extend beyond ``end``.
  423. :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
  424. Monday is 1 and Sunday is 7.
  425. Supported frame values: year, quarter, month, week, day, hour, minute, second.
  426. Usage::
  427. >>> arrow.utcnow()
  428. <Arrow [2013-05-09T03:32:36.186203+00:00]>
  429. >>> arrow.utcnow().span('hour')
  430. (<Arrow [2013-05-09T03:00:00+00:00]>, <Arrow [2013-05-09T03:59:59.999999+00:00]>)
  431. >>> arrow.utcnow().span('day')
  432. (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-09T23:59:59.999999+00:00]>)
  433. >>> arrow.utcnow().span('day', count=2)
  434. (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T23:59:59.999999+00:00]>)
  435. >>> arrow.utcnow().span('day', bounds='[]')
  436. (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T00:00:00+00:00]>)
  437. >>> arrow.utcnow().span('week')
  438. (<Arrow [2021-02-22T00:00:00+00:00]>, <Arrow [2021-02-28T23:59:59.999999+00:00]>)
  439. >>> arrow.utcnow().span('week', week_start=6)
  440. (<Arrow [2021-02-20T00:00:00+00:00]>, <Arrow [2021-02-26T23:59:59.999999+00:00]>)
  441. """
  442. util.validate_bounds(bounds)
  443. frame_absolute, frame_relative, relative_steps = self._get_frames(frame)
  444. if frame_absolute == "week":
  445. if not 1 <= week_start <= 7:
  446. raise ValueError("week_start argument must be between 1 and 7.")
  447. attr = "day"
  448. elif frame_absolute == "quarter":
  449. attr = "month"
  450. else:
  451. attr = frame_absolute
  452. floor = self
  453. if not exact:
  454. index = self._ATTRS.index(attr)
  455. frames = self._ATTRS[: index + 1]
  456. values = [getattr(self, f) for f in frames]
  457. for _ in range(3 - len(values)):
  458. values.append(1)
  459. floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc]
  460. if frame_absolute == "week":
  461. # if week_start is greater than self.isoweekday() go back one week by setting delta = 7
  462. delta = 7 if week_start > self.isoweekday() else 0
  463. floor = floor.shift(days=-(self.isoweekday() - week_start) - delta)
  464. elif frame_absolute == "quarter":
  465. floor = floor.shift(months=-((self.month - 1) % 3))
  466. ceil = floor.shift(
  467. check_imaginary=True, **{frame_relative: count * relative_steps}
  468. )
  469. if bounds[0] == "(":
  470. floor = floor.shift(microseconds=+1)
  471. if bounds[1] == ")":
  472. ceil = ceil.shift(microseconds=-1)
  473. return floor, ceil
  474. def floor(self, frame: _T_FRAMES, **kwargs: Any) -> "Arrow":
  475. """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "floor"
  476. of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
  477. Equivalent to the first element in the 2-tuple returned by
  478. :func:`span <arrow.arrow.Arrow.span>`.
  479. :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
  480. :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
  481. Monday is 1 and Sunday is 7.
  482. Usage::
  483. >>> arrow.utcnow().floor('hour')
  484. <Arrow [2013-05-09T03:00:00+00:00]>
  485. >>> arrow.utcnow().floor('week', week_start=7)
  486. <Arrow [2021-02-21T00:00:00+00:00]>
  487. """
  488. return self.span(frame, **kwargs)[0]
  489. def ceil(self, frame: _T_FRAMES, **kwargs: Any) -> "Arrow":
  490. """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "ceiling"
  491. of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
  492. Equivalent to the second element in the 2-tuple returned by
  493. :func:`span <arrow.arrow.Arrow.span>`.
  494. :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
  495. :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
  496. Monday is 1 and Sunday is 7.
  497. Usage::
  498. >>> arrow.utcnow().ceil('hour')
  499. <Arrow [2013-05-09T03:59:59.999999+00:00]>
  500. >>> arrow.utcnow().ceil('week', week_start=7)
  501. <Arrow [2021-02-27T23:59:59.999999+00:00]>
  502. """
  503. return self.span(frame, **kwargs)[1]
  504. @classmethod
  505. def span_range(
  506. cls,
  507. frame: _T_FRAMES,
  508. start: dt_datetime,
  509. end: dt_datetime,
  510. tz: Optional[TZ_EXPR] = None,
  511. limit: Optional[int] = None,
  512. bounds: _BOUNDS = "[)",
  513. exact: bool = False,
  514. ) -> Iterable[Tuple["Arrow", "Arrow"]]:
  515. """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
  516. representing a series of timespans between two inputs.
  517. :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
  518. :param start: A datetime expression, the start of the range.
  519. :param end: (optional) A datetime expression, the end of the range.
  520. :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to
  521. ``start``'s timezone, or UTC if ``start`` is naive.
  522. :param limit: (optional) A maximum number of tuples to return.
  523. :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
  524. whether to include or exclude the start and end values in each span in the range. '(' excludes
  525. the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
  526. If the bounds are not specified, the default bound '[)' is used.
  527. :param exact: (optional) whether to have the first timespan start exactly
  528. at the time specified by ``start`` and the final span truncated
  529. so as not to extend beyond ``end``.
  530. **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to
  531. return the entire range. Call with ``limit`` alone to return a maximum # of results from
  532. the start. Call with both to cap a range at a maximum # of results.
  533. **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
  534. iterating. As such, either call with naive objects and ``tz``, or aware objects from the
  535. same timezone and no ``tz``.
  536. Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.
  537. Recognized datetime expressions:
  538. - An :class:`Arrow <arrow.arrow.Arrow>` object.
  539. - A ``datetime`` object.
  540. **NOTE**: Unlike Python's ``range``, ``end`` will *always* be included in the returned
  541. iterator of timespans.
  542. Usage:
  543. >>> start = datetime(2013, 5, 5, 12, 30)
  544. >>> end = datetime(2013, 5, 5, 17, 15)
  545. >>> for r in arrow.Arrow.span_range('hour', start, end):
  546. ... print(r)
  547. ...
  548. (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>)
  549. (<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
  550. (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>)
  551. (<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
  552. (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>)
  553. (<Arrow [2013-05-05T17:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:00]>)
  554. """
  555. tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
  556. start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0]
  557. end = cls.fromdatetime(end, tzinfo)
  558. _range = cls.range(frame, start, end, tz, limit)
  559. if not exact:
  560. for r in _range:
  561. yield r.span(frame, bounds=bounds, exact=exact)
  562. for r in _range:
  563. floor, ceil = r.span(frame, bounds=bounds, exact=exact)
  564. if ceil > end:
  565. ceil = end
  566. if bounds[1] == ")":
  567. ceil += relativedelta(microseconds=-1)
  568. if floor == end:
  569. break
  570. elif floor + relativedelta(microseconds=-1) == end:
  571. break
  572. yield floor, ceil
  573. @classmethod
  574. def interval(
  575. cls,
  576. frame: _T_FRAMES,
  577. start: dt_datetime,
  578. end: dt_datetime,
  579. interval: int = 1,
  580. tz: Optional[TZ_EXPR] = None,
  581. bounds: _BOUNDS = "[)",
  582. exact: bool = False,
  583. ) -> Iterable[Tuple["Arrow", "Arrow"]]:
  584. """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
  585. representing a series of intervals between two inputs.
  586. :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
  587. :param start: A datetime expression, the start of the range.
  588. :param end: (optional) A datetime expression, the end of the range.
  589. :param interval: (optional) Time interval for the given time frame.
  590. :param tz: (optional) A timezone expression. Defaults to UTC.
  591. :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
  592. whether to include or exclude the start and end values in the intervals. '(' excludes
  593. the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
  594. If the bounds are not specified, the default bound '[)' is used.
  595. :param exact: (optional) whether to have the first timespan start exactly
  596. at the time specified by ``start`` and the final interval truncated
  597. so as not to extend beyond ``end``.
  598. Supported frame values: year, quarter, month, week, day, hour, minute, second
  599. Recognized datetime expressions:
  600. - An :class:`Arrow <arrow.arrow.Arrow>` object.
  601. - A ``datetime`` object.
  602. Recognized timezone expressions:
  603. - A ``tzinfo`` object.
  604. - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
  605. - A ``str`` in ISO 8601 style, as in '+07:00'.
  606. - A ``str``, one of the following: 'local', 'utc', 'UTC'.
  607. Usage:
  608. >>> start = datetime(2013, 5, 5, 12, 30)
  609. >>> end = datetime(2013, 5, 5, 17, 15)
  610. >>> for r in arrow.Arrow.interval('hour', start, end, 2):
  611. ... print(r)
  612. ...
  613. (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
  614. (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
  615. (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:0]>)
  616. """
  617. if interval < 1:
  618. raise ValueError("interval has to be a positive integer")
  619. spanRange = iter(
  620. cls.span_range(frame, start, end, tz, bounds=bounds, exact=exact)
  621. )
  622. while True:
  623. try:
  624. intvlStart, intvlEnd = next(spanRange)
  625. for _ in range(interval - 1):
  626. try:
  627. _, intvlEnd = next(spanRange)
  628. except StopIteration:
  629. continue
  630. yield intvlStart, intvlEnd
  631. except StopIteration:
  632. return
  633. # representations
  634. def __repr__(self) -> str:
  635. return f"<{self.__class__.__name__} [{self.__str__()}]>"
  636. def __str__(self) -> str:
  637. return self._datetime.isoformat()
  638. def __format__(self, formatstr: str) -> str:
  639. if len(formatstr) > 0:
  640. return self.format(formatstr)
  641. return str(self)
  642. def __hash__(self) -> int:
  643. return self._datetime.__hash__()
  644. # attributes and properties
  645. def __getattr__(self, name: str) -> Any:
  646. if name == "week":
  647. return self.isocalendar()[1]
  648. if name == "quarter":
  649. return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1
  650. if not name.startswith("_"):
  651. value: Optional[Any] = getattr(self._datetime, name, None)
  652. if value is not None:
  653. return value
  654. return cast(int, object.__getattribute__(self, name))
  655. @property
  656. def tzinfo(self) -> dt_tzinfo:
  657. """Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object.
  658. Usage::
  659. >>> arw=arrow.utcnow()
  660. >>> arw.tzinfo
  661. tzutc()
  662. """
  663. # In Arrow, `_datetime` cannot be naive.
  664. return cast(dt_tzinfo, self._datetime.tzinfo)
  665. @property
  666. def datetime(self) -> dt_datetime:
  667. """Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object.
  668. Usage::
  669. >>> arw=arrow.utcnow()
  670. >>> arw.datetime
  671. datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc())
  672. """
  673. return self._datetime
  674. @property
  675. def naive(self) -> dt_datetime:
  676. """Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>`
  677. object.
  678. Usage::
  679. >>> nairobi = arrow.now('Africa/Nairobi')
  680. >>> nairobi
  681. <Arrow [2019-01-23T19:27:12.297999+03:00]>
  682. >>> nairobi.naive
  683. datetime.datetime(2019, 1, 23, 19, 27, 12, 297999)
  684. """
  685. return self._datetime.replace(tzinfo=None)
  686. def timestamp(self) -> float:
  687. """Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
  688. UTC time.
  689. Usage::
  690. >>> arrow.utcnow().timestamp()
  691. 1616882340.256501
  692. """
  693. return self._datetime.timestamp()
  694. @property
  695. def int_timestamp(self) -> int:
  696. """Returns an integer timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
  697. UTC time.
  698. Usage::
  699. >>> arrow.utcnow().int_timestamp
  700. 1548260567
  701. """
  702. return int(self.timestamp())
  703. @property
  704. def float_timestamp(self) -> float:
  705. """Returns a floating-point timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>`
  706. object, in UTC time.
  707. Usage::
  708. >>> arrow.utcnow().float_timestamp
  709. 1548260516.830896
  710. """
  711. return self.timestamp()
  712. @property
  713. def fold(self) -> int:
  714. """Returns the ``fold`` value of the :class:`Arrow <arrow.arrow.Arrow>` object."""
  715. return self._datetime.fold
  716. @property
  717. def ambiguous(self) -> bool:
  718. """Indicates whether the :class:`Arrow <arrow.arrow.Arrow>` object is a repeated wall time in the current
  719. timezone.
  720. """
  721. return dateutil_tz.datetime_ambiguous(self._datetime)
  722. @property
  723. def imaginary(self) -> bool:
  724. """Indicates whether the :class: `Arrow <arrow.arrow.Arrow>` object exists in the current timezone."""
  725. return not dateutil_tz.datetime_exists(self._datetime)
  726. # mutation and duplication.
  727. def clone(self) -> "Arrow":
  728. """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, cloned from the current one.
  729. Usage:
  730. >>> arw = arrow.utcnow()
  731. >>> cloned = arw.clone()
  732. """
  733. return self.fromdatetime(self._datetime)
  734. def replace(self, **kwargs: Any) -> "Arrow":
  735. """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
  736. according to inputs.
  737. Use property names to set their value absolutely::
  738. >>> import arrow
  739. >>> arw = arrow.utcnow()
  740. >>> arw
  741. <Arrow [2013-05-11T22:27:34.787885+00:00]>
  742. >>> arw.replace(year=2014, month=6)
  743. <Arrow [2014-06-11T22:27:34.787885+00:00]>
  744. You can also replace the timezone without conversion, using a
  745. :ref:`timezone expression <tz-expr>`::
  746. >>> arw.replace(tzinfo=tz.tzlocal())
  747. <Arrow [2013-05-11T22:27:34.787885-07:00]>
  748. """
  749. absolute_kwargs = {}
  750. for key, value in kwargs.items():
  751. if key in self._ATTRS:
  752. absolute_kwargs[key] = value
  753. elif key in ["week", "quarter"]:
  754. raise ValueError(f"Setting absolute {key} is not supported.")
  755. elif key not in ["tzinfo", "fold"]:
  756. raise ValueError(f"Unknown attribute: {key!r}.")
  757. current = self._datetime.replace(**absolute_kwargs)
  758. tzinfo = kwargs.get("tzinfo")
  759. if tzinfo is not None:
  760. tzinfo = self._get_tzinfo(tzinfo)
  761. current = current.replace(tzinfo=tzinfo)
  762. fold = kwargs.get("fold")
  763. if fold is not None:
  764. current = current.replace(fold=fold)
  765. return self.fromdatetime(current)
  766. def shift(self, check_imaginary: bool = True, **kwargs: Any) -> "Arrow":
  767. """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
  768. according to inputs.
  769. Parameters:
  770. check_imaginary (bool): If True (default), will check for and resolve
  771. imaginary times (like during DST transitions). If False, skips this check.
  772. Use pluralized property names to relatively shift their current value:
  773. >>> import arrow
  774. >>> arw = arrow.utcnow()
  775. >>> arw
  776. <Arrow [2013-05-11T22:27:34.787885+00:00]>
  777. >>> arw.shift(years=1, months=-1)
  778. <Arrow [2014-04-11T22:27:34.787885+00:00]>
  779. Day-of-the-week relative shifting can use either Python's weekday numbers
  780. (Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's
  781. day instances (MO, TU .. SU). When using weekday numbers, the returned
  782. date will always be greater than or equal to the starting date.
  783. Using the above code (which is a Saturday) and asking it to shift to Saturday:
  784. >>> arw.shift(weekday=5)
  785. <Arrow [2013-05-11T22:27:34.787885+00:00]>
  786. While asking for a Monday:
  787. >>> arw.shift(weekday=0)
  788. <Arrow [2013-05-13T22:27:34.787885+00:00]>
  789. """
  790. relative_kwargs = {}
  791. additional_attrs = ["weeks", "quarters", "weekday"]
  792. for key, value in kwargs.items():
  793. if key in self._ATTRS_PLURAL or key in additional_attrs:
  794. relative_kwargs[key] = value
  795. else:
  796. supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs)
  797. raise ValueError(
  798. f"Invalid shift time frame. Please select one of the following: {supported_attr}."
  799. )
  800. # core datetime does not support quarters, translate to months.
  801. relative_kwargs.setdefault("months", 0)
  802. relative_kwargs["months"] += (
  803. relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER
  804. )
  805. current = self._datetime + relativedelta(**relative_kwargs)
  806. # If check_imaginary is True, perform the check for imaginary times (DST transitions)
  807. if check_imaginary and not dateutil_tz.datetime_exists(current):
  808. current = dateutil_tz.resolve_imaginary(current)
  809. return self.fromdatetime(current)
  810. def to(self, tz: TZ_EXPR) -> "Arrow":
  811. """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted
  812. to the target timezone.
  813. :param tz: A :ref:`timezone expression <tz-expr>`.
  814. Usage::
  815. >>> utc = arrow.utcnow()
  816. >>> utc
  817. <Arrow [2013-05-09T03:49:12.311072+00:00]>
  818. >>> utc.to('US/Pacific')
  819. <Arrow [2013-05-08T20:49:12.311072-07:00]>
  820. >>> utc.to(tz.tzlocal())
  821. <Arrow [2013-05-08T20:49:12.311072-07:00]>
  822. >>> utc.to('-07:00')
  823. <Arrow [2013-05-08T20:49:12.311072-07:00]>
  824. >>> utc.to('local')
  825. <Arrow [2013-05-08T20:49:12.311072-07:00]>
  826. >>> utc.to('local').to('utc')
  827. <Arrow [2013-05-09T03:49:12.311072+00:00]>
  828. """
  829. if not isinstance(tz, dt_tzinfo):
  830. tz = parser.TzinfoParser.parse(tz)
  831. dt = self._datetime.astimezone(tz)
  832. return self.__class__(
  833. dt.year,
  834. dt.month,
  835. dt.day,
  836. dt.hour,
  837. dt.minute,
  838. dt.second,
  839. dt.microsecond,
  840. dt.tzinfo,
  841. fold=getattr(dt, "fold", 0),
  842. )
  843. # string output and formatting
  844. def format(
  845. self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE
  846. ) -> str:
  847. """Returns a string representation of the :class:`Arrow <arrow.arrow.Arrow>` object,
  848. formatted according to the provided format string. For a list of formatting values,
  849. see :ref:`supported-tokens`
  850. :param fmt: the format string.
  851. :param locale: the locale to format.
  852. Usage::
  853. >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ')
  854. '2013-05-09 03:56:47 -00:00'
  855. >>> arrow.utcnow().format('X')
  856. '1368071882'
  857. >>> arrow.utcnow().format('MMMM DD, YYYY')
  858. 'May 09, 2013'
  859. >>> arrow.utcnow().format()
  860. '2013-05-09 03:56:47 -00:00'
  861. """
  862. return formatter.DateTimeFormatter(locale).format(self._datetime, fmt)
  863. def humanize(
  864. self,
  865. other: Union["Arrow", dt_datetime, None] = None,
  866. locale: str = DEFAULT_LOCALE,
  867. only_distance: bool = False,
  868. granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto",
  869. ) -> str:
  870. """Returns a localized, humanized representation of a relative difference in time.
  871. :param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object.
  872. Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone.
  873. :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'.
  874. :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part.
  875. :param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute',
  876. 'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings
  877. Usage::
  878. >>> earlier = arrow.utcnow().shift(hours=-2)
  879. >>> earlier.humanize()
  880. '2 hours ago'
  881. >>> later = earlier.shift(hours=4)
  882. >>> later.humanize(earlier)
  883. 'in 4 hours'
  884. """
  885. locale_name = locale
  886. locale = locales.get_locale(locale)
  887. if other is None:
  888. utc = dt_datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
  889. dt = utc.astimezone(self._datetime.tzinfo)
  890. elif isinstance(other, Arrow):
  891. dt = other._datetime
  892. elif isinstance(other, dt_datetime):
  893. if other.tzinfo is None:
  894. dt = other.replace(tzinfo=self._datetime.tzinfo)
  895. else:
  896. dt = other.astimezone(self._datetime.tzinfo)
  897. else:
  898. raise TypeError(
  899. f"Invalid 'other' argument of type {type(other).__name__!r}. "
  900. "Argument must be of type None, Arrow, or datetime."
  901. )
  902. if isinstance(granularity, list) and len(granularity) == 1:
  903. granularity = granularity[0]
  904. _delta = int(round((self._datetime - dt).total_seconds()))
  905. sign = -1 if _delta < 0 else 1
  906. delta_second = diff = abs(_delta)
  907. try:
  908. if granularity == "auto":
  909. if diff < 10:
  910. return locale.describe("now", only_distance=only_distance)
  911. if diff < self._SECS_PER_MINUTE:
  912. seconds = sign * delta_second
  913. return locale.describe(
  914. "seconds", seconds, only_distance=only_distance
  915. )
  916. elif diff < self._SECS_PER_MINUTE * 2:
  917. return locale.describe("minute", sign, only_distance=only_distance)
  918. elif diff < self._SECS_PER_HOUR:
  919. minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2)
  920. return locale.describe(
  921. "minutes", minutes, only_distance=only_distance
  922. )
  923. elif diff < self._SECS_PER_HOUR * 2:
  924. return locale.describe("hour", sign, only_distance=only_distance)
  925. elif diff < self._SECS_PER_DAY:
  926. hours = sign * max(delta_second // self._SECS_PER_HOUR, 2)
  927. return locale.describe("hours", hours, only_distance=only_distance)
  928. calendar_diff = (
  929. relativedelta(dt, self._datetime)
  930. if self._datetime < dt
  931. else relativedelta(self._datetime, dt)
  932. )
  933. calendar_months = (
  934. calendar_diff.years * self._MONTHS_PER_YEAR + calendar_diff.months
  935. )
  936. # For months, if more than 2 weeks, count as a full month
  937. if calendar_diff.days > 14:
  938. calendar_months += 1
  939. calendar_months = min(calendar_months, self._MONTHS_PER_YEAR)
  940. if diff < self._SECS_PER_DAY * 2:
  941. return locale.describe("day", sign, only_distance=only_distance)
  942. elif diff < self._SECS_PER_WEEK:
  943. days = sign * max(delta_second // self._SECS_PER_DAY, 2)
  944. return locale.describe("days", days, only_distance=only_distance)
  945. elif calendar_months >= 1 and diff < self._SECS_PER_YEAR:
  946. if calendar_months == 1:
  947. return locale.describe(
  948. "month", sign, only_distance=only_distance
  949. )
  950. else:
  951. months = sign * calendar_months
  952. return locale.describe(
  953. "months", months, only_distance=only_distance
  954. )
  955. elif diff < self._SECS_PER_WEEK * 2:
  956. return locale.describe("week", sign, only_distance=only_distance)
  957. elif diff < self._SECS_PER_MONTH:
  958. weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2)
  959. return locale.describe("weeks", weeks, only_distance=only_distance)
  960. elif diff < self._SECS_PER_YEAR * 2:
  961. return locale.describe("year", sign, only_distance=only_distance)
  962. else:
  963. years = sign * max(delta_second // self._SECS_PER_YEAR, 2)
  964. return locale.describe("years", years, only_distance=only_distance)
  965. elif isinstance(granularity, str):
  966. granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment]
  967. if granularity == "second":
  968. delta = sign * float(delta_second)
  969. if abs(delta) < 2:
  970. return locale.describe("now", only_distance=only_distance)
  971. elif granularity == "minute":
  972. delta = sign * delta_second / self._SECS_PER_MINUTE
  973. elif granularity == "hour":
  974. delta = sign * delta_second / self._SECS_PER_HOUR
  975. elif granularity == "day":
  976. delta = sign * delta_second / self._SECS_PER_DAY
  977. elif granularity == "week":
  978. delta = sign * delta_second / self._SECS_PER_WEEK
  979. elif granularity == "month":
  980. delta = sign * delta_second / self._SECS_PER_MONTH
  981. elif granularity == "quarter":
  982. delta = sign * delta_second / self._SECS_PER_QUARTER
  983. elif granularity == "year":
  984. delta = sign * delta_second / self._SECS_PER_YEAR
  985. else:
  986. raise ValueError(
  987. "Invalid level of granularity. "
  988. "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
  989. )
  990. if trunc(abs(delta)) != 1:
  991. granularity += "s" # type: ignore[assignment]
  992. return locale.describe(granularity, delta, only_distance=only_distance)
  993. else:
  994. if not granularity:
  995. raise ValueError(
  996. "Empty granularity list provided. "
  997. "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'."
  998. )
  999. timeframes: List[Tuple[TimeFrameLiteral, float]] = []
  1000. def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float:
  1001. if _frame in granularity:
  1002. value = sign * _delta / self._SECS_MAP[_frame]
  1003. _delta %= self._SECS_MAP[_frame]
  1004. if trunc(abs(value)) != 1:
  1005. timeframes.append(
  1006. (cast(TimeFrameLiteral, _frame + "s"), value)
  1007. )
  1008. else:
  1009. timeframes.append((_frame, value))
  1010. return _delta
  1011. delta = float(delta_second)
  1012. frames: Tuple[TimeFrameLiteral, ...] = (
  1013. "year",
  1014. "quarter",
  1015. "month",
  1016. "week",
  1017. "day",
  1018. "hour",
  1019. "minute",
  1020. "second",
  1021. )
  1022. for frame in frames:
  1023. delta = gather_timeframes(delta, frame)
  1024. if len(timeframes) < len(granularity):
  1025. raise ValueError(
  1026. "Invalid level of granularity. "
  1027. "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
  1028. )
  1029. return locale.describe_multi(timeframes, only_distance=only_distance)
  1030. except KeyError as e:
  1031. raise ValueError(
  1032. f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. "
  1033. "Please consider making a contribution to this locale."
  1034. )
  1035. def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow":
  1036. """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, that represents
  1037. the time difference relative to the attributes of the
  1038. :class:`Arrow <arrow.arrow.Arrow>` object.
  1039. :param timestring: a ``str`` representing a humanized relative time.
  1040. :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'.
  1041. Usage::
  1042. >>> arw = arrow.utcnow()
  1043. >>> arw
  1044. <Arrow [2021-04-20T22:27:34.787885+00:00]>
  1045. >>> earlier = arw.dehumanize("2 days ago")
  1046. >>> earlier
  1047. <Arrow [2021-04-18T22:27:34.787885+00:00]>
  1048. >>> arw = arrow.utcnow()
  1049. >>> arw
  1050. <Arrow [2021-04-20T22:27:34.787885+00:00]>
  1051. >>> later = arw.dehumanize("in a month")
  1052. >>> later
  1053. <Arrow [2021-05-18T22:27:34.787885+00:00]>
  1054. """
  1055. # Create a locale object based off given local
  1056. locale_obj = locales.get_locale(locale)
  1057. # Check to see if locale is supported
  1058. normalized_locale_name = locale.lower().replace("_", "-")
  1059. if normalized_locale_name not in DEHUMANIZE_LOCALES:
  1060. raise ValueError(
  1061. f"Dehumanize does not currently support the {locale} locale, please consider making a contribution to add support for this locale."
  1062. )
  1063. current_time = self.fromdatetime(self._datetime)
  1064. # Create an object containing the relative time info
  1065. time_object_info = dict.fromkeys(
  1066. ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0
  1067. )
  1068. # Create an object representing if unit has been seen
  1069. unit_visited = dict.fromkeys(
  1070. ["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"],
  1071. False,
  1072. )
  1073. # Create a regex pattern object for numbers
  1074. num_pattern = re.compile(r"\d+")
  1075. # Search input string for each time unit within locale
  1076. for unit, unit_object in locale_obj.timeframes.items():
  1077. # Need to check the type of unit_object to create the correct dictionary
  1078. if isinstance(unit_object, Mapping):
  1079. strings_to_search = unit_object
  1080. else:
  1081. strings_to_search = {unit: str(unit_object)}
  1082. # Search for any matches that exist for that locale's unit.
  1083. # Needs to cycle all through strings as some locales have strings that
  1084. # could overlap in a regex match, since input validation isn't being performed.
  1085. for time_delta, time_string in strings_to_search.items():
  1086. # Replace {0} with regex \d representing digits
  1087. search_string = str(time_string)
  1088. search_string = search_string.format(r"\d+")
  1089. # Create search pattern and find within string
  1090. pattern = re.compile(rf"(^|\b|\d){search_string}")
  1091. match = pattern.search(input_string)
  1092. # If there is no match continue to next iteration
  1093. if not match:
  1094. continue
  1095. match_string = match.group()
  1096. num_match = num_pattern.search(match_string)
  1097. # If no number matches
  1098. # Need for absolute value as some locales have signs included in their objects
  1099. if not num_match:
  1100. change_value = (
  1101. 1 if not time_delta.isnumeric() else abs(int(time_delta))
  1102. )
  1103. else:
  1104. change_value = int(num_match.group())
  1105. # No time to update if now is the unit
  1106. if unit == "now":
  1107. unit_visited[unit] = True
  1108. continue
  1109. # Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds)
  1110. time_unit_to_change = str(unit)
  1111. time_unit_to_change += (
  1112. "s" if (str(time_unit_to_change)[-1] != "s") else ""
  1113. )
  1114. time_object_info[time_unit_to_change] = change_value
  1115. unit_visited[time_unit_to_change] = True
  1116. # Assert error if string does not modify any units
  1117. if not any([True for k, v in unit_visited.items() if v]):
  1118. raise ValueError(
  1119. "Input string not valid. Note: Some locales do not support the week granularity in Arrow. "
  1120. "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error."
  1121. )
  1122. # Sign logic
  1123. future_string = locale_obj.future
  1124. future_string = future_string.format(".*")
  1125. future_pattern = re.compile(rf"^{future_string}$")
  1126. future_pattern_match = future_pattern.findall(input_string)
  1127. past_string = locale_obj.past
  1128. past_string = past_string.format(".*")
  1129. past_pattern = re.compile(rf"^{past_string}$")
  1130. past_pattern_match = past_pattern.findall(input_string)
  1131. # If a string contains the now unit, there will be no relative units, hence the need to check if the now unit
  1132. # was visited before raising a ValueError
  1133. if past_pattern_match:
  1134. sign_val = -1
  1135. elif future_pattern_match:
  1136. sign_val = 1
  1137. elif unit_visited["now"]:
  1138. sign_val = 0
  1139. else:
  1140. raise ValueError(
  1141. "Invalid input String. String does not contain any relative time information. "
  1142. "String should either represent a time in the future or a time in the past. "
  1143. "Ex: 'in 5 seconds' or '5 seconds ago'."
  1144. )
  1145. time_changes = {k: sign_val * v for k, v in time_object_info.items()}
  1146. return current_time.shift(check_imaginary=True, **time_changes)
  1147. # query functions
  1148. def is_between(
  1149. self,
  1150. start: "Arrow",
  1151. end: "Arrow",
  1152. bounds: _BOUNDS = "()",
  1153. ) -> bool:
  1154. """Returns a boolean denoting whether the :class:`Arrow <arrow.arrow.Arrow>` object is between
  1155. the start and end limits.
  1156. :param start: an :class:`Arrow <arrow.arrow.Arrow>` object.
  1157. :param end: an :class:`Arrow <arrow.arrow.Arrow>` object.
  1158. :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
  1159. whether to include or exclude the start and end values in the range. '(' excludes
  1160. the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
  1161. If the bounds are not specified, the default bound '()' is used.
  1162. Usage::
  1163. >>> start = arrow.get(datetime(2013, 5, 5, 12, 30, 10))
  1164. >>> end = arrow.get(datetime(2013, 5, 5, 12, 30, 36))
  1165. >>> arrow.get(datetime(2013, 5, 5, 12, 30, 27)).is_between(start, end)
  1166. True
  1167. >>> start = arrow.get(datetime(2013, 5, 5))
  1168. >>> end = arrow.get(datetime(2013, 5, 8))
  1169. >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[]')
  1170. True
  1171. >>> start = arrow.get(datetime(2013, 5, 5))
  1172. >>> end = arrow.get(datetime(2013, 5, 8))
  1173. >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[)')
  1174. False
  1175. """
  1176. util.validate_bounds(bounds)
  1177. if not isinstance(start, Arrow):
  1178. raise TypeError(
  1179. f"Cannot parse start date argument type of {type(start)!r}."
  1180. )
  1181. if not isinstance(end, Arrow):
  1182. raise TypeError(f"Cannot parse end date argument type of {type(start)!r}.")
  1183. include_start = bounds[0] == "["
  1184. include_end = bounds[1] == "]"
  1185. target_ts = self.float_timestamp
  1186. start_ts = start.float_timestamp
  1187. end_ts = end.float_timestamp
  1188. return (
  1189. (start_ts <= target_ts <= end_ts)
  1190. and (include_start or start_ts < target_ts)
  1191. and (include_end or target_ts < end_ts)
  1192. )
  1193. # datetime methods
  1194. def date(self) -> date:
  1195. """Returns a ``date`` object with the same year, month and day.
  1196. Usage::
  1197. >>> arrow.utcnow().date()
  1198. datetime.date(2019, 1, 23)
  1199. """
  1200. return self._datetime.date()
  1201. def time(self) -> dt_time:
  1202. """Returns a ``time`` object with the same hour, minute, second, microsecond.
  1203. Usage::
  1204. >>> arrow.utcnow().time()
  1205. datetime.time(12, 15, 34, 68352)
  1206. """
  1207. return self._datetime.time()
  1208. def timetz(self) -> dt_time:
  1209. """Returns a ``time`` object with the same hour, minute, second, microsecond and
  1210. tzinfo.
  1211. Usage::
  1212. >>> arrow.utcnow().timetz()
  1213. datetime.time(12, 5, 18, 298893, tzinfo=tzutc())
  1214. """
  1215. return self._datetime.timetz()
  1216. def astimezone(self, tz: Optional[dt_tzinfo]) -> dt_datetime:
  1217. """Returns a ``datetime`` object, converted to the specified timezone.
  1218. :param tz: a ``tzinfo`` object.
  1219. Usage::
  1220. >>> pacific=arrow.now('US/Pacific')
  1221. >>> nyc=arrow.now('America/New_York').tzinfo
  1222. >>> pacific.astimezone(nyc)
  1223. datetime.datetime(2019, 1, 20, 10, 24, 22, 328172, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York'))
  1224. """
  1225. return self._datetime.astimezone(tz)
  1226. def utcoffset(self) -> Optional[timedelta]:
  1227. """Returns a ``timedelta`` object representing the whole number of minutes difference from
  1228. UTC time.
  1229. Usage::
  1230. >>> arrow.now('US/Pacific').utcoffset()
  1231. datetime.timedelta(-1, 57600)
  1232. """
  1233. return self._datetime.utcoffset()
  1234. def dst(self) -> Optional[timedelta]:
  1235. """Returns the daylight savings time adjustment.
  1236. Usage::
  1237. >>> arrow.utcnow().dst()
  1238. datetime.timedelta(0)
  1239. """
  1240. return self._datetime.dst()
  1241. def timetuple(self) -> struct_time:
  1242. """Returns a ``time.struct_time``, in the current timezone.
  1243. Usage::
  1244. >>> arrow.utcnow().timetuple()
  1245. time.struct_time(tm_year=2019, tm_mon=1, tm_mday=20, tm_hour=15, tm_min=17, tm_sec=8, tm_wday=6, tm_yday=20, tm_isdst=0)
  1246. """
  1247. return self._datetime.timetuple()
  1248. def utctimetuple(self) -> struct_time:
  1249. """Returns a ``time.struct_time``, in UTC time.
  1250. Usage::
  1251. >>> arrow.utcnow().utctimetuple()
  1252. time.struct_time(tm_year=2019, tm_mon=1, tm_mday=19, tm_hour=21, tm_min=41, tm_sec=7, tm_wday=5, tm_yday=19, tm_isdst=0)
  1253. """
  1254. return self._datetime.utctimetuple()
  1255. def toordinal(self) -> int:
  1256. """Returns the proleptic Gregorian ordinal of the date.
  1257. Usage::
  1258. >>> arrow.utcnow().toordinal()
  1259. 737078
  1260. """
  1261. return self._datetime.toordinal()
  1262. def weekday(self) -> int:
  1263. """Returns the day of the week as an integer (0-6).
  1264. Usage::
  1265. >>> arrow.utcnow().weekday()
  1266. 5
  1267. """
  1268. return self._datetime.weekday()
  1269. def isoweekday(self) -> int:
  1270. """Returns the ISO day of the week as an integer (1-7).
  1271. Usage::
  1272. >>> arrow.utcnow().isoweekday()
  1273. 6
  1274. """
  1275. return self._datetime.isoweekday()
  1276. def isocalendar(self) -> Tuple[int, int, int]:
  1277. """Returns a 3-tuple, (ISO year, ISO week number, ISO weekday).
  1278. Usage::
  1279. >>> arrow.utcnow().isocalendar()
  1280. (2019, 3, 6)
  1281. """
  1282. return self._datetime.isocalendar()
  1283. def isoformat(self, sep: str = "T", timespec: str = "auto") -> str:
  1284. """Returns an ISO 8601 formatted representation of the date and time.
  1285. Usage::
  1286. >>> arrow.utcnow().isoformat()
  1287. '2019-01-19T18:30:52.442118+00:00'
  1288. """
  1289. return self._datetime.isoformat(sep, timespec)
  1290. def ctime(self) -> str:
  1291. """Returns a ctime formatted representation of the date and time.
  1292. Usage::
  1293. >>> arrow.utcnow().ctime()
  1294. 'Sat Jan 19 18:26:50 2019'
  1295. """
  1296. return self._datetime.ctime()
  1297. def strftime(self, format: str) -> str:
  1298. """Formats in the style of ``datetime.strftime``.
  1299. :param format: the format string.
  1300. Usage::
  1301. >>> arrow.utcnow().strftime('%d-%m-%Y %H:%M:%S')
  1302. '23-01-2019 12:28:17'
  1303. """
  1304. return self._datetime.strftime(format)
  1305. def for_json(self) -> str:
  1306. """Serializes for the ``for_json`` protocol of simplejson.
  1307. Usage::
  1308. >>> arrow.utcnow().for_json()
  1309. '2019-01-19T18:25:36.760079+00:00'
  1310. """
  1311. return self.isoformat()
  1312. # math
  1313. def __add__(self, other: Any) -> "Arrow":
  1314. if isinstance(other, (timedelta, relativedelta)):
  1315. return self.fromdatetime(self._datetime + other, self._datetime.tzinfo)
  1316. return NotImplemented
  1317. def __radd__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
  1318. return self.__add__(other)
  1319. @overload
  1320. def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
  1321. pass # pragma: no cover
  1322. @overload
  1323. def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta:
  1324. pass # pragma: no cover
  1325. def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]:
  1326. if isinstance(other, (timedelta, relativedelta)):
  1327. return self.fromdatetime(self._datetime - other, self._datetime.tzinfo)
  1328. elif isinstance(other, dt_datetime):
  1329. return self._datetime - other
  1330. elif isinstance(other, Arrow):
  1331. return self._datetime - other._datetime
  1332. return NotImplemented
  1333. def __rsub__(self, other: Any) -> timedelta:
  1334. if isinstance(other, dt_datetime):
  1335. return other - self._datetime
  1336. return NotImplemented
  1337. # comparisons
  1338. def __eq__(self, other: Any) -> bool:
  1339. if not isinstance(other, (Arrow, dt_datetime)):
  1340. return False
  1341. return self._datetime == self._get_datetime(other)
  1342. def __ne__(self, other: Any) -> bool:
  1343. if not isinstance(other, (Arrow, dt_datetime)):
  1344. return True
  1345. return not self.__eq__(other)
  1346. def __gt__(self, other: Any) -> bool:
  1347. if not isinstance(other, (Arrow, dt_datetime)):
  1348. return NotImplemented
  1349. return self._datetime > self._get_datetime(other)
  1350. def __ge__(self, other: Any) -> bool:
  1351. if not isinstance(other, (Arrow, dt_datetime)):
  1352. return NotImplemented
  1353. return self._datetime >= self._get_datetime(other)
  1354. def __lt__(self, other: Any) -> bool:
  1355. if not isinstance(other, (Arrow, dt_datetime)):
  1356. return NotImplemented
  1357. return self._datetime < self._get_datetime(other)
  1358. def __le__(self, other: Any) -> bool:
  1359. if not isinstance(other, (Arrow, dt_datetime)):
  1360. return NotImplemented
  1361. return self._datetime <= self._get_datetime(other)
  1362. # internal methods
  1363. @staticmethod
  1364. def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo:
  1365. """Get normalized tzinfo object from various inputs."""
  1366. if tz_expr is None:
  1367. return timezone.utc
  1368. if isinstance(tz_expr, dt_tzinfo):
  1369. return tz_expr
  1370. else:
  1371. try:
  1372. return parser.TzinfoParser.parse(tz_expr)
  1373. except parser.ParserError:
  1374. raise ValueError(f"{tz_expr!r} not recognized as a timezone.")
  1375. @classmethod
  1376. def _get_datetime(
  1377. cls, expr: Union["Arrow", dt_datetime, int, float, str]
  1378. ) -> dt_datetime:
  1379. """Get datetime object from a specified expression."""
  1380. if isinstance(expr, Arrow):
  1381. return expr.datetime
  1382. elif isinstance(expr, dt_datetime):
  1383. return expr
  1384. elif util.is_timestamp(expr):
  1385. timestamp = float(expr)
  1386. return cls.utcfromtimestamp(timestamp).datetime
  1387. else:
  1388. raise ValueError(f"{expr!r} not recognized as a datetime or timestamp.")
  1389. @classmethod
  1390. def _get_frames(cls, name: _T_FRAMES) -> Tuple[str, str, int]:
  1391. """Finds relevant timeframe and steps for use in range and span methods.
  1392. Returns a 3 element tuple in the form (frame, plural frame, step), for example ("day", "days", 1)
  1393. """
  1394. if name in cls._ATTRS:
  1395. return name, f"{name}s", 1
  1396. elif name[-1] == "s" and name[:-1] in cls._ATTRS:
  1397. return name[:-1], name, 1
  1398. elif name in ["week", "weeks"]:
  1399. return "week", "weeks", 1
  1400. elif name in ["quarter", "quarters"]:
  1401. return "quarter", "months", 3
  1402. else:
  1403. supported = ", ".join(
  1404. [
  1405. "year(s)",
  1406. "month(s)",
  1407. "day(s)",
  1408. "hour(s)",
  1409. "minute(s)",
  1410. "second(s)",
  1411. "microsecond(s)",
  1412. "week(s)",
  1413. "quarter(s)",
  1414. ]
  1415. )
  1416. raise ValueError(
  1417. f"Range or span over frame {name} not supported. Supported frames: {supported}."
  1418. )
  1419. @classmethod
  1420. def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]:
  1421. """Sets default end and limit values for range method."""
  1422. if end is None:
  1423. if limit is None:
  1424. raise ValueError("One of 'end' or 'limit' is required.")
  1425. return cls.max, limit
  1426. else:
  1427. if limit is None:
  1428. return end, sys.maxsize
  1429. return end, limit
  1430. @staticmethod
  1431. def _is_last_day_of_month(date: "Arrow") -> bool:
  1432. """Returns a boolean indicating whether the datetime is the last day of the month."""
  1433. return cast(int, date.day) == calendar.monthrange(date.year, date.month)[1]
  1434. Arrow.min = Arrow.fromdatetime(dt_datetime.min)
  1435. Arrow.max = Arrow.fromdatetime(dt_datetime.max)