Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/arrow/arrow.py: 26%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime``
3replacement.
5"""
7import calendar
8import re
9import sys
10from datetime import date
11from datetime import datetime as dt_datetime
12from datetime import time as dt_time
13from datetime import timedelta, timezone
14from datetime import tzinfo as dt_tzinfo
15from math import trunc
16from time import struct_time
17from typing import (
18 Any,
19 ClassVar,
20 Final,
21 Generator,
22 Iterable,
23 List,
24 Literal,
25 Mapping,
26 Optional,
27 Tuple,
28 Union,
29 cast,
30 overload,
31)
33from dateutil import tz as dateutil_tz
34from dateutil.relativedelta import relativedelta
36from arrow import formatter, locales, parser, util
37from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES
38from arrow.locales import TimeFrameLiteral
40TZ_EXPR = Union[dt_tzinfo, str]
42_T_FRAMES = Literal[
43 "year",
44 "years",
45 "month",
46 "months",
47 "day",
48 "days",
49 "hour",
50 "hours",
51 "minute",
52 "minutes",
53 "second",
54 "seconds",
55 "microsecond",
56 "microseconds",
57 "week",
58 "weeks",
59 "quarter",
60 "quarters",
61]
63_BOUNDS = Literal["[)", "()", "(]", "[]"]
65_GRANULARITY = Literal[
66 "auto",
67 "second",
68 "minute",
69 "hour",
70 "day",
71 "week",
72 "month",
73 "quarter",
74 "year",
75]
78class Arrow:
79 """An :class:`Arrow <arrow.arrow.Arrow>` object.
81 Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing
82 additional functionality.
84 :param year: the calendar year.
85 :param month: the calendar month.
86 :param day: the calendar day.
87 :param hour: (optional) the hour. Defaults to 0.
88 :param minute: (optional) the minute, Defaults to 0.
89 :param second: (optional) the second, Defaults to 0.
90 :param microsecond: (optional) the microsecond. Defaults to 0.
91 :param tzinfo: (optional) A timezone expression. Defaults to UTC.
92 :param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0.
94 .. _tz-expr:
96 Recognized timezone expressions:
98 - A ``tzinfo`` object.
99 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
100 - A ``str`` in ISO 8601 style, as in '+07:00'.
101 - A ``str``, one of the following: 'local', 'utc', 'UTC'.
103 Usage::
105 >>> import arrow
106 >>> arrow.Arrow(2013, 5, 5, 12, 30, 45)
107 <Arrow [2013-05-05T12:30:45+00:00]>
109 """
111 resolution: ClassVar[timedelta] = dt_datetime.resolution
112 min: ClassVar["Arrow"]
113 max: ClassVar["Arrow"]
115 _ATTRS: Final[List[str]] = [
116 "year",
117 "month",
118 "day",
119 "hour",
120 "minute",
121 "second",
122 "microsecond",
123 ]
124 _ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS]
125 _MONTHS_PER_QUARTER: Final[int] = 3
126 _SECS_PER_MINUTE: Final[int] = 60
127 _SECS_PER_HOUR: Final[int] = 60 * 60
128 _SECS_PER_DAY: Final[int] = 60 * 60 * 24
129 _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7
130 _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5
131 _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3
132 _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365
134 _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = {
135 "second": 1.0,
136 "minute": _SECS_PER_MINUTE,
137 "hour": _SECS_PER_HOUR,
138 "day": _SECS_PER_DAY,
139 "week": _SECS_PER_WEEK,
140 "month": _SECS_PER_MONTH,
141 "quarter": _SECS_PER_QUARTER,
142 "year": _SECS_PER_YEAR,
143 }
145 _datetime: dt_datetime
147 def __init__(
148 self,
149 year: int,
150 month: int,
151 day: int,
152 hour: int = 0,
153 minute: int = 0,
154 second: int = 0,
155 microsecond: int = 0,
156 tzinfo: Optional[TZ_EXPR] = None,
157 **kwargs: Any,
158 ) -> None:
159 if tzinfo is None:
160 tzinfo = dateutil_tz.tzutc()
161 # detect that tzinfo is a pytz object (issue #626)
162 elif (
163 isinstance(tzinfo, dt_tzinfo)
164 and hasattr(tzinfo, "localize")
165 and hasattr(tzinfo, "zone")
166 and tzinfo.zone
167 ):
168 tzinfo = parser.TzinfoParser.parse(tzinfo.zone)
169 elif isinstance(tzinfo, str):
170 tzinfo = parser.TzinfoParser.parse(tzinfo)
172 fold = kwargs.get("fold", 0)
174 self._datetime = dt_datetime(
175 year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold
176 )
178 # factories: single object, both original and from datetime.
180 @classmethod
181 def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow":
182 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given
183 timezone.
185 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
187 Usage::
189 >>> arrow.now('Asia/Baku')
190 <Arrow [2019-01-24T20:26:31.146412+04:00]>
192 """
194 if tzinfo is None:
195 tzinfo = dateutil_tz.tzlocal()
197 dt = dt_datetime.now(tzinfo)
199 return cls(
200 dt.year,
201 dt.month,
202 dt.day,
203 dt.hour,
204 dt.minute,
205 dt.second,
206 dt.microsecond,
207 dt.tzinfo,
208 fold=getattr(dt, "fold", 0),
209 )
211 @classmethod
212 def utcnow(cls) -> "Arrow":
213 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC
214 time.
216 Usage::
218 >>> arrow.utcnow()
219 <Arrow [2019-01-24T16:31:40.651108+00:00]>
221 """
223 dt = dt_datetime.now(dateutil_tz.tzutc())
225 return cls(
226 dt.year,
227 dt.month,
228 dt.day,
229 dt.hour,
230 dt.minute,
231 dt.second,
232 dt.microsecond,
233 dt.tzinfo,
234 fold=getattr(dt, "fold", 0),
235 )
237 @classmethod
238 def fromtimestamp(
239 cls,
240 timestamp: Union[int, float, str],
241 tzinfo: Optional[TZ_EXPR] = None,
242 ) -> "Arrow":
243 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, converted to
244 the given timezone.
246 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
247 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
249 """
251 if tzinfo is None:
252 tzinfo = dateutil_tz.tzlocal()
253 elif isinstance(tzinfo, str):
254 tzinfo = parser.TzinfoParser.parse(tzinfo)
256 if not util.is_timestamp(timestamp):
257 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")
259 timestamp = util.normalize_timestamp(float(timestamp))
260 dt = dt_datetime.fromtimestamp(timestamp, tzinfo)
262 return cls(
263 dt.year,
264 dt.month,
265 dt.day,
266 dt.hour,
267 dt.minute,
268 dt.second,
269 dt.microsecond,
270 dt.tzinfo,
271 fold=getattr(dt, "fold", 0),
272 )
274 @classmethod
275 def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow":
276 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, in UTC time.
278 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
280 """
282 if not util.is_timestamp(timestamp):
283 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")
285 timestamp = util.normalize_timestamp(float(timestamp))
286 dt = dt_datetime.utcfromtimestamp(timestamp)
288 return cls(
289 dt.year,
290 dt.month,
291 dt.day,
292 dt.hour,
293 dt.minute,
294 dt.second,
295 dt.microsecond,
296 dateutil_tz.tzutc(),
297 fold=getattr(dt, "fold", 0),
298 )
300 @classmethod
301 def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
302 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and
303 optional replacement timezone.
305 :param dt: the ``datetime``
306 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to ``dt``'s
307 timezone, or UTC if naive.
309 Usage::
311 >>> dt
312 datetime.datetime(2021, 4, 7, 13, 48, tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific'))
313 >>> arrow.Arrow.fromdatetime(dt)
314 <Arrow [2021-04-07T13:48:00-07:00]>
316 """
318 if tzinfo is None:
319 if dt.tzinfo is None:
320 tzinfo = dateutil_tz.tzutc()
321 else:
322 tzinfo = dt.tzinfo
324 return cls(
325 dt.year,
326 dt.month,
327 dt.day,
328 dt.hour,
329 dt.minute,
330 dt.second,
331 dt.microsecond,
332 tzinfo,
333 fold=getattr(dt, "fold", 0),
334 )
336 @classmethod
337 def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
338 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional
339 replacement timezone. All time values are set to 0.
341 :param date: the ``date``
342 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to UTC.
344 """
346 if tzinfo is None:
347 tzinfo = dateutil_tz.tzutc()
349 return cls(date.year, date.month, date.day, tzinfo=tzinfo)
351 @classmethod
352 def strptime(
353 cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None
354 ) -> "Arrow":
355 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a date string and format,
356 in the style of ``datetime.strptime``. Optionally replaces the parsed timezone.
358 :param date_str: the date string.
359 :param fmt: the format string using datetime format codes.
360 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to the parsed
361 timezone if ``fmt`` contains a timezone directive, otherwise UTC.
363 Usage::
365 >>> arrow.Arrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S')
366 <Arrow [2019-01-20T15:49:10+00:00]>
368 """
370 dt = dt_datetime.strptime(date_str, fmt)
371 if tzinfo is None:
372 tzinfo = dt.tzinfo
374 return cls(
375 dt.year,
376 dt.month,
377 dt.day,
378 dt.hour,
379 dt.minute,
380 dt.second,
381 dt.microsecond,
382 tzinfo,
383 fold=getattr(dt, "fold", 0),
384 )
386 @classmethod
387 def fromordinal(cls, ordinal: int) -> "Arrow":
388 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object corresponding
389 to the Gregorian Ordinal.
391 :param ordinal: an ``int`` corresponding to a Gregorian Ordinal.
393 Usage::
395 >>> arrow.fromordinal(737741)
396 <Arrow [2020-11-12T00:00:00+00:00]>
398 """
400 util.validate_ordinal(ordinal)
401 dt = dt_datetime.fromordinal(ordinal)
402 return cls(
403 dt.year,
404 dt.month,
405 dt.day,
406 dt.hour,
407 dt.minute,
408 dt.second,
409 dt.microsecond,
410 dt.tzinfo,
411 fold=getattr(dt, "fold", 0),
412 )
414 # factories: ranges and spans
416 @classmethod
417 def range(
418 cls,
419 frame: _T_FRAMES,
420 start: Union["Arrow", dt_datetime],
421 end: Union["Arrow", dt_datetime, None] = None,
422 tz: Optional[TZ_EXPR] = None,
423 limit: Optional[int] = None,
424 ) -> Generator["Arrow", None, None]:
425 """Returns an iterator of :class:`Arrow <arrow.arrow.Arrow>` objects, representing
426 points in time between two inputs.
428 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
429 :param start: A datetime expression, the start of the range.
430 :param end: (optional) A datetime expression, the end of the range.
431 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to
432 ``start``'s timezone, or UTC if ``start`` is naive.
433 :param limit: (optional) A maximum number of tuples to return.
435 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to
436 return the entire range. Call with ``limit`` alone to return a maximum # of results from
437 the start. Call with both to cap a range at a maximum # of results.
439 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
440 iterating. As such, either call with naive objects and ``tz``, or aware objects from the
441 same timezone and no ``tz``.
443 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.
445 Recognized datetime expressions:
447 - An :class:`Arrow <arrow.arrow.Arrow>` object.
448 - A ``datetime`` object.
450 Usage::
452 >>> start = datetime(2013, 5, 5, 12, 30)
453 >>> end = datetime(2013, 5, 5, 17, 15)
454 >>> for r in arrow.Arrow.range('hour', start, end):
455 ... print(repr(r))
456 ...
457 <Arrow [2013-05-05T12:30:00+00:00]>
458 <Arrow [2013-05-05T13:30:00+00:00]>
459 <Arrow [2013-05-05T14:30:00+00:00]>
460 <Arrow [2013-05-05T15:30:00+00:00]>
461 <Arrow [2013-05-05T16:30:00+00:00]>
463 **NOTE**: Unlike Python's ``range``, ``end`` *may* be included in the returned iterator::
465 >>> start = datetime(2013, 5, 5, 12, 30)
466 >>> end = datetime(2013, 5, 5, 13, 30)
467 >>> for r in arrow.Arrow.range('hour', start, end):
468 ... print(repr(r))
469 ...
470 <Arrow [2013-05-05T12:30:00+00:00]>
471 <Arrow [2013-05-05T13:30:00+00:00]>
473 """
475 _, frame_relative, relative_steps = cls._get_frames(frame)
477 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
479 start = cls._get_datetime(start).replace(tzinfo=tzinfo)
480 end, limit = cls._get_iteration_params(end, limit)
481 end = cls._get_datetime(end).replace(tzinfo=tzinfo)
483 current = cls.fromdatetime(start)
484 original_day = start.day
485 day_is_clipped = False
486 i = 0
488 while current <= end and i < limit:
489 i += 1
490 yield current
492 values = [getattr(current, f) for f in cls._ATTRS]
493 current = cls(*values, tzinfo=tzinfo).shift( # type: ignore[misc]
494 check_imaginary=True, **{frame_relative: relative_steps}
495 )
497 if frame in ["month", "quarter", "year"] and current.day < original_day:
498 day_is_clipped = True
500 if day_is_clipped and not cls._is_last_day_of_month(current):
501 current = current.replace(day=original_day)
503 def span(
504 self,
505 frame: _T_FRAMES,
506 count: int = 1,
507 bounds: _BOUNDS = "[)",
508 exact: bool = False,
509 week_start: int = 1,
510 ) -> Tuple["Arrow", "Arrow"]:
511 """Returns a tuple of two new :class:`Arrow <arrow.arrow.Arrow>` objects, representing the timespan
512 of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
514 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
515 :param count: (optional) the number of frames to span.
516 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
517 whether to include or exclude the start and end values in the span. '(' excludes
518 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
519 If the bounds are not specified, the default bound '[)' is used.
520 :param exact: (optional) whether to have the start of the timespan begin exactly
521 at the time specified by ``start`` and the end of the timespan truncated
522 so as not to extend beyond ``end``.
523 :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
524 Monday is 1 and Sunday is 7.
526 Supported frame values: year, quarter, month, week, day, hour, minute, second.
528 Usage::
530 >>> arrow.utcnow()
531 <Arrow [2013-05-09T03:32:36.186203+00:00]>
533 >>> arrow.utcnow().span('hour')
534 (<Arrow [2013-05-09T03:00:00+00:00]>, <Arrow [2013-05-09T03:59:59.999999+00:00]>)
536 >>> arrow.utcnow().span('day')
537 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-09T23:59:59.999999+00:00]>)
539 >>> arrow.utcnow().span('day', count=2)
540 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T23:59:59.999999+00:00]>)
542 >>> arrow.utcnow().span('day', bounds='[]')
543 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T00:00:00+00:00]>)
545 >>> arrow.utcnow().span('week')
546 (<Arrow [2021-02-22T00:00:00+00:00]>, <Arrow [2021-02-28T23:59:59.999999+00:00]>)
548 >>> arrow.utcnow().span('week', week_start=6)
549 (<Arrow [2021-02-20T00:00:00+00:00]>, <Arrow [2021-02-26T23:59:59.999999+00:00]>)
551 """
552 if not 1 <= week_start <= 7:
553 raise ValueError("week_start argument must be between 1 and 7.")
555 util.validate_bounds(bounds)
557 frame_absolute, frame_relative, relative_steps = self._get_frames(frame)
559 if frame_absolute == "week":
560 attr = "day"
561 elif frame_absolute == "quarter":
562 attr = "month"
563 else:
564 attr = frame_absolute
566 floor = self
567 if not exact:
568 index = self._ATTRS.index(attr)
569 frames = self._ATTRS[: index + 1]
571 values = [getattr(self, f) for f in frames]
573 for _ in range(3 - len(values)):
574 values.append(1)
576 floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc]
578 if frame_absolute == "week":
579 # if week_start is greater than self.isoweekday() go back one week by setting delta = 7
580 delta = 7 if week_start > self.isoweekday() else 0
581 floor = floor.shift(days=-(self.isoweekday() - week_start) - delta)
582 elif frame_absolute == "quarter":
583 floor = floor.shift(months=-((self.month - 1) % 3))
585 ceil = floor.shift(
586 check_imaginary=True, **{frame_relative: count * relative_steps}
587 )
589 if bounds[0] == "(":
590 floor = floor.shift(microseconds=+1)
592 if bounds[1] == ")":
593 ceil = ceil.shift(microseconds=-1)
595 return floor, ceil
597 def floor(self, frame: _T_FRAMES) -> "Arrow":
598 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "floor"
599 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
600 Equivalent to the first element in the 2-tuple returned by
601 :func:`span <arrow.arrow.Arrow.span>`.
603 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
605 Usage::
607 >>> arrow.utcnow().floor('hour')
608 <Arrow [2013-05-09T03:00:00+00:00]>
610 """
612 return self.span(frame)[0]
614 def ceil(self, frame: _T_FRAMES) -> "Arrow":
615 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "ceiling"
616 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
617 Equivalent to the second element in the 2-tuple returned by
618 :func:`span <arrow.arrow.Arrow.span>`.
620 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
622 Usage::
624 >>> arrow.utcnow().ceil('hour')
625 <Arrow [2013-05-09T03:59:59.999999+00:00]>
627 """
629 return self.span(frame)[1]
631 @classmethod
632 def span_range(
633 cls,
634 frame: _T_FRAMES,
635 start: dt_datetime,
636 end: dt_datetime,
637 tz: Optional[TZ_EXPR] = None,
638 limit: Optional[int] = None,
639 bounds: _BOUNDS = "[)",
640 exact: bool = False,
641 ) -> Iterable[Tuple["Arrow", "Arrow"]]:
642 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
643 representing a series of timespans between two inputs.
645 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
646 :param start: A datetime expression, the start of the range.
647 :param end: (optional) A datetime expression, the end of the range.
648 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to
649 ``start``'s timezone, or UTC if ``start`` is naive.
650 :param limit: (optional) A maximum number of tuples to return.
651 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
652 whether to include or exclude the start and end values in each span in the range. '(' excludes
653 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
654 If the bounds are not specified, the default bound '[)' is used.
655 :param exact: (optional) whether to have the first timespan start exactly
656 at the time specified by ``start`` and the final span truncated
657 so as not to extend beyond ``end``.
659 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to
660 return the entire range. Call with ``limit`` alone to return a maximum # of results from
661 the start. Call with both to cap a range at a maximum # of results.
663 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
664 iterating. As such, either call with naive objects and ``tz``, or aware objects from the
665 same timezone and no ``tz``.
667 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.
669 Recognized datetime expressions:
671 - An :class:`Arrow <arrow.arrow.Arrow>` object.
672 - A ``datetime`` object.
674 **NOTE**: Unlike Python's ``range``, ``end`` will *always* be included in the returned
675 iterator of timespans.
677 Usage:
679 >>> start = datetime(2013, 5, 5, 12, 30)
680 >>> end = datetime(2013, 5, 5, 17, 15)
681 >>> for r in arrow.Arrow.span_range('hour', start, end):
682 ... print(r)
683 ...
684 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>)
685 (<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
686 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>)
687 (<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
688 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>)
689 (<Arrow [2013-05-05T17:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:00]>)
691 """
693 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
694 start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0]
695 end = cls.fromdatetime(end, tzinfo)
696 _range = cls.range(frame, start, end, tz, limit)
697 if not exact:
698 for r in _range:
699 yield r.span(frame, bounds=bounds, exact=exact)
701 for r in _range:
702 floor, ceil = r.span(frame, bounds=bounds, exact=exact)
703 if ceil > end:
704 ceil = end
705 if bounds[1] == ")":
706 ceil += relativedelta(microseconds=-1)
707 if floor == end:
708 break
709 elif floor + relativedelta(microseconds=-1) == end:
710 break
711 yield floor, ceil
713 @classmethod
714 def interval(
715 cls,
716 frame: _T_FRAMES,
717 start: dt_datetime,
718 end: dt_datetime,
719 interval: int = 1,
720 tz: Optional[TZ_EXPR] = None,
721 bounds: _BOUNDS = "[)",
722 exact: bool = False,
723 ) -> Iterable[Tuple["Arrow", "Arrow"]]:
724 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
725 representing a series of intervals between two inputs.
727 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
728 :param start: A datetime expression, the start of the range.
729 :param end: (optional) A datetime expression, the end of the range.
730 :param interval: (optional) Time interval for the given time frame.
731 :param tz: (optional) A timezone expression. Defaults to UTC.
732 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
733 whether to include or exclude the start and end values in the intervals. '(' excludes
734 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
735 If the bounds are not specified, the default bound '[)' is used.
736 :param exact: (optional) whether to have the first timespan start exactly
737 at the time specified by ``start`` and the final interval truncated
738 so as not to extend beyond ``end``.
740 Supported frame values: year, quarter, month, week, day, hour, minute, second
742 Recognized datetime expressions:
744 - An :class:`Arrow <arrow.arrow.Arrow>` object.
745 - A ``datetime`` object.
747 Recognized timezone expressions:
749 - A ``tzinfo`` object.
750 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
751 - A ``str`` in ISO 8601 style, as in '+07:00'.
752 - A ``str``, one of the following: 'local', 'utc', 'UTC'.
754 Usage:
756 >>> start = datetime(2013, 5, 5, 12, 30)
757 >>> end = datetime(2013, 5, 5, 17, 15)
758 >>> for r in arrow.Arrow.interval('hour', start, end, 2):
759 ... print(r)
760 ...
761 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
762 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
763 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:0]>)
764 """
765 if interval < 1:
766 raise ValueError("interval has to be a positive integer")
768 spanRange = iter(
769 cls.span_range(frame, start, end, tz, bounds=bounds, exact=exact)
770 )
771 while True:
772 try:
773 intvlStart, intvlEnd = next(spanRange)
774 for _ in range(interval - 1):
775 try:
776 _, intvlEnd = next(spanRange)
777 except StopIteration:
778 continue
779 yield intvlStart, intvlEnd
780 except StopIteration:
781 return
783 # representations
785 def __repr__(self) -> str:
786 return f"<{self.__class__.__name__} [{self.__str__()}]>"
788 def __str__(self) -> str:
789 return self._datetime.isoformat()
791 def __format__(self, formatstr: str) -> str:
792 if len(formatstr) > 0:
793 return self.format(formatstr)
795 return str(self)
797 def __hash__(self) -> int:
798 return self._datetime.__hash__()
800 # attributes and properties
802 def __getattr__(self, name: str) -> Any:
803 if name == "week":
804 return self.isocalendar()[1]
806 if name == "quarter":
807 return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1
809 if not name.startswith("_"):
810 value: Optional[Any] = getattr(self._datetime, name, None)
812 if value is not None:
813 return value
815 return cast(int, object.__getattribute__(self, name))
817 @property
818 def tzinfo(self) -> dt_tzinfo:
819 """Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object.
821 Usage::
823 >>> arw=arrow.utcnow()
824 >>> arw.tzinfo
825 tzutc()
827 """
829 # In Arrow, `_datetime` cannot be naive.
830 return cast(dt_tzinfo, self._datetime.tzinfo)
832 @property
833 def datetime(self) -> dt_datetime:
834 """Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object.
836 Usage::
838 >>> arw=arrow.utcnow()
839 >>> arw.datetime
840 datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc())
842 """
844 return self._datetime
846 @property
847 def naive(self) -> dt_datetime:
848 """Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>`
849 object.
851 Usage::
853 >>> nairobi = arrow.now('Africa/Nairobi')
854 >>> nairobi
855 <Arrow [2019-01-23T19:27:12.297999+03:00]>
856 >>> nairobi.naive
857 datetime.datetime(2019, 1, 23, 19, 27, 12, 297999)
859 """
861 return self._datetime.replace(tzinfo=None)
863 def timestamp(self) -> float:
864 """Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
865 UTC time.
867 Usage::
869 >>> arrow.utcnow().timestamp()
870 1616882340.256501
872 """
874 return self._datetime.timestamp()
876 @property
877 def int_timestamp(self) -> int:
878 """Returns an integer timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
879 UTC time.
881 Usage::
883 >>> arrow.utcnow().int_timestamp
884 1548260567
886 """
888 return int(self.timestamp())
890 @property
891 def float_timestamp(self) -> float:
892 """Returns a floating-point timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>`
893 object, in UTC time.
895 Usage::
897 >>> arrow.utcnow().float_timestamp
898 1548260516.830896
900 """
902 return self.timestamp()
904 @property
905 def fold(self) -> int:
906 """Returns the ``fold`` value of the :class:`Arrow <arrow.arrow.Arrow>` object."""
908 return self._datetime.fold
910 @property
911 def ambiguous(self) -> bool:
912 """Indicates whether the :class:`Arrow <arrow.arrow.Arrow>` object is a repeated wall time in the current
913 timezone.
915 """
917 return dateutil_tz.datetime_ambiguous(self._datetime)
919 @property
920 def imaginary(self) -> bool:
921 """Indicates whether the :class: `Arrow <arrow.arrow.Arrow>` object exists in the current timezone."""
923 return not dateutil_tz.datetime_exists(self._datetime)
925 # mutation and duplication.
927 def clone(self) -> "Arrow":
928 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, cloned from the current one.
930 Usage:
932 >>> arw = arrow.utcnow()
933 >>> cloned = arw.clone()
935 """
937 return self.fromdatetime(self._datetime)
939 def replace(self, **kwargs: Any) -> "Arrow":
940 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
941 according to inputs.
943 Use property names to set their value absolutely::
945 >>> import arrow
946 >>> arw = arrow.utcnow()
947 >>> arw
948 <Arrow [2013-05-11T22:27:34.787885+00:00]>
949 >>> arw.replace(year=2014, month=6)
950 <Arrow [2014-06-11T22:27:34.787885+00:00]>
952 You can also replace the timezone without conversion, using a
953 :ref:`timezone expression <tz-expr>`::
955 >>> arw.replace(tzinfo=tz.tzlocal())
956 <Arrow [2013-05-11T22:27:34.787885-07:00]>
958 """
960 absolute_kwargs = {}
962 for key, value in kwargs.items():
963 if key in self._ATTRS:
964 absolute_kwargs[key] = value
965 elif key in ["week", "quarter"]:
966 raise ValueError(f"Setting absolute {key} is not supported.")
967 elif key not in ["tzinfo", "fold"]:
968 raise ValueError(f"Unknown attribute: {key!r}.")
970 current = self._datetime.replace(**absolute_kwargs)
972 tzinfo = kwargs.get("tzinfo")
974 if tzinfo is not None:
975 tzinfo = self._get_tzinfo(tzinfo)
976 current = current.replace(tzinfo=tzinfo)
978 fold = kwargs.get("fold")
980 if fold is not None:
981 current = current.replace(fold=fold)
983 return self.fromdatetime(current)
985 def shift(self, check_imaginary: bool = True, **kwargs: Any) -> "Arrow":
986 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
987 according to inputs.
989 Parameters:
990 check_imaginary (bool): If True (default), will check for and resolve
991 imaginary times (like during DST transitions). If False, skips this check.
994 Use pluralized property names to relatively shift their current value:
996 >>> import arrow
997 >>> arw = arrow.utcnow()
998 >>> arw
999 <Arrow [2013-05-11T22:27:34.787885+00:00]>
1000 >>> arw.shift(years=1, months=-1)
1001 <Arrow [2014-04-11T22:27:34.787885+00:00]>
1003 Day-of-the-week relative shifting can use either Python's weekday numbers
1004 (Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's
1005 day instances (MO, TU .. SU). When using weekday numbers, the returned
1006 date will always be greater than or equal to the starting date.
1008 Using the above code (which is a Saturday) and asking it to shift to Saturday:
1010 >>> arw.shift(weekday=5)
1011 <Arrow [2013-05-11T22:27:34.787885+00:00]>
1013 While asking for a Monday:
1015 >>> arw.shift(weekday=0)
1016 <Arrow [2013-05-13T22:27:34.787885+00:00]>
1018 """
1020 relative_kwargs = {}
1021 additional_attrs = ["weeks", "quarters", "weekday"]
1023 for key, value in kwargs.items():
1024 if key in self._ATTRS_PLURAL or key in additional_attrs:
1025 relative_kwargs[key] = value
1026 else:
1027 supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs)
1028 raise ValueError(
1029 f"Invalid shift time frame. Please select one of the following: {supported_attr}."
1030 )
1032 # core datetime does not support quarters, translate to months.
1033 relative_kwargs.setdefault("months", 0)
1034 relative_kwargs["months"] += (
1035 relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER
1036 )
1038 current = self._datetime + relativedelta(**relative_kwargs)
1040 # If check_imaginary is True, perform the check for imaginary times (DST transitions)
1041 if check_imaginary and not dateutil_tz.datetime_exists(current):
1042 current = dateutil_tz.resolve_imaginary(current)
1044 return self.fromdatetime(current)
1046 def to(self, tz: TZ_EXPR) -> "Arrow":
1047 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted
1048 to the target timezone.
1050 :param tz: A :ref:`timezone expression <tz-expr>`.
1052 Usage::
1054 >>> utc = arrow.utcnow()
1055 >>> utc
1056 <Arrow [2013-05-09T03:49:12.311072+00:00]>
1058 >>> utc.to('US/Pacific')
1059 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1061 >>> utc.to(tz.tzlocal())
1062 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1064 >>> utc.to('-07:00')
1065 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1067 >>> utc.to('local')
1068 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1070 >>> utc.to('local').to('utc')
1071 <Arrow [2013-05-09T03:49:12.311072+00:00]>
1073 """
1075 if not isinstance(tz, dt_tzinfo):
1076 tz = parser.TzinfoParser.parse(tz)
1078 dt = self._datetime.astimezone(tz)
1080 return self.__class__(
1081 dt.year,
1082 dt.month,
1083 dt.day,
1084 dt.hour,
1085 dt.minute,
1086 dt.second,
1087 dt.microsecond,
1088 dt.tzinfo,
1089 fold=getattr(dt, "fold", 0),
1090 )
1092 # string output and formatting
1094 def format(
1095 self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE
1096 ) -> str:
1097 """Returns a string representation of the :class:`Arrow <arrow.arrow.Arrow>` object,
1098 formatted according to the provided format string. For a list of formatting values,
1099 see :ref:`supported-tokens`
1101 :param fmt: the format string.
1102 :param locale: the locale to format.
1104 Usage::
1106 >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ')
1107 '2013-05-09 03:56:47 -00:00'
1109 >>> arrow.utcnow().format('X')
1110 '1368071882'
1112 >>> arrow.utcnow().format('MMMM DD, YYYY')
1113 'May 09, 2013'
1115 >>> arrow.utcnow().format()
1116 '2013-05-09 03:56:47 -00:00'
1118 """
1120 return formatter.DateTimeFormatter(locale).format(self._datetime, fmt)
1122 def humanize(
1123 self,
1124 other: Union["Arrow", dt_datetime, None] = None,
1125 locale: str = DEFAULT_LOCALE,
1126 only_distance: bool = False,
1127 granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto",
1128 ) -> str:
1129 """Returns a localized, humanized representation of a relative difference in time.
1131 :param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object.
1132 Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone.
1133 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'.
1134 :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part.
1135 :param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute',
1136 'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings
1138 Usage::
1140 >>> earlier = arrow.utcnow().shift(hours=-2)
1141 >>> earlier.humanize()
1142 '2 hours ago'
1144 >>> later = earlier.shift(hours=4)
1145 >>> later.humanize(earlier)
1146 'in 4 hours'
1148 """
1150 locale_name = locale
1151 locale = locales.get_locale(locale)
1153 if other is None:
1154 utc = dt_datetime.now(timezone.utc).replace(tzinfo=dateutil_tz.tzutc())
1155 dt = utc.astimezone(self._datetime.tzinfo)
1157 elif isinstance(other, Arrow):
1158 dt = other._datetime
1160 elif isinstance(other, dt_datetime):
1161 if other.tzinfo is None:
1162 dt = other.replace(tzinfo=self._datetime.tzinfo)
1163 else:
1164 dt = other.astimezone(self._datetime.tzinfo)
1166 else:
1167 raise TypeError(
1168 f"Invalid 'other' argument of type {type(other).__name__!r}. "
1169 "Argument must be of type None, Arrow, or datetime."
1170 )
1172 if isinstance(granularity, list) and len(granularity) == 1:
1173 granularity = granularity[0]
1175 _delta = int(round((self._datetime - dt).total_seconds()))
1176 sign = -1 if _delta < 0 else 1
1177 delta_second = diff = abs(_delta)
1179 try:
1180 if granularity == "auto":
1181 if diff < 10:
1182 return locale.describe("now", only_distance=only_distance)
1184 if diff < self._SECS_PER_MINUTE:
1185 seconds = sign * delta_second
1186 return locale.describe(
1187 "seconds", seconds, only_distance=only_distance
1188 )
1190 elif diff < self._SECS_PER_MINUTE * 2:
1191 return locale.describe("minute", sign, only_distance=only_distance)
1192 elif diff < self._SECS_PER_HOUR:
1193 minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2)
1194 return locale.describe(
1195 "minutes", minutes, only_distance=only_distance
1196 )
1198 elif diff < self._SECS_PER_HOUR * 2:
1199 return locale.describe("hour", sign, only_distance=only_distance)
1200 elif diff < self._SECS_PER_DAY:
1201 hours = sign * max(delta_second // self._SECS_PER_HOUR, 2)
1202 return locale.describe("hours", hours, only_distance=only_distance)
1203 elif diff < self._SECS_PER_DAY * 2:
1204 return locale.describe("day", sign, only_distance=only_distance)
1205 elif diff < self._SECS_PER_WEEK:
1206 days = sign * max(delta_second // self._SECS_PER_DAY, 2)
1207 return locale.describe("days", days, only_distance=only_distance)
1209 elif diff < self._SECS_PER_WEEK * 2:
1210 return locale.describe("week", sign, only_distance=only_distance)
1211 elif diff < self._SECS_PER_MONTH:
1212 weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2)
1213 return locale.describe("weeks", weeks, only_distance=only_distance)
1215 elif diff < self._SECS_PER_MONTH * 2:
1216 return locale.describe("month", sign, only_distance=only_distance)
1217 elif diff < self._SECS_PER_YEAR:
1218 # TODO revisit for humanization during leap years
1219 self_months = self._datetime.year * 12 + self._datetime.month
1220 other_months = dt.year * 12 + dt.month
1222 months = sign * max(abs(other_months - self_months), 2)
1224 return locale.describe(
1225 "months", months, only_distance=only_distance
1226 )
1228 elif diff < self._SECS_PER_YEAR * 2:
1229 return locale.describe("year", sign, only_distance=only_distance)
1230 else:
1231 years = sign * max(delta_second // self._SECS_PER_YEAR, 2)
1232 return locale.describe("years", years, only_distance=only_distance)
1234 elif isinstance(granularity, str):
1235 granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment]
1237 if granularity == "second":
1238 delta = sign * float(delta_second)
1239 if abs(delta) < 2:
1240 return locale.describe("now", only_distance=only_distance)
1241 elif granularity == "minute":
1242 delta = sign * delta_second / self._SECS_PER_MINUTE
1243 elif granularity == "hour":
1244 delta = sign * delta_second / self._SECS_PER_HOUR
1245 elif granularity == "day":
1246 delta = sign * delta_second / self._SECS_PER_DAY
1247 elif granularity == "week":
1248 delta = sign * delta_second / self._SECS_PER_WEEK
1249 elif granularity == "month":
1250 delta = sign * delta_second / self._SECS_PER_MONTH
1251 elif granularity == "quarter":
1252 delta = sign * delta_second / self._SECS_PER_QUARTER
1253 elif granularity == "year":
1254 delta = sign * delta_second / self._SECS_PER_YEAR
1255 else:
1256 raise ValueError(
1257 "Invalid level of granularity. "
1258 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
1259 )
1261 if trunc(abs(delta)) != 1:
1262 granularity += "s" # type: ignore[assignment]
1263 return locale.describe(granularity, delta, only_distance=only_distance)
1265 else:
1266 if not granularity:
1267 raise ValueError(
1268 "Empty granularity list provided. "
1269 "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'."
1270 )
1272 timeframes: List[Tuple[TimeFrameLiteral, float]] = []
1274 def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float:
1275 if _frame in granularity:
1276 value = sign * _delta / self._SECS_MAP[_frame]
1277 _delta %= self._SECS_MAP[_frame]
1278 if trunc(abs(value)) != 1:
1279 timeframes.append(
1280 (cast(TimeFrameLiteral, _frame + "s"), value)
1281 )
1282 else:
1283 timeframes.append((_frame, value))
1284 return _delta
1286 delta = float(delta_second)
1287 frames: Tuple[TimeFrameLiteral, ...] = (
1288 "year",
1289 "quarter",
1290 "month",
1291 "week",
1292 "day",
1293 "hour",
1294 "minute",
1295 "second",
1296 )
1297 for frame in frames:
1298 delta = gather_timeframes(delta, frame)
1300 if len(timeframes) < len(granularity):
1301 raise ValueError(
1302 "Invalid level of granularity. "
1303 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
1304 )
1306 return locale.describe_multi(timeframes, only_distance=only_distance)
1308 except KeyError as e:
1309 raise ValueError(
1310 f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. "
1311 "Please consider making a contribution to this locale."
1312 )
1314 def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow":
1315 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, that represents
1316 the time difference relative to the attributes of the
1317 :class:`Arrow <arrow.arrow.Arrow>` object.
1319 :param timestring: a ``str`` representing a humanized relative time.
1320 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'.
1322 Usage::
1324 >>> arw = arrow.utcnow()
1325 >>> arw
1326 <Arrow [2021-04-20T22:27:34.787885+00:00]>
1327 >>> earlier = arw.dehumanize("2 days ago")
1328 >>> earlier
1329 <Arrow [2021-04-18T22:27:34.787885+00:00]>
1331 >>> arw = arrow.utcnow()
1332 >>> arw
1333 <Arrow [2021-04-20T22:27:34.787885+00:00]>
1334 >>> later = arw.dehumanize("in a month")
1335 >>> later
1336 <Arrow [2021-05-18T22:27:34.787885+00:00]>
1338 """
1340 # Create a locale object based off given local
1341 locale_obj = locales.get_locale(locale)
1343 # Check to see if locale is supported
1344 normalized_locale_name = locale.lower().replace("_", "-")
1346 if normalized_locale_name not in DEHUMANIZE_LOCALES:
1347 raise ValueError(
1348 f"Dehumanize does not currently support the {locale} locale, please consider making a contribution to add support for this locale."
1349 )
1351 current_time = self.fromdatetime(self._datetime)
1353 # Create an object containing the relative time info
1354 time_object_info = dict.fromkeys(
1355 ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0
1356 )
1358 # Create an object representing if unit has been seen
1359 unit_visited = dict.fromkeys(
1360 ["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"],
1361 False,
1362 )
1364 # Create a regex pattern object for numbers
1365 num_pattern = re.compile(r"\d+")
1367 # Search input string for each time unit within locale
1368 for unit, unit_object in locale_obj.timeframes.items():
1369 # Need to check the type of unit_object to create the correct dictionary
1370 if isinstance(unit_object, Mapping):
1371 strings_to_search = unit_object
1372 else:
1373 strings_to_search = {unit: str(unit_object)}
1375 # Search for any matches that exist for that locale's unit.
1376 # Needs to cycle all through strings as some locales have strings that
1377 # could overlap in a regex match, since input validation isn't being performed.
1378 for time_delta, time_string in strings_to_search.items():
1379 # Replace {0} with regex \d representing digits
1380 search_string = str(time_string)
1381 search_string = search_string.format(r"\d+")
1383 # Create search pattern and find within string
1384 pattern = re.compile(rf"(^|\b|\d){search_string}")
1385 match = pattern.search(input_string)
1387 # If there is no match continue to next iteration
1388 if not match:
1389 continue
1391 match_string = match.group()
1392 num_match = num_pattern.search(match_string)
1394 # If no number matches
1395 # Need for absolute value as some locales have signs included in their objects
1396 if not num_match:
1397 change_value = (
1398 1 if not time_delta.isnumeric() else abs(int(time_delta))
1399 )
1400 else:
1401 change_value = int(num_match.group())
1403 # No time to update if now is the unit
1404 if unit == "now":
1405 unit_visited[unit] = True
1406 continue
1408 # Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds)
1409 time_unit_to_change = str(unit)
1410 time_unit_to_change += (
1411 "s" if (str(time_unit_to_change)[-1] != "s") else ""
1412 )
1413 time_object_info[time_unit_to_change] = change_value
1414 unit_visited[time_unit_to_change] = True
1416 # Assert error if string does not modify any units
1417 if not any([True for k, v in unit_visited.items() if v]):
1418 raise ValueError(
1419 "Input string not valid. Note: Some locales do not support the week granularity in Arrow. "
1420 "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error."
1421 )
1423 # Sign logic
1424 future_string = locale_obj.future
1425 future_string = future_string.format(".*")
1426 future_pattern = re.compile(rf"^{future_string}$")
1427 future_pattern_match = future_pattern.findall(input_string)
1429 past_string = locale_obj.past
1430 past_string = past_string.format(".*")
1431 past_pattern = re.compile(rf"^{past_string}$")
1432 past_pattern_match = past_pattern.findall(input_string)
1434 # If a string contains the now unit, there will be no relative units, hence the need to check if the now unit
1435 # was visited before raising a ValueError
1436 if past_pattern_match:
1437 sign_val = -1
1438 elif future_pattern_match:
1439 sign_val = 1
1440 elif unit_visited["now"]:
1441 sign_val = 0
1442 else:
1443 raise ValueError(
1444 "Invalid input String. String does not contain any relative time information. "
1445 "String should either represent a time in the future or a time in the past. "
1446 "Ex: 'in 5 seconds' or '5 seconds ago'."
1447 )
1449 time_changes = {k: sign_val * v for k, v in time_object_info.items()}
1451 return current_time.shift(check_imaginary=True, **time_changes)
1453 # query functions
1455 def is_between(
1456 self,
1457 start: "Arrow",
1458 end: "Arrow",
1459 bounds: _BOUNDS = "()",
1460 ) -> bool:
1461 """Returns a boolean denoting whether the :class:`Arrow <arrow.arrow.Arrow>` object is between
1462 the start and end limits.
1464 :param start: an :class:`Arrow <arrow.arrow.Arrow>` object.
1465 :param end: an :class:`Arrow <arrow.arrow.Arrow>` object.
1466 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
1467 whether to include or exclude the start and end values in the range. '(' excludes
1468 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
1469 If the bounds are not specified, the default bound '()' is used.
1471 Usage::
1473 >>> start = arrow.get(datetime(2013, 5, 5, 12, 30, 10))
1474 >>> end = arrow.get(datetime(2013, 5, 5, 12, 30, 36))
1475 >>> arrow.get(datetime(2013, 5, 5, 12, 30, 27)).is_between(start, end)
1476 True
1478 >>> start = arrow.get(datetime(2013, 5, 5))
1479 >>> end = arrow.get(datetime(2013, 5, 8))
1480 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[]')
1481 True
1483 >>> start = arrow.get(datetime(2013, 5, 5))
1484 >>> end = arrow.get(datetime(2013, 5, 8))
1485 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[)')
1486 False
1488 """
1490 util.validate_bounds(bounds)
1492 if not isinstance(start, Arrow):
1493 raise TypeError(
1494 f"Cannot parse start date argument type of {type(start)!r}."
1495 )
1497 if not isinstance(end, Arrow):
1498 raise TypeError(f"Cannot parse end date argument type of {type(start)!r}.")
1500 include_start = bounds[0] == "["
1501 include_end = bounds[1] == "]"
1503 target_ts = self.float_timestamp
1504 start_ts = start.float_timestamp
1505 end_ts = end.float_timestamp
1507 return (
1508 (start_ts <= target_ts <= end_ts)
1509 and (include_start or start_ts < target_ts)
1510 and (include_end or target_ts < end_ts)
1511 )
1513 # datetime methods
1515 def date(self) -> date:
1516 """Returns a ``date`` object with the same year, month and day.
1518 Usage::
1520 >>> arrow.utcnow().date()
1521 datetime.date(2019, 1, 23)
1523 """
1525 return self._datetime.date()
1527 def time(self) -> dt_time:
1528 """Returns a ``time`` object with the same hour, minute, second, microsecond.
1530 Usage::
1532 >>> arrow.utcnow().time()
1533 datetime.time(12, 15, 34, 68352)
1535 """
1537 return self._datetime.time()
1539 def timetz(self) -> dt_time:
1540 """Returns a ``time`` object with the same hour, minute, second, microsecond and
1541 tzinfo.
1543 Usage::
1545 >>> arrow.utcnow().timetz()
1546 datetime.time(12, 5, 18, 298893, tzinfo=tzutc())
1548 """
1550 return self._datetime.timetz()
1552 def astimezone(self, tz: Optional[dt_tzinfo]) -> dt_datetime:
1553 """Returns a ``datetime`` object, converted to the specified timezone.
1555 :param tz: a ``tzinfo`` object.
1557 Usage::
1559 >>> pacific=arrow.now('US/Pacific')
1560 >>> nyc=arrow.now('America/New_York').tzinfo
1561 >>> pacific.astimezone(nyc)
1562 datetime.datetime(2019, 1, 20, 10, 24, 22, 328172, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York'))
1564 """
1566 return self._datetime.astimezone(tz)
1568 def utcoffset(self) -> Optional[timedelta]:
1569 """Returns a ``timedelta`` object representing the whole number of minutes difference from
1570 UTC time.
1572 Usage::
1574 >>> arrow.now('US/Pacific').utcoffset()
1575 datetime.timedelta(-1, 57600)
1577 """
1579 return self._datetime.utcoffset()
1581 def dst(self) -> Optional[timedelta]:
1582 """Returns the daylight savings time adjustment.
1584 Usage::
1586 >>> arrow.utcnow().dst()
1587 datetime.timedelta(0)
1589 """
1591 return self._datetime.dst()
1593 def timetuple(self) -> struct_time:
1594 """Returns a ``time.struct_time``, in the current timezone.
1596 Usage::
1598 >>> arrow.utcnow().timetuple()
1599 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)
1601 """
1603 return self._datetime.timetuple()
1605 def utctimetuple(self) -> struct_time:
1606 """Returns a ``time.struct_time``, in UTC time.
1608 Usage::
1610 >>> arrow.utcnow().utctimetuple()
1611 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)
1613 """
1615 return self._datetime.utctimetuple()
1617 def toordinal(self) -> int:
1618 """Returns the proleptic Gregorian ordinal of the date.
1620 Usage::
1622 >>> arrow.utcnow().toordinal()
1623 737078
1625 """
1627 return self._datetime.toordinal()
1629 def weekday(self) -> int:
1630 """Returns the day of the week as an integer (0-6).
1632 Usage::
1634 >>> arrow.utcnow().weekday()
1635 5
1637 """
1639 return self._datetime.weekday()
1641 def isoweekday(self) -> int:
1642 """Returns the ISO day of the week as an integer (1-7).
1644 Usage::
1646 >>> arrow.utcnow().isoweekday()
1647 6
1649 """
1651 return self._datetime.isoweekday()
1653 def isocalendar(self) -> Tuple[int, int, int]:
1654 """Returns a 3-tuple, (ISO year, ISO week number, ISO weekday).
1656 Usage::
1658 >>> arrow.utcnow().isocalendar()
1659 (2019, 3, 6)
1661 """
1663 return self._datetime.isocalendar()
1665 def isoformat(self, sep: str = "T", timespec: str = "auto") -> str:
1666 """Returns an ISO 8601 formatted representation of the date and time.
1668 Usage::
1670 >>> arrow.utcnow().isoformat()
1671 '2019-01-19T18:30:52.442118+00:00'
1673 """
1675 return self._datetime.isoformat(sep, timespec)
1677 def ctime(self) -> str:
1678 """Returns a ctime formatted representation of the date and time.
1680 Usage::
1682 >>> arrow.utcnow().ctime()
1683 'Sat Jan 19 18:26:50 2019'
1685 """
1687 return self._datetime.ctime()
1689 def strftime(self, format: str) -> str:
1690 """Formats in the style of ``datetime.strftime``.
1692 :param format: the format string.
1694 Usage::
1696 >>> arrow.utcnow().strftime('%d-%m-%Y %H:%M:%S')
1697 '23-01-2019 12:28:17'
1699 """
1701 return self._datetime.strftime(format)
1703 def for_json(self) -> str:
1704 """Serializes for the ``for_json`` protocol of simplejson.
1706 Usage::
1708 >>> arrow.utcnow().for_json()
1709 '2019-01-19T18:25:36.760079+00:00'
1711 """
1713 return self.isoformat()
1715 # math
1717 def __add__(self, other: Any) -> "Arrow":
1718 if isinstance(other, (timedelta, relativedelta)):
1719 return self.fromdatetime(self._datetime + other, self._datetime.tzinfo)
1721 return NotImplemented
1723 def __radd__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
1724 return self.__add__(other)
1726 @overload
1727 def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
1728 pass # pragma: no cover
1730 @overload
1731 def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta:
1732 pass # pragma: no cover
1734 def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]:
1735 if isinstance(other, (timedelta, relativedelta)):
1736 return self.fromdatetime(self._datetime - other, self._datetime.tzinfo)
1738 elif isinstance(other, dt_datetime):
1739 return self._datetime - other
1741 elif isinstance(other, Arrow):
1742 return self._datetime - other._datetime
1744 return NotImplemented
1746 def __rsub__(self, other: Any) -> timedelta:
1747 if isinstance(other, dt_datetime):
1748 return other - self._datetime
1750 return NotImplemented
1752 # comparisons
1754 def __eq__(self, other: Any) -> bool:
1755 if not isinstance(other, (Arrow, dt_datetime)):
1756 return False
1758 return self._datetime == self._get_datetime(other)
1760 def __ne__(self, other: Any) -> bool:
1761 if not isinstance(other, (Arrow, dt_datetime)):
1762 return True
1764 return not self.__eq__(other)
1766 def __gt__(self, other: Any) -> bool:
1767 if not isinstance(other, (Arrow, dt_datetime)):
1768 return NotImplemented
1770 return self._datetime > self._get_datetime(other)
1772 def __ge__(self, other: Any) -> bool:
1773 if not isinstance(other, (Arrow, dt_datetime)):
1774 return NotImplemented
1776 return self._datetime >= self._get_datetime(other)
1778 def __lt__(self, other: Any) -> bool:
1779 if not isinstance(other, (Arrow, dt_datetime)):
1780 return NotImplemented
1782 return self._datetime < self._get_datetime(other)
1784 def __le__(self, other: Any) -> bool:
1785 if not isinstance(other, (Arrow, dt_datetime)):
1786 return NotImplemented
1788 return self._datetime <= self._get_datetime(other)
1790 # internal methods
1791 @staticmethod
1792 def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo:
1793 """Get normalized tzinfo object from various inputs."""
1794 if tz_expr is None:
1795 return dateutil_tz.tzutc()
1796 if isinstance(tz_expr, dt_tzinfo):
1797 return tz_expr
1798 else:
1799 try:
1800 return parser.TzinfoParser.parse(tz_expr)
1801 except parser.ParserError:
1802 raise ValueError(f"{tz_expr!r} not recognized as a timezone.")
1804 @classmethod
1805 def _get_datetime(
1806 cls, expr: Union["Arrow", dt_datetime, int, float, str]
1807 ) -> dt_datetime:
1808 """Get datetime object from a specified expression."""
1809 if isinstance(expr, Arrow):
1810 return expr.datetime
1811 elif isinstance(expr, dt_datetime):
1812 return expr
1813 elif util.is_timestamp(expr):
1814 timestamp = float(expr)
1815 return cls.utcfromtimestamp(timestamp).datetime
1816 else:
1817 raise ValueError(f"{expr!r} not recognized as a datetime or timestamp.")
1819 @classmethod
1820 def _get_frames(cls, name: _T_FRAMES) -> Tuple[str, str, int]:
1821 """Finds relevant timeframe and steps for use in range and span methods.
1823 Returns a 3 element tuple in the form (frame, plural frame, step), for example ("day", "days", 1)
1825 """
1826 if name in cls._ATTRS:
1827 return name, f"{name}s", 1
1828 elif name[-1] == "s" and name[:-1] in cls._ATTRS:
1829 return name[:-1], name, 1
1830 elif name in ["week", "weeks"]:
1831 return "week", "weeks", 1
1832 elif name in ["quarter", "quarters"]:
1833 return "quarter", "months", 3
1834 else:
1835 supported = ", ".join(
1836 [
1837 "year(s)",
1838 "month(s)",
1839 "day(s)",
1840 "hour(s)",
1841 "minute(s)",
1842 "second(s)",
1843 "microsecond(s)",
1844 "week(s)",
1845 "quarter(s)",
1846 ]
1847 )
1848 raise ValueError(
1849 f"Range or span over frame {name} not supported. Supported frames: {supported}."
1850 )
1852 @classmethod
1853 def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]:
1854 """Sets default end and limit values for range method."""
1855 if end is None:
1856 if limit is None:
1857 raise ValueError("One of 'end' or 'limit' is required.")
1859 return cls.max, limit
1861 else:
1862 if limit is None:
1863 return end, sys.maxsize
1864 return end, limit
1866 @staticmethod
1867 def _is_last_day_of_month(date: "Arrow") -> bool:
1868 """Returns a boolean indicating whether the datetime is the last day of the month."""
1869 return cast(int, date.day) == calendar.monthrange(date.year, date.month)[1]
1872Arrow.min = Arrow.fromdatetime(dt_datetime.min)
1873Arrow.max = Arrow.fromdatetime(dt_datetime.max)