Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/arrow/arrow.py: 26%
558 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:26 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:26 +0000
1"""
2Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime``
3replacement.
5"""
8import calendar
9import re
10import sys
11from datetime import date
12from datetime import datetime as dt_datetime
13from datetime import time as dt_time
14from datetime import timedelta
15from datetime import tzinfo as dt_tzinfo
16from math import trunc
17from time import struct_time
18from typing import (
19 Any,
20 ClassVar,
21 Generator,
22 Iterable,
23 List,
24 Mapping,
25 Optional,
26 Tuple,
27 Union,
28 cast,
29 overload,
30)
32from dateutil import tz as dateutil_tz
33from dateutil.relativedelta import relativedelta
35from arrow import formatter, locales, parser, util
36from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES
37from arrow.locales import TimeFrameLiteral
39if sys.version_info < (3, 8): # pragma: no cover
40 from typing_extensions import Final, Literal
41else:
42 from typing import Final, Literal # pragma: no cover
45TZ_EXPR = Union[dt_tzinfo, str]
47_T_FRAMES = Literal[
48 "year",
49 "years",
50 "month",
51 "months",
52 "day",
53 "days",
54 "hour",
55 "hours",
56 "minute",
57 "minutes",
58 "second",
59 "seconds",
60 "microsecond",
61 "microseconds",
62 "week",
63 "weeks",
64 "quarter",
65 "quarters",
66]
68_BOUNDS = Literal["[)", "()", "(]", "[]"]
70_GRANULARITY = Literal[
71 "auto",
72 "second",
73 "minute",
74 "hour",
75 "day",
76 "week",
77 "month",
78 "quarter",
79 "year",
80]
83class Arrow:
84 """An :class:`Arrow <arrow.arrow.Arrow>` object.
86 Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing
87 additional functionality.
89 :param year: the calendar year.
90 :param month: the calendar month.
91 :param day: the calendar day.
92 :param hour: (optional) the hour. Defaults to 0.
93 :param minute: (optional) the minute, Defaults to 0.
94 :param second: (optional) the second, Defaults to 0.
95 :param microsecond: (optional) the microsecond. Defaults to 0.
96 :param tzinfo: (optional) A timezone expression. Defaults to UTC.
97 :param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0.
99 .. _tz-expr:
101 Recognized timezone expressions:
103 - A ``tzinfo`` object.
104 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
105 - A ``str`` in ISO 8601 style, as in '+07:00'.
106 - A ``str``, one of the following: 'local', 'utc', 'UTC'.
108 Usage::
110 >>> import arrow
111 >>> arrow.Arrow(2013, 5, 5, 12, 30, 45)
112 <Arrow [2013-05-05T12:30:45+00:00]>
114 """
116 resolution: ClassVar[timedelta] = dt_datetime.resolution
117 min: ClassVar["Arrow"]
118 max: ClassVar["Arrow"]
120 _ATTRS: Final[List[str]] = [
121 "year",
122 "month",
123 "day",
124 "hour",
125 "minute",
126 "second",
127 "microsecond",
128 ]
129 _ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS]
130 _MONTHS_PER_QUARTER: Final[int] = 3
131 _SECS_PER_MINUTE: Final[int] = 60
132 _SECS_PER_HOUR: Final[int] = 60 * 60
133 _SECS_PER_DAY: Final[int] = 60 * 60 * 24
134 _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7
135 _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5
136 _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3
137 _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365
139 _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = {
140 "second": 1.0,
141 "minute": _SECS_PER_MINUTE,
142 "hour": _SECS_PER_HOUR,
143 "day": _SECS_PER_DAY,
144 "week": _SECS_PER_WEEK,
145 "month": _SECS_PER_MONTH,
146 "quarter": _SECS_PER_QUARTER,
147 "year": _SECS_PER_YEAR,
148 }
150 _datetime: dt_datetime
152 def __init__(
153 self,
154 year: int,
155 month: int,
156 day: int,
157 hour: int = 0,
158 minute: int = 0,
159 second: int = 0,
160 microsecond: int = 0,
161 tzinfo: Optional[TZ_EXPR] = None,
162 **kwargs: Any,
163 ) -> None:
164 if tzinfo is None:
165 tzinfo = dateutil_tz.tzutc()
166 # detect that tzinfo is a pytz object (issue #626)
167 elif (
168 isinstance(tzinfo, dt_tzinfo)
169 and hasattr(tzinfo, "localize")
170 and hasattr(tzinfo, "zone")
171 and tzinfo.zone
172 ):
173 tzinfo = parser.TzinfoParser.parse(tzinfo.zone)
174 elif isinstance(tzinfo, str):
175 tzinfo = parser.TzinfoParser.parse(tzinfo)
177 fold = kwargs.get("fold", 0)
179 self._datetime = dt_datetime(
180 year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold
181 )
183 # factories: single object, both original and from datetime.
185 @classmethod
186 def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow":
187 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given
188 timezone.
190 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
192 Usage::
194 >>> arrow.now('Asia/Baku')
195 <Arrow [2019-01-24T20:26:31.146412+04:00]>
197 """
199 if tzinfo is None:
200 tzinfo = dateutil_tz.tzlocal()
202 dt = dt_datetime.now(tzinfo)
204 return cls(
205 dt.year,
206 dt.month,
207 dt.day,
208 dt.hour,
209 dt.minute,
210 dt.second,
211 dt.microsecond,
212 dt.tzinfo,
213 fold=getattr(dt, "fold", 0),
214 )
216 @classmethod
217 def utcnow(cls) -> "Arrow":
218 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC
219 time.
221 Usage::
223 >>> arrow.utcnow()
224 <Arrow [2019-01-24T16:31:40.651108+00:00]>
226 """
228 dt = dt_datetime.now(dateutil_tz.tzutc())
230 return cls(
231 dt.year,
232 dt.month,
233 dt.day,
234 dt.hour,
235 dt.minute,
236 dt.second,
237 dt.microsecond,
238 dt.tzinfo,
239 fold=getattr(dt, "fold", 0),
240 )
242 @classmethod
243 def fromtimestamp(
244 cls,
245 timestamp: Union[int, float, str],
246 tzinfo: Optional[TZ_EXPR] = None,
247 ) -> "Arrow":
248 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, converted to
249 the given timezone.
251 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
252 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
254 """
256 if tzinfo is None:
257 tzinfo = dateutil_tz.tzlocal()
258 elif isinstance(tzinfo, str):
259 tzinfo = parser.TzinfoParser.parse(tzinfo)
261 if not util.is_timestamp(timestamp):
262 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")
264 timestamp = util.normalize_timestamp(float(timestamp))
265 dt = dt_datetime.fromtimestamp(timestamp, tzinfo)
267 return cls(
268 dt.year,
269 dt.month,
270 dt.day,
271 dt.hour,
272 dt.minute,
273 dt.second,
274 dt.microsecond,
275 dt.tzinfo,
276 fold=getattr(dt, "fold", 0),
277 )
279 @classmethod
280 def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow":
281 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, in UTC time.
283 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
285 """
287 if not util.is_timestamp(timestamp):
288 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")
290 timestamp = util.normalize_timestamp(float(timestamp))
291 dt = dt_datetime.utcfromtimestamp(timestamp)
293 return cls(
294 dt.year,
295 dt.month,
296 dt.day,
297 dt.hour,
298 dt.minute,
299 dt.second,
300 dt.microsecond,
301 dateutil_tz.tzutc(),
302 fold=getattr(dt, "fold", 0),
303 )
305 @classmethod
306 def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
307 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and
308 optional replacement timezone.
310 :param dt: the ``datetime``
311 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to ``dt``'s
312 timezone, or UTC if naive.
314 Usage::
316 >>> dt
317 datetime.datetime(2021, 4, 7, 13, 48, tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific'))
318 >>> arrow.Arrow.fromdatetime(dt)
319 <Arrow [2021-04-07T13:48:00-07:00]>
321 """
323 if tzinfo is None:
324 if dt.tzinfo is None:
325 tzinfo = dateutil_tz.tzutc()
326 else:
327 tzinfo = dt.tzinfo
329 return cls(
330 dt.year,
331 dt.month,
332 dt.day,
333 dt.hour,
334 dt.minute,
335 dt.second,
336 dt.microsecond,
337 tzinfo,
338 fold=getattr(dt, "fold", 0),
339 )
341 @classmethod
342 def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
343 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional
344 replacement timezone. All time values are set to 0.
346 :param date: the ``date``
347 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to UTC.
349 """
351 if tzinfo is None:
352 tzinfo = dateutil_tz.tzutc()
354 return cls(date.year, date.month, date.day, tzinfo=tzinfo)
356 @classmethod
357 def strptime(
358 cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None
359 ) -> "Arrow":
360 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a date string and format,
361 in the style of ``datetime.strptime``. Optionally replaces the parsed timezone.
363 :param date_str: the date string.
364 :param fmt: the format string using datetime format codes.
365 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to the parsed
366 timezone if ``fmt`` contains a timezone directive, otherwise UTC.
368 Usage::
370 >>> arrow.Arrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S')
371 <Arrow [2019-01-20T15:49:10+00:00]>
373 """
375 dt = dt_datetime.strptime(date_str, fmt)
376 if tzinfo is None:
377 tzinfo = dt.tzinfo
379 return cls(
380 dt.year,
381 dt.month,
382 dt.day,
383 dt.hour,
384 dt.minute,
385 dt.second,
386 dt.microsecond,
387 tzinfo,
388 fold=getattr(dt, "fold", 0),
389 )
391 @classmethod
392 def fromordinal(cls, ordinal: int) -> "Arrow":
393 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object corresponding
394 to the Gregorian Ordinal.
396 :param ordinal: an ``int`` corresponding to a Gregorian Ordinal.
398 Usage::
400 >>> arrow.fromordinal(737741)
401 <Arrow [2020-11-12T00:00:00+00:00]>
403 """
405 util.validate_ordinal(ordinal)
406 dt = dt_datetime.fromordinal(ordinal)
407 return cls(
408 dt.year,
409 dt.month,
410 dt.day,
411 dt.hour,
412 dt.minute,
413 dt.second,
414 dt.microsecond,
415 dt.tzinfo,
416 fold=getattr(dt, "fold", 0),
417 )
419 # factories: ranges and spans
421 @classmethod
422 def range(
423 cls,
424 frame: _T_FRAMES,
425 start: Union["Arrow", dt_datetime],
426 end: Union["Arrow", dt_datetime, None] = None,
427 tz: Optional[TZ_EXPR] = None,
428 limit: Optional[int] = None,
429 ) -> Generator["Arrow", None, None]:
430 """Returns an iterator of :class:`Arrow <arrow.arrow.Arrow>` objects, representing
431 points in time between two inputs.
433 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
434 :param start: A datetime expression, the start of the range.
435 :param end: (optional) A datetime expression, the end of the range.
436 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to
437 ``start``'s timezone, or UTC if ``start`` is naive.
438 :param limit: (optional) A maximum number of tuples to return.
440 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to
441 return the entire range. Call with ``limit`` alone to return a maximum # of results from
442 the start. Call with both to cap a range at a maximum # of results.
444 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
445 iterating. As such, either call with naive objects and ``tz``, or aware objects from the
446 same timezone and no ``tz``.
448 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.
450 Recognized datetime expressions:
452 - An :class:`Arrow <arrow.arrow.Arrow>` object.
453 - A ``datetime`` object.
455 Usage::
457 >>> start = datetime(2013, 5, 5, 12, 30)
458 >>> end = datetime(2013, 5, 5, 17, 15)
459 >>> for r in arrow.Arrow.range('hour', start, end):
460 ... print(repr(r))
461 ...
462 <Arrow [2013-05-05T12:30:00+00:00]>
463 <Arrow [2013-05-05T13:30:00+00:00]>
464 <Arrow [2013-05-05T14:30:00+00:00]>
465 <Arrow [2013-05-05T15:30:00+00:00]>
466 <Arrow [2013-05-05T16:30:00+00:00]>
468 **NOTE**: Unlike Python's ``range``, ``end`` *may* be included in the returned iterator::
470 >>> start = datetime(2013, 5, 5, 12, 30)
471 >>> end = datetime(2013, 5, 5, 13, 30)
472 >>> for r in arrow.Arrow.range('hour', start, end):
473 ... print(repr(r))
474 ...
475 <Arrow [2013-05-05T12:30:00+00:00]>
476 <Arrow [2013-05-05T13:30:00+00:00]>
478 """
480 _, frame_relative, relative_steps = cls._get_frames(frame)
482 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
484 start = cls._get_datetime(start).replace(tzinfo=tzinfo)
485 end, limit = cls._get_iteration_params(end, limit)
486 end = cls._get_datetime(end).replace(tzinfo=tzinfo)
488 current = cls.fromdatetime(start)
489 original_day = start.day
490 day_is_clipped = False
491 i = 0
493 while current <= end and i < limit:
494 i += 1
495 yield current
497 values = [getattr(current, f) for f in cls._ATTRS]
498 current = cls(*values, tzinfo=tzinfo).shift( # type: ignore[misc]
499 **{frame_relative: relative_steps}
500 )
502 if frame in ["month", "quarter", "year"] and current.day < original_day:
503 day_is_clipped = True
505 if day_is_clipped and not cls._is_last_day_of_month(current):
506 current = current.replace(day=original_day)
508 def span(
509 self,
510 frame: _T_FRAMES,
511 count: int = 1,
512 bounds: _BOUNDS = "[)",
513 exact: bool = False,
514 week_start: int = 1,
515 ) -> Tuple["Arrow", "Arrow"]:
516 """Returns a tuple of two new :class:`Arrow <arrow.arrow.Arrow>` objects, representing the timespan
517 of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
519 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
520 :param count: (optional) the number of frames to span.
521 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
522 whether to include or exclude the start and end values in the span. '(' excludes
523 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
524 If the bounds are not specified, the default bound '[)' is used.
525 :param exact: (optional) whether to have the start of the timespan begin exactly
526 at the time specified by ``start`` and the end of the timespan truncated
527 so as not to extend beyond ``end``.
528 :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
529 Monday is 1 and Sunday is 7.
531 Supported frame values: year, quarter, month, week, day, hour, minute, second.
533 Usage::
535 >>> arrow.utcnow()
536 <Arrow [2013-05-09T03:32:36.186203+00:00]>
538 >>> arrow.utcnow().span('hour')
539 (<Arrow [2013-05-09T03:00:00+00:00]>, <Arrow [2013-05-09T03:59:59.999999+00:00]>)
541 >>> arrow.utcnow().span('day')
542 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-09T23:59:59.999999+00:00]>)
544 >>> arrow.utcnow().span('day', count=2)
545 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T23:59:59.999999+00:00]>)
547 >>> arrow.utcnow().span('day', bounds='[]')
548 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T00:00:00+00:00]>)
550 >>> arrow.utcnow().span('week')
551 (<Arrow [2021-02-22T00:00:00+00:00]>, <Arrow [2021-02-28T23:59:59.999999+00:00]>)
553 >>> arrow.utcnow().span('week', week_start=6)
554 (<Arrow [2021-02-20T00:00:00+00:00]>, <Arrow [2021-02-26T23:59:59.999999+00:00]>)
556 """
557 if not 1 <= week_start <= 7:
558 raise ValueError("week_start argument must be between 1 and 7.")
560 util.validate_bounds(bounds)
562 frame_absolute, frame_relative, relative_steps = self._get_frames(frame)
564 if frame_absolute == "week":
565 attr = "day"
566 elif frame_absolute == "quarter":
567 attr = "month"
568 else:
569 attr = frame_absolute
571 floor = self
572 if not exact:
573 index = self._ATTRS.index(attr)
574 frames = self._ATTRS[: index + 1]
576 values = [getattr(self, f) for f in frames]
578 for _ in range(3 - len(values)):
579 values.append(1)
581 floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc]
583 if frame_absolute == "week":
584 # if week_start is greater than self.isoweekday() go back one week by setting delta = 7
585 delta = 7 if week_start > self.isoweekday() else 0
586 floor = floor.shift(days=-(self.isoweekday() - week_start) - delta)
587 elif frame_absolute == "quarter":
588 floor = floor.shift(months=-((self.month - 1) % 3))
590 ceil = floor.shift(**{frame_relative: count * relative_steps})
592 if bounds[0] == "(":
593 floor = floor.shift(microseconds=+1)
595 if bounds[1] == ")":
596 ceil = ceil.shift(microseconds=-1)
598 return floor, ceil
600 def floor(self, frame: _T_FRAMES) -> "Arrow":
601 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "floor"
602 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
603 Equivalent to the first element in the 2-tuple returned by
604 :func:`span <arrow.arrow.Arrow.span>`.
606 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
608 Usage::
610 >>> arrow.utcnow().floor('hour')
611 <Arrow [2013-05-09T03:00:00+00:00]>
613 """
615 return self.span(frame)[0]
617 def ceil(self, frame: _T_FRAMES) -> "Arrow":
618 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "ceiling"
619 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
620 Equivalent to the second element in the 2-tuple returned by
621 :func:`span <arrow.arrow.Arrow.span>`.
623 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
625 Usage::
627 >>> arrow.utcnow().ceil('hour')
628 <Arrow [2013-05-09T03:59:59.999999+00:00]>
630 """
632 return self.span(frame)[1]
634 @classmethod
635 def span_range(
636 cls,
637 frame: _T_FRAMES,
638 start: dt_datetime,
639 end: dt_datetime,
640 tz: Optional[TZ_EXPR] = None,
641 limit: Optional[int] = None,
642 bounds: _BOUNDS = "[)",
643 exact: bool = False,
644 ) -> Iterable[Tuple["Arrow", "Arrow"]]:
645 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
646 representing a series of timespans between two inputs.
648 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
649 :param start: A datetime expression, the start of the range.
650 :param end: (optional) A datetime expression, the end of the range.
651 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to
652 ``start``'s timezone, or UTC if ``start`` is naive.
653 :param limit: (optional) A maximum number of tuples to return.
654 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
655 whether to include or exclude the start and end values in each span in the range. '(' excludes
656 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
657 If the bounds are not specified, the default bound '[)' is used.
658 :param exact: (optional) whether to have the first timespan start exactly
659 at the time specified by ``start`` and the final span truncated
660 so as not to extend beyond ``end``.
662 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to
663 return the entire range. Call with ``limit`` alone to return a maximum # of results from
664 the start. Call with both to cap a range at a maximum # of results.
666 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
667 iterating. As such, either call with naive objects and ``tz``, or aware objects from the
668 same timezone and no ``tz``.
670 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.
672 Recognized datetime expressions:
674 - An :class:`Arrow <arrow.arrow.Arrow>` object.
675 - A ``datetime`` object.
677 **NOTE**: Unlike Python's ``range``, ``end`` will *always* be included in the returned
678 iterator of timespans.
680 Usage:
682 >>> start = datetime(2013, 5, 5, 12, 30)
683 >>> end = datetime(2013, 5, 5, 17, 15)
684 >>> for r in arrow.Arrow.span_range('hour', start, end):
685 ... print(r)
686 ...
687 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>)
688 (<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
689 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>)
690 (<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
691 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>)
692 (<Arrow [2013-05-05T17:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:00]>)
694 """
696 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
697 start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0]
698 end = cls.fromdatetime(end, tzinfo)
699 _range = cls.range(frame, start, end, tz, limit)
700 if not exact:
701 for r in _range:
702 yield r.span(frame, bounds=bounds, exact=exact)
704 for r in _range:
705 floor, ceil = r.span(frame, bounds=bounds, exact=exact)
706 if ceil > end:
707 ceil = end
708 if bounds[1] == ")":
709 ceil += relativedelta(microseconds=-1)
710 if floor == end:
711 break
712 elif floor + relativedelta(microseconds=-1) == end:
713 break
714 yield floor, ceil
716 @classmethod
717 def interval(
718 cls,
719 frame: _T_FRAMES,
720 start: dt_datetime,
721 end: dt_datetime,
722 interval: int = 1,
723 tz: Optional[TZ_EXPR] = None,
724 bounds: _BOUNDS = "[)",
725 exact: bool = False,
726 ) -> Iterable[Tuple["Arrow", "Arrow"]]:
727 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
728 representing a series of intervals between two inputs.
730 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
731 :param start: A datetime expression, the start of the range.
732 :param end: (optional) A datetime expression, the end of the range.
733 :param interval: (optional) Time interval for the given time frame.
734 :param tz: (optional) A timezone expression. Defaults to UTC.
735 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
736 whether to include or exclude the start and end values in the intervals. '(' excludes
737 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
738 If the bounds are not specified, the default bound '[)' is used.
739 :param exact: (optional) whether to have the first timespan start exactly
740 at the time specified by ``start`` and the final interval truncated
741 so as not to extend beyond ``end``.
743 Supported frame values: year, quarter, month, week, day, hour, minute, second
745 Recognized datetime expressions:
747 - An :class:`Arrow <arrow.arrow.Arrow>` object.
748 - A ``datetime`` object.
750 Recognized timezone expressions:
752 - A ``tzinfo`` object.
753 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
754 - A ``str`` in ISO 8601 style, as in '+07:00'.
755 - A ``str``, one of the following: 'local', 'utc', 'UTC'.
757 Usage:
759 >>> start = datetime(2013, 5, 5, 12, 30)
760 >>> end = datetime(2013, 5, 5, 17, 15)
761 >>> for r in arrow.Arrow.interval('hour', start, end, 2):
762 ... print(r)
763 ...
764 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
765 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
766 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:0]>)
767 """
768 if interval < 1:
769 raise ValueError("interval has to be a positive integer")
771 spanRange = iter(
772 cls.span_range(frame, start, end, tz, bounds=bounds, exact=exact)
773 )
774 while True:
775 try:
776 intvlStart, intvlEnd = next(spanRange)
777 for _ in range(interval - 1):
778 try:
779 _, intvlEnd = next(spanRange)
780 except StopIteration:
781 continue
782 yield intvlStart, intvlEnd
783 except StopIteration:
784 return
786 # representations
788 def __repr__(self) -> str:
789 return f"<{self.__class__.__name__} [{self.__str__()}]>"
791 def __str__(self) -> str:
792 return self._datetime.isoformat()
794 def __format__(self, formatstr: str) -> str:
795 if len(formatstr) > 0:
796 return self.format(formatstr)
798 return str(self)
800 def __hash__(self) -> int:
801 return self._datetime.__hash__()
803 # attributes and properties
805 def __getattr__(self, name: str) -> int:
806 if name == "week":
807 return self.isocalendar()[1]
809 if name == "quarter":
810 return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1
812 if not name.startswith("_"):
813 value: Optional[int] = getattr(self._datetime, name, None)
815 if value is not None:
816 return value
818 return cast(int, object.__getattribute__(self, name))
820 @property
821 def tzinfo(self) -> dt_tzinfo:
822 """Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object.
824 Usage::
826 >>> arw=arrow.utcnow()
827 >>> arw.tzinfo
828 tzutc()
830 """
832 # In Arrow, `_datetime` cannot be naive.
833 return cast(dt_tzinfo, self._datetime.tzinfo)
835 @property
836 def datetime(self) -> dt_datetime:
837 """Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object.
839 Usage::
841 >>> arw=arrow.utcnow()
842 >>> arw.datetime
843 datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc())
845 """
847 return self._datetime
849 @property
850 def naive(self) -> dt_datetime:
851 """Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>`
852 object.
854 Usage::
856 >>> nairobi = arrow.now('Africa/Nairobi')
857 >>> nairobi
858 <Arrow [2019-01-23T19:27:12.297999+03:00]>
859 >>> nairobi.naive
860 datetime.datetime(2019, 1, 23, 19, 27, 12, 297999)
862 """
864 return self._datetime.replace(tzinfo=None)
866 def timestamp(self) -> float:
867 """Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
868 UTC time.
870 Usage::
872 >>> arrow.utcnow().timestamp()
873 1616882340.256501
875 """
877 return self._datetime.timestamp()
879 @property
880 def int_timestamp(self) -> int:
881 """Returns an integer timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
882 UTC time.
884 Usage::
886 >>> arrow.utcnow().int_timestamp
887 1548260567
889 """
891 return int(self.timestamp())
893 @property
894 def float_timestamp(self) -> float:
895 """Returns a floating-point timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>`
896 object, in UTC time.
898 Usage::
900 >>> arrow.utcnow().float_timestamp
901 1548260516.830896
903 """
905 return self.timestamp()
907 @property
908 def fold(self) -> int:
909 """Returns the ``fold`` value of the :class:`Arrow <arrow.arrow.Arrow>` object."""
911 return self._datetime.fold
913 @property
914 def ambiguous(self) -> bool:
915 """Indicates whether the :class:`Arrow <arrow.arrow.Arrow>` object is a repeated wall time in the current
916 timezone.
918 """
920 return dateutil_tz.datetime_ambiguous(self._datetime)
922 @property
923 def imaginary(self) -> bool:
924 """Indicates whether the :class: `Arrow <arrow.arrow.Arrow>` object exists in the current timezone."""
926 return not dateutil_tz.datetime_exists(self._datetime)
928 # mutation and duplication.
930 def clone(self) -> "Arrow":
931 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, cloned from the current one.
933 Usage:
935 >>> arw = arrow.utcnow()
936 >>> cloned = arw.clone()
938 """
940 return self.fromdatetime(self._datetime)
942 def replace(self, **kwargs: Any) -> "Arrow":
943 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
944 according to inputs.
946 Use property names to set their value absolutely::
948 >>> import arrow
949 >>> arw = arrow.utcnow()
950 >>> arw
951 <Arrow [2013-05-11T22:27:34.787885+00:00]>
952 >>> arw.replace(year=2014, month=6)
953 <Arrow [2014-06-11T22:27:34.787885+00:00]>
955 You can also replace the timezone without conversion, using a
956 :ref:`timezone expression <tz-expr>`::
958 >>> arw.replace(tzinfo=tz.tzlocal())
959 <Arrow [2013-05-11T22:27:34.787885-07:00]>
961 """
963 absolute_kwargs = {}
965 for key, value in kwargs.items():
966 if key in self._ATTRS:
967 absolute_kwargs[key] = value
968 elif key in ["week", "quarter"]:
969 raise ValueError(f"Setting absolute {key} is not supported.")
970 elif key not in ["tzinfo", "fold"]:
971 raise ValueError(f"Unknown attribute: {key!r}.")
973 current = self._datetime.replace(**absolute_kwargs)
975 tzinfo = kwargs.get("tzinfo")
977 if tzinfo is not None:
978 tzinfo = self._get_tzinfo(tzinfo)
979 current = current.replace(tzinfo=tzinfo)
981 fold = kwargs.get("fold")
983 if fold is not None:
984 current = current.replace(fold=fold)
986 return self.fromdatetime(current)
988 def shift(self, **kwargs: Any) -> "Arrow":
989 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
990 according to inputs.
992 Use pluralized property names to relatively shift their current value:
994 >>> import arrow
995 >>> arw = arrow.utcnow()
996 >>> arw
997 <Arrow [2013-05-11T22:27:34.787885+00:00]>
998 >>> arw.shift(years=1, months=-1)
999 <Arrow [2014-04-11T22:27:34.787885+00:00]>
1001 Day-of-the-week relative shifting can use either Python's weekday numbers
1002 (Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's
1003 day instances (MO, TU .. SU). When using weekday numbers, the returned
1004 date will always be greater than or equal to the starting date.
1006 Using the above code (which is a Saturday) and asking it to shift to Saturday:
1008 >>> arw.shift(weekday=5)
1009 <Arrow [2013-05-11T22:27:34.787885+00:00]>
1011 While asking for a Monday:
1013 >>> arw.shift(weekday=0)
1014 <Arrow [2013-05-13T22:27:34.787885+00:00]>
1016 """
1018 relative_kwargs = {}
1019 additional_attrs = ["weeks", "quarters", "weekday"]
1021 for key, value in kwargs.items():
1022 if key in self._ATTRS_PLURAL or key in additional_attrs:
1023 relative_kwargs[key] = value
1024 else:
1025 supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs)
1026 raise ValueError(
1027 f"Invalid shift time frame. Please select one of the following: {supported_attr}."
1028 )
1030 # core datetime does not support quarters, translate to months.
1031 relative_kwargs.setdefault("months", 0)
1032 relative_kwargs["months"] += (
1033 relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER
1034 )
1036 current = self._datetime + relativedelta(**relative_kwargs)
1038 if not dateutil_tz.datetime_exists(current):
1039 current = dateutil_tz.resolve_imaginary(current)
1041 return self.fromdatetime(current)
1043 def to(self, tz: TZ_EXPR) -> "Arrow":
1044 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted
1045 to the target timezone.
1047 :param tz: A :ref:`timezone expression <tz-expr>`.
1049 Usage::
1051 >>> utc = arrow.utcnow()
1052 >>> utc
1053 <Arrow [2013-05-09T03:49:12.311072+00:00]>
1055 >>> utc.to('US/Pacific')
1056 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1058 >>> utc.to(tz.tzlocal())
1059 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1061 >>> utc.to('-07:00')
1062 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1064 >>> utc.to('local')
1065 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1067 >>> utc.to('local').to('utc')
1068 <Arrow [2013-05-09T03:49:12.311072+00:00]>
1070 """
1072 if not isinstance(tz, dt_tzinfo):
1073 tz = parser.TzinfoParser.parse(tz)
1075 dt = self._datetime.astimezone(tz)
1077 return self.__class__(
1078 dt.year,
1079 dt.month,
1080 dt.day,
1081 dt.hour,
1082 dt.minute,
1083 dt.second,
1084 dt.microsecond,
1085 dt.tzinfo,
1086 fold=getattr(dt, "fold", 0),
1087 )
1089 # string output and formatting
1091 def format(
1092 self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE
1093 ) -> str:
1094 """Returns a string representation of the :class:`Arrow <arrow.arrow.Arrow>` object,
1095 formatted according to the provided format string.
1097 :param fmt: the format string.
1098 :param locale: the locale to format.
1100 Usage::
1102 >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ')
1103 '2013-05-09 03:56:47 -00:00'
1105 >>> arrow.utcnow().format('X')
1106 '1368071882'
1108 >>> arrow.utcnow().format('MMMM DD, YYYY')
1109 'May 09, 2013'
1111 >>> arrow.utcnow().format()
1112 '2013-05-09 03:56:47 -00:00'
1114 """
1116 return formatter.DateTimeFormatter(locale).format(self._datetime, fmt)
1118 def humanize(
1119 self,
1120 other: Union["Arrow", dt_datetime, None] = None,
1121 locale: str = DEFAULT_LOCALE,
1122 only_distance: bool = False,
1123 granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto",
1124 ) -> str:
1125 """Returns a localized, humanized representation of a relative difference in time.
1127 :param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object.
1128 Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone.
1129 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'.
1130 :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part.
1131 :param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute',
1132 'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings
1134 Usage::
1136 >>> earlier = arrow.utcnow().shift(hours=-2)
1137 >>> earlier.humanize()
1138 '2 hours ago'
1140 >>> later = earlier.shift(hours=4)
1141 >>> later.humanize(earlier)
1142 'in 4 hours'
1144 """
1146 locale_name = locale
1147 locale = locales.get_locale(locale)
1149 if other is None:
1150 utc = dt_datetime.utcnow().replace(tzinfo=dateutil_tz.tzutc())
1151 dt = utc.astimezone(self._datetime.tzinfo)
1153 elif isinstance(other, Arrow):
1154 dt = other._datetime
1156 elif isinstance(other, dt_datetime):
1157 if other.tzinfo is None:
1158 dt = other.replace(tzinfo=self._datetime.tzinfo)
1159 else:
1160 dt = other.astimezone(self._datetime.tzinfo)
1162 else:
1163 raise TypeError(
1164 f"Invalid 'other' argument of type {type(other).__name__!r}. "
1165 "Argument must be of type None, Arrow, or datetime."
1166 )
1168 if isinstance(granularity, list) and len(granularity) == 1:
1169 granularity = granularity[0]
1171 _delta = int(round((self._datetime - dt).total_seconds()))
1172 sign = -1 if _delta < 0 else 1
1173 delta_second = diff = abs(_delta)
1175 try:
1176 if granularity == "auto":
1177 if diff < 10:
1178 return locale.describe("now", only_distance=only_distance)
1180 if diff < self._SECS_PER_MINUTE:
1181 seconds = sign * delta_second
1182 return locale.describe(
1183 "seconds", seconds, only_distance=only_distance
1184 )
1186 elif diff < self._SECS_PER_MINUTE * 2:
1187 return locale.describe("minute", sign, only_distance=only_distance)
1188 elif diff < self._SECS_PER_HOUR:
1189 minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2)
1190 return locale.describe(
1191 "minutes", minutes, only_distance=only_distance
1192 )
1194 elif diff < self._SECS_PER_HOUR * 2:
1195 return locale.describe("hour", sign, only_distance=only_distance)
1196 elif diff < self._SECS_PER_DAY:
1197 hours = sign * max(delta_second // self._SECS_PER_HOUR, 2)
1198 return locale.describe("hours", hours, only_distance=only_distance)
1199 elif diff < self._SECS_PER_DAY * 2:
1200 return locale.describe("day", sign, only_distance=only_distance)
1201 elif diff < self._SECS_PER_WEEK:
1202 days = sign * max(delta_second // self._SECS_PER_DAY, 2)
1203 return locale.describe("days", days, only_distance=only_distance)
1205 elif diff < self._SECS_PER_WEEK * 2:
1206 return locale.describe("week", sign, only_distance=only_distance)
1207 elif diff < self._SECS_PER_MONTH:
1208 weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2)
1209 return locale.describe("weeks", weeks, only_distance=only_distance)
1211 elif diff < self._SECS_PER_MONTH * 2:
1212 return locale.describe("month", sign, only_distance=only_distance)
1213 elif diff < self._SECS_PER_YEAR:
1214 # TODO revisit for humanization during leap years
1215 self_months = self._datetime.year * 12 + self._datetime.month
1216 other_months = dt.year * 12 + dt.month
1218 months = sign * max(abs(other_months - self_months), 2)
1220 return locale.describe(
1221 "months", months, only_distance=only_distance
1222 )
1224 elif diff < self._SECS_PER_YEAR * 2:
1225 return locale.describe("year", sign, only_distance=only_distance)
1226 else:
1227 years = sign * max(delta_second // self._SECS_PER_YEAR, 2)
1228 return locale.describe("years", years, only_distance=only_distance)
1230 elif isinstance(granularity, str):
1231 granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment]
1233 if granularity == "second":
1234 delta = sign * float(delta_second)
1235 if abs(delta) < 2:
1236 return locale.describe("now", only_distance=only_distance)
1237 elif granularity == "minute":
1238 delta = sign * delta_second / self._SECS_PER_MINUTE
1239 elif granularity == "hour":
1240 delta = sign * delta_second / self._SECS_PER_HOUR
1241 elif granularity == "day":
1242 delta = sign * delta_second / self._SECS_PER_DAY
1243 elif granularity == "week":
1244 delta = sign * delta_second / self._SECS_PER_WEEK
1245 elif granularity == "month":
1246 delta = sign * delta_second / self._SECS_PER_MONTH
1247 elif granularity == "quarter":
1248 delta = sign * delta_second / self._SECS_PER_QUARTER
1249 elif granularity == "year":
1250 delta = sign * delta_second / self._SECS_PER_YEAR
1251 else:
1252 raise ValueError(
1253 "Invalid level of granularity. "
1254 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
1255 )
1257 if trunc(abs(delta)) != 1:
1258 granularity += "s" # type: ignore[assignment]
1259 return locale.describe(granularity, delta, only_distance=only_distance)
1261 else:
1262 if not granularity:
1263 raise ValueError(
1264 "Empty granularity list provided. "
1265 "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'."
1266 )
1268 timeframes: List[Tuple[TimeFrameLiteral, float]] = []
1270 def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float:
1271 if _frame in granularity:
1272 value = sign * _delta / self._SECS_MAP[_frame]
1273 _delta %= self._SECS_MAP[_frame]
1274 if trunc(abs(value)) != 1:
1275 timeframes.append(
1276 (cast(TimeFrameLiteral, _frame + "s"), value)
1277 )
1278 else:
1279 timeframes.append((_frame, value))
1280 return _delta
1282 delta = float(delta_second)
1283 frames: Tuple[TimeFrameLiteral, ...] = (
1284 "year",
1285 "quarter",
1286 "month",
1287 "week",
1288 "day",
1289 "hour",
1290 "minute",
1291 "second",
1292 )
1293 for frame in frames:
1294 delta = gather_timeframes(delta, frame)
1296 if len(timeframes) < len(granularity):
1297 raise ValueError(
1298 "Invalid level of granularity. "
1299 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
1300 )
1302 return locale.describe_multi(timeframes, only_distance=only_distance)
1304 except KeyError as e:
1305 raise ValueError(
1306 f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. "
1307 "Please consider making a contribution to this locale."
1308 )
1310 def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow":
1311 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, that represents
1312 the time difference relative to the attributes of the
1313 :class:`Arrow <arrow.arrow.Arrow>` object.
1315 :param timestring: a ``str`` representing a humanized relative time.
1316 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'.
1318 Usage::
1320 >>> arw = arrow.utcnow()
1321 >>> arw
1322 <Arrow [2021-04-20T22:27:34.787885+00:00]>
1323 >>> earlier = arw.dehumanize("2 days ago")
1324 >>> earlier
1325 <Arrow [2021-04-18T22:27:34.787885+00:00]>
1327 >>> arw = arrow.utcnow()
1328 >>> arw
1329 <Arrow [2021-04-20T22:27:34.787885+00:00]>
1330 >>> later = arw.dehumanize("in a month")
1331 >>> later
1332 <Arrow [2021-05-18T22:27:34.787885+00:00]>
1334 """
1336 # Create a locale object based off given local
1337 locale_obj = locales.get_locale(locale)
1339 # Check to see if locale is supported
1340 normalized_locale_name = locale.lower().replace("_", "-")
1342 if normalized_locale_name not in DEHUMANIZE_LOCALES:
1343 raise ValueError(
1344 f"Dehumanize does not currently support the {locale} locale, please consider making a contribution to add support for this locale."
1345 )
1347 current_time = self.fromdatetime(self._datetime)
1349 # Create an object containing the relative time info
1350 time_object_info = dict.fromkeys(
1351 ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0
1352 )
1354 # Create an object representing if unit has been seen
1355 unit_visited = dict.fromkeys(
1356 ["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"],
1357 False,
1358 )
1360 # Create a regex pattern object for numbers
1361 num_pattern = re.compile(r"\d+")
1363 # Search input string for each time unit within locale
1364 for unit, unit_object in locale_obj.timeframes.items():
1365 # Need to check the type of unit_object to create the correct dictionary
1366 if isinstance(unit_object, Mapping):
1367 strings_to_search = unit_object
1368 else:
1369 strings_to_search = {unit: str(unit_object)}
1371 # Search for any matches that exist for that locale's unit.
1372 # Needs to cycle all through strings as some locales have strings that
1373 # could overlap in a regex match, since input validation isn't being performed.
1374 for time_delta, time_string in strings_to_search.items():
1375 # Replace {0} with regex \d representing digits
1376 search_string = str(time_string)
1377 search_string = search_string.format(r"\d+")
1379 # Create search pattern and find within string
1380 pattern = re.compile(rf"(^|\b|\d){search_string}")
1381 match = pattern.search(input_string)
1383 # If there is no match continue to next iteration
1384 if not match:
1385 continue
1387 match_string = match.group()
1388 num_match = num_pattern.search(match_string)
1390 # If no number matches
1391 # Need for absolute value as some locales have signs included in their objects
1392 if not num_match:
1393 change_value = (
1394 1 if not time_delta.isnumeric() else abs(int(time_delta))
1395 )
1396 else:
1397 change_value = int(num_match.group())
1399 # No time to update if now is the unit
1400 if unit == "now":
1401 unit_visited[unit] = True
1402 continue
1404 # Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds)
1405 time_unit_to_change = str(unit)
1406 time_unit_to_change += (
1407 "s" if (str(time_unit_to_change)[-1] != "s") else ""
1408 )
1409 time_object_info[time_unit_to_change] = change_value
1410 unit_visited[time_unit_to_change] = True
1412 # Assert error if string does not modify any units
1413 if not any([True for k, v in unit_visited.items() if v]):
1414 raise ValueError(
1415 "Input string not valid. Note: Some locales do not support the week granularity in Arrow. "
1416 "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error."
1417 )
1419 # Sign logic
1420 future_string = locale_obj.future
1421 future_string = future_string.format(".*")
1422 future_pattern = re.compile(rf"^{future_string}$")
1423 future_pattern_match = future_pattern.findall(input_string)
1425 past_string = locale_obj.past
1426 past_string = past_string.format(".*")
1427 past_pattern = re.compile(rf"^{past_string}$")
1428 past_pattern_match = past_pattern.findall(input_string)
1430 # If a string contains the now unit, there will be no relative units, hence the need to check if the now unit
1431 # was visited before raising a ValueError
1432 if past_pattern_match:
1433 sign_val = -1
1434 elif future_pattern_match:
1435 sign_val = 1
1436 elif unit_visited["now"]:
1437 sign_val = 0
1438 else:
1439 raise ValueError(
1440 "Invalid input String. String does not contain any relative time information. "
1441 "String should either represent a time in the future or a time in the past. "
1442 "Ex: 'in 5 seconds' or '5 seconds ago'."
1443 )
1445 time_changes = {k: sign_val * v for k, v in time_object_info.items()}
1447 return current_time.shift(**time_changes)
1449 # query functions
1451 def is_between(
1452 self,
1453 start: "Arrow",
1454 end: "Arrow",
1455 bounds: _BOUNDS = "()",
1456 ) -> bool:
1457 """Returns a boolean denoting whether the :class:`Arrow <arrow.arrow.Arrow>` object is between
1458 the start and end limits.
1460 :param start: an :class:`Arrow <arrow.arrow.Arrow>` object.
1461 :param end: an :class:`Arrow <arrow.arrow.Arrow>` object.
1462 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
1463 whether to include or exclude the start and end values in the range. '(' excludes
1464 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
1465 If the bounds are not specified, the default bound '()' is used.
1467 Usage::
1469 >>> start = arrow.get(datetime(2013, 5, 5, 12, 30, 10))
1470 >>> end = arrow.get(datetime(2013, 5, 5, 12, 30, 36))
1471 >>> arrow.get(datetime(2013, 5, 5, 12, 30, 27)).is_between(start, end)
1472 True
1474 >>> start = arrow.get(datetime(2013, 5, 5))
1475 >>> end = arrow.get(datetime(2013, 5, 8))
1476 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[]')
1477 True
1479 >>> start = arrow.get(datetime(2013, 5, 5))
1480 >>> end = arrow.get(datetime(2013, 5, 8))
1481 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[)')
1482 False
1484 """
1486 util.validate_bounds(bounds)
1488 if not isinstance(start, Arrow):
1489 raise TypeError(
1490 f"Cannot parse start date argument type of {type(start)!r}."
1491 )
1493 if not isinstance(end, Arrow):
1494 raise TypeError(f"Cannot parse end date argument type of {type(start)!r}.")
1496 include_start = bounds[0] == "["
1497 include_end = bounds[1] == "]"
1499 target_ts = self.float_timestamp
1500 start_ts = start.float_timestamp
1501 end_ts = end.float_timestamp
1503 return (
1504 (start_ts <= target_ts <= end_ts)
1505 and (include_start or start_ts < target_ts)
1506 and (include_end or target_ts < end_ts)
1507 )
1509 # datetime methods
1511 def date(self) -> date:
1512 """Returns a ``date`` object with the same year, month and day.
1514 Usage::
1516 >>> arrow.utcnow().date()
1517 datetime.date(2019, 1, 23)
1519 """
1521 return self._datetime.date()
1523 def time(self) -> dt_time:
1524 """Returns a ``time`` object with the same hour, minute, second, microsecond.
1526 Usage::
1528 >>> arrow.utcnow().time()
1529 datetime.time(12, 15, 34, 68352)
1531 """
1533 return self._datetime.time()
1535 def timetz(self) -> dt_time:
1536 """Returns a ``time`` object with the same hour, minute, second, microsecond and
1537 tzinfo.
1539 Usage::
1541 >>> arrow.utcnow().timetz()
1542 datetime.time(12, 5, 18, 298893, tzinfo=tzutc())
1544 """
1546 return self._datetime.timetz()
1548 def astimezone(self, tz: Optional[dt_tzinfo]) -> dt_datetime:
1549 """Returns a ``datetime`` object, converted to the specified timezone.
1551 :param tz: a ``tzinfo`` object.
1553 Usage::
1555 >>> pacific=arrow.now('US/Pacific')
1556 >>> nyc=arrow.now('America/New_York').tzinfo
1557 >>> pacific.astimezone(nyc)
1558 datetime.datetime(2019, 1, 20, 10, 24, 22, 328172, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York'))
1560 """
1562 return self._datetime.astimezone(tz)
1564 def utcoffset(self) -> Optional[timedelta]:
1565 """Returns a ``timedelta`` object representing the whole number of minutes difference from
1566 UTC time.
1568 Usage::
1570 >>> arrow.now('US/Pacific').utcoffset()
1571 datetime.timedelta(-1, 57600)
1573 """
1575 return self._datetime.utcoffset()
1577 def dst(self) -> Optional[timedelta]:
1578 """Returns the daylight savings time adjustment.
1580 Usage::
1582 >>> arrow.utcnow().dst()
1583 datetime.timedelta(0)
1585 """
1587 return self._datetime.dst()
1589 def timetuple(self) -> struct_time:
1590 """Returns a ``time.struct_time``, in the current timezone.
1592 Usage::
1594 >>> arrow.utcnow().timetuple()
1595 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)
1597 """
1599 return self._datetime.timetuple()
1601 def utctimetuple(self) -> struct_time:
1602 """Returns a ``time.struct_time``, in UTC time.
1604 Usage::
1606 >>> arrow.utcnow().utctimetuple()
1607 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)
1609 """
1611 return self._datetime.utctimetuple()
1613 def toordinal(self) -> int:
1614 """Returns the proleptic Gregorian ordinal of the date.
1616 Usage::
1618 >>> arrow.utcnow().toordinal()
1619 737078
1621 """
1623 return self._datetime.toordinal()
1625 def weekday(self) -> int:
1626 """Returns the day of the week as an integer (0-6).
1628 Usage::
1630 >>> arrow.utcnow().weekday()
1631 5
1633 """
1635 return self._datetime.weekday()
1637 def isoweekday(self) -> int:
1638 """Returns the ISO day of the week as an integer (1-7).
1640 Usage::
1642 >>> arrow.utcnow().isoweekday()
1643 6
1645 """
1647 return self._datetime.isoweekday()
1649 def isocalendar(self) -> Tuple[int, int, int]:
1650 """Returns a 3-tuple, (ISO year, ISO week number, ISO weekday).
1652 Usage::
1654 >>> arrow.utcnow().isocalendar()
1655 (2019, 3, 6)
1657 """
1659 return self._datetime.isocalendar()
1661 def isoformat(self, sep: str = "T", timespec: str = "auto") -> str:
1662 """Returns an ISO 8601 formatted representation of the date and time.
1664 Usage::
1666 >>> arrow.utcnow().isoformat()
1667 '2019-01-19T18:30:52.442118+00:00'
1669 """
1671 return self._datetime.isoformat(sep, timespec)
1673 def ctime(self) -> str:
1674 """Returns a ctime formatted representation of the date and time.
1676 Usage::
1678 >>> arrow.utcnow().ctime()
1679 'Sat Jan 19 18:26:50 2019'
1681 """
1683 return self._datetime.ctime()
1685 def strftime(self, format: str) -> str:
1686 """Formats in the style of ``datetime.strftime``.
1688 :param format: the format string.
1690 Usage::
1692 >>> arrow.utcnow().strftime('%d-%m-%Y %H:%M:%S')
1693 '23-01-2019 12:28:17'
1695 """
1697 return self._datetime.strftime(format)
1699 def for_json(self) -> str:
1700 """Serializes for the ``for_json`` protocol of simplejson.
1702 Usage::
1704 >>> arrow.utcnow().for_json()
1705 '2019-01-19T18:25:36.760079+00:00'
1707 """
1709 return self.isoformat()
1711 # math
1713 def __add__(self, other: Any) -> "Arrow":
1714 if isinstance(other, (timedelta, relativedelta)):
1715 return self.fromdatetime(self._datetime + other, self._datetime.tzinfo)
1717 return NotImplemented
1719 def __radd__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
1720 return self.__add__(other)
1722 @overload
1723 def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
1724 pass # pragma: no cover
1726 @overload
1727 def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta:
1728 pass # pragma: no cover
1730 def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]:
1731 if isinstance(other, (timedelta, relativedelta)):
1732 return self.fromdatetime(self._datetime - other, self._datetime.tzinfo)
1734 elif isinstance(other, dt_datetime):
1735 return self._datetime - other
1737 elif isinstance(other, Arrow):
1738 return self._datetime - other._datetime
1740 return NotImplemented
1742 def __rsub__(self, other: Any) -> timedelta:
1743 if isinstance(other, dt_datetime):
1744 return other - self._datetime
1746 return NotImplemented
1748 # comparisons
1750 def __eq__(self, other: Any) -> bool:
1751 if not isinstance(other, (Arrow, dt_datetime)):
1752 return False
1754 return self._datetime == self._get_datetime(other)
1756 def __ne__(self, other: Any) -> bool:
1757 if not isinstance(other, (Arrow, dt_datetime)):
1758 return True
1760 return not self.__eq__(other)
1762 def __gt__(self, other: Any) -> bool:
1763 if not isinstance(other, (Arrow, dt_datetime)):
1764 return NotImplemented
1766 return self._datetime > self._get_datetime(other)
1768 def __ge__(self, other: Any) -> bool:
1769 if not isinstance(other, (Arrow, dt_datetime)):
1770 return NotImplemented
1772 return self._datetime >= self._get_datetime(other)
1774 def __lt__(self, other: Any) -> bool:
1775 if not isinstance(other, (Arrow, dt_datetime)):
1776 return NotImplemented
1778 return self._datetime < self._get_datetime(other)
1780 def __le__(self, other: Any) -> bool:
1781 if not isinstance(other, (Arrow, dt_datetime)):
1782 return NotImplemented
1784 return self._datetime <= self._get_datetime(other)
1786 # internal methods
1787 @staticmethod
1788 def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo:
1789 """Get normalized tzinfo object from various inputs."""
1790 if tz_expr is None:
1791 return dateutil_tz.tzutc()
1792 if isinstance(tz_expr, dt_tzinfo):
1793 return tz_expr
1794 else:
1795 try:
1796 return parser.TzinfoParser.parse(tz_expr)
1797 except parser.ParserError:
1798 raise ValueError(f"{tz_expr!r} not recognized as a timezone.")
1800 @classmethod
1801 def _get_datetime(
1802 cls, expr: Union["Arrow", dt_datetime, int, float, str]
1803 ) -> dt_datetime:
1804 """Get datetime object from a specified expression."""
1805 if isinstance(expr, Arrow):
1806 return expr.datetime
1807 elif isinstance(expr, dt_datetime):
1808 return expr
1809 elif util.is_timestamp(expr):
1810 timestamp = float(expr)
1811 return cls.utcfromtimestamp(timestamp).datetime
1812 else:
1813 raise ValueError(f"{expr!r} not recognized as a datetime or timestamp.")
1815 @classmethod
1816 def _get_frames(cls, name: _T_FRAMES) -> Tuple[str, str, int]:
1817 """Finds relevant timeframe and steps for use in range and span methods.
1819 Returns a 3 element tuple in the form (frame, plural frame, step), for example ("day", "days", 1)
1821 """
1822 if name in cls._ATTRS:
1823 return name, f"{name}s", 1
1824 elif name[-1] == "s" and name[:-1] in cls._ATTRS:
1825 return name[:-1], name, 1
1826 elif name in ["week", "weeks"]:
1827 return "week", "weeks", 1
1828 elif name in ["quarter", "quarters"]:
1829 return "quarter", "months", 3
1830 else:
1831 supported = ", ".join(
1832 [
1833 "year(s)",
1834 "month(s)",
1835 "day(s)",
1836 "hour(s)",
1837 "minute(s)",
1838 "second(s)",
1839 "microsecond(s)",
1840 "week(s)",
1841 "quarter(s)",
1842 ]
1843 )
1844 raise ValueError(
1845 f"Range or span over frame {name} not supported. Supported frames: {supported}."
1846 )
1848 @classmethod
1849 def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]:
1850 """Sets default end and limit values for range method."""
1851 if end is None:
1852 if limit is None:
1853 raise ValueError("One of 'end' or 'limit' is required.")
1855 return cls.max, limit
1857 else:
1858 if limit is None:
1859 return end, sys.maxsize
1860 return end, limit
1862 @staticmethod
1863 def _is_last_day_of_month(date: "Arrow") -> bool:
1864 """Returns a boolean indicating whether the datetime is the last day of the month."""
1865 return date.day == calendar.monthrange(date.year, date.month)[1]
1868Arrow.min = Arrow.fromdatetime(dt_datetime.min)
1869Arrow.max = Arrow.fromdatetime(dt_datetime.max)