Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/arrow/arrow.py: 26%
558 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:17 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:17 +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 # type: ignore[attr-defined]
172 ):
173 tzinfo = parser.TzinfoParser.parse(tzinfo.zone) # type: ignore[attr-defined]
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:
796 if len(formatstr) > 0:
797 return self.format(formatstr)
799 return str(self)
801 def __hash__(self) -> int:
802 return self._datetime.__hash__()
804 # attributes and properties
806 def __getattr__(self, name: str) -> int:
808 if name == "week":
809 return self.isocalendar()[1]
811 if name == "quarter":
812 return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1
814 if not name.startswith("_"):
815 value: Optional[int] = getattr(self._datetime, name, None)
817 if value is not None:
818 return value
820 return cast(int, object.__getattribute__(self, name))
822 @property
823 def tzinfo(self) -> dt_tzinfo:
824 """Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object.
826 Usage::
828 >>> arw=arrow.utcnow()
829 >>> arw.tzinfo
830 tzutc()
832 """
834 # In Arrow, `_datetime` cannot be naive.
835 return cast(dt_tzinfo, self._datetime.tzinfo)
837 @property
838 def datetime(self) -> dt_datetime:
839 """Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object.
841 Usage::
843 >>> arw=arrow.utcnow()
844 >>> arw.datetime
845 datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc())
847 """
849 return self._datetime
851 @property
852 def naive(self) -> dt_datetime:
853 """Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>`
854 object.
856 Usage::
858 >>> nairobi = arrow.now('Africa/Nairobi')
859 >>> nairobi
860 <Arrow [2019-01-23T19:27:12.297999+03:00]>
861 >>> nairobi.naive
862 datetime.datetime(2019, 1, 23, 19, 27, 12, 297999)
864 """
866 return self._datetime.replace(tzinfo=None)
868 def timestamp(self) -> float:
869 """Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
870 UTC time.
872 Usage::
874 >>> arrow.utcnow().timestamp()
875 1616882340.256501
877 """
879 return self._datetime.timestamp()
881 @property
882 def int_timestamp(self) -> int:
883 """Returns an integer timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
884 UTC time.
886 Usage::
888 >>> arrow.utcnow().int_timestamp
889 1548260567
891 """
893 return int(self.timestamp())
895 @property
896 def float_timestamp(self) -> float:
897 """Returns a floating-point timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>`
898 object, in UTC time.
900 Usage::
902 >>> arrow.utcnow().float_timestamp
903 1548260516.830896
905 """
907 return self.timestamp()
909 @property
910 def fold(self) -> int:
911 """Returns the ``fold`` value of the :class:`Arrow <arrow.arrow.Arrow>` object."""
913 return self._datetime.fold
915 @property
916 def ambiguous(self) -> bool:
917 """Indicates whether the :class:`Arrow <arrow.arrow.Arrow>` object is a repeated wall time in the current
918 timezone.
920 """
922 return dateutil_tz.datetime_ambiguous(self._datetime)
924 @property
925 def imaginary(self) -> bool:
926 """Indicates whether the :class: `Arrow <arrow.arrow.Arrow>` object exists in the current timezone."""
928 return not dateutil_tz.datetime_exists(self._datetime)
930 # mutation and duplication.
932 def clone(self) -> "Arrow":
933 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, cloned from the current one.
935 Usage:
937 >>> arw = arrow.utcnow()
938 >>> cloned = arw.clone()
940 """
942 return self.fromdatetime(self._datetime)
944 def replace(self, **kwargs: Any) -> "Arrow":
945 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
946 according to inputs.
948 Use property names to set their value absolutely::
950 >>> import arrow
951 >>> arw = arrow.utcnow()
952 >>> arw
953 <Arrow [2013-05-11T22:27:34.787885+00:00]>
954 >>> arw.replace(year=2014, month=6)
955 <Arrow [2014-06-11T22:27:34.787885+00:00]>
957 You can also replace the timezone without conversion, using a
958 :ref:`timezone expression <tz-expr>`::
960 >>> arw.replace(tzinfo=tz.tzlocal())
961 <Arrow [2013-05-11T22:27:34.787885-07:00]>
963 """
965 absolute_kwargs = {}
967 for key, value in kwargs.items():
969 if key in self._ATTRS:
970 absolute_kwargs[key] = value
971 elif key in ["week", "quarter"]:
972 raise ValueError(f"Setting absolute {key} is not supported.")
973 elif key not in ["tzinfo", "fold"]:
974 raise ValueError(f"Unknown attribute: {key!r}.")
976 current = self._datetime.replace(**absolute_kwargs)
978 tzinfo = kwargs.get("tzinfo")
980 if tzinfo is not None:
981 tzinfo = self._get_tzinfo(tzinfo)
982 current = current.replace(tzinfo=tzinfo)
984 fold = kwargs.get("fold")
986 if fold is not None:
987 current = current.replace(fold=fold)
989 return self.fromdatetime(current)
991 def shift(self, **kwargs: Any) -> "Arrow":
992 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
993 according to inputs.
995 Use pluralized property names to relatively shift their current value:
997 >>> import arrow
998 >>> arw = arrow.utcnow()
999 >>> arw
1000 <Arrow [2013-05-11T22:27:34.787885+00:00]>
1001 >>> arw.shift(years=1, months=-1)
1002 <Arrow [2014-04-11T22:27:34.787885+00:00]>
1004 Day-of-the-week relative shifting can use either Python's weekday numbers
1005 (Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's
1006 day instances (MO, TU .. SU). When using weekday numbers, the returned
1007 date will always be greater than or equal to the starting date.
1009 Using the above code (which is a Saturday) and asking it to shift to Saturday:
1011 >>> arw.shift(weekday=5)
1012 <Arrow [2013-05-11T22:27:34.787885+00:00]>
1014 While asking for a Monday:
1016 >>> arw.shift(weekday=0)
1017 <Arrow [2013-05-13T22:27:34.787885+00:00]>
1019 """
1021 relative_kwargs = {}
1022 additional_attrs = ["weeks", "quarters", "weekday"]
1024 for key, value in kwargs.items():
1026 if key in self._ATTRS_PLURAL or key in additional_attrs:
1027 relative_kwargs[key] = value
1028 else:
1029 supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs)
1030 raise ValueError(
1031 f"Invalid shift time frame. Please select one of the following: {supported_attr}."
1032 )
1034 # core datetime does not support quarters, translate to months.
1035 relative_kwargs.setdefault("months", 0)
1036 relative_kwargs["months"] += (
1037 relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER
1038 )
1040 current = self._datetime + relativedelta(**relative_kwargs)
1042 if not dateutil_tz.datetime_exists(current):
1043 current = dateutil_tz.resolve_imaginary(current)
1045 return self.fromdatetime(current)
1047 def to(self, tz: TZ_EXPR) -> "Arrow":
1048 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted
1049 to the target timezone.
1051 :param tz: A :ref:`timezone expression <tz-expr>`.
1053 Usage::
1055 >>> utc = arrow.utcnow()
1056 >>> utc
1057 <Arrow [2013-05-09T03:49:12.311072+00:00]>
1059 >>> utc.to('US/Pacific')
1060 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1062 >>> utc.to(tz.tzlocal())
1063 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1065 >>> utc.to('-07:00')
1066 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1068 >>> utc.to('local')
1069 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1071 >>> utc.to('local').to('utc')
1072 <Arrow [2013-05-09T03:49:12.311072+00:00]>
1074 """
1076 if not isinstance(tz, dt_tzinfo):
1077 tz = parser.TzinfoParser.parse(tz)
1079 dt = self._datetime.astimezone(tz)
1081 return self.__class__(
1082 dt.year,
1083 dt.month,
1084 dt.day,
1085 dt.hour,
1086 dt.minute,
1087 dt.second,
1088 dt.microsecond,
1089 dt.tzinfo,
1090 fold=getattr(dt, "fold", 0),
1091 )
1093 # string output and formatting
1095 def format(
1096 self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE
1097 ) -> str:
1098 """Returns a string representation of the :class:`Arrow <arrow.arrow.Arrow>` object,
1099 formatted according to the provided format string.
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.utcnow().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:
1267 if not granularity:
1268 raise ValueError(
1269 "Empty granularity list provided. "
1270 "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'."
1271 )
1273 timeframes: List[Tuple[TimeFrameLiteral, float]] = []
1275 def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float:
1276 if _frame in granularity:
1277 value = sign * _delta / self._SECS_MAP[_frame]
1278 _delta %= self._SECS_MAP[_frame]
1279 if trunc(abs(value)) != 1:
1280 timeframes.append(
1281 (cast(TimeFrameLiteral, _frame + "s"), value)
1282 )
1283 else:
1284 timeframes.append((_frame, value))
1285 return _delta
1287 delta = float(delta_second)
1288 frames: Tuple[TimeFrameLiteral, ...] = (
1289 "year",
1290 "quarter",
1291 "month",
1292 "week",
1293 "day",
1294 "hour",
1295 "minute",
1296 "second",
1297 )
1298 for frame in frames:
1299 delta = gather_timeframes(delta, frame)
1301 if len(timeframes) < len(granularity):
1302 raise ValueError(
1303 "Invalid level of granularity. "
1304 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
1305 )
1307 return locale.describe_multi(timeframes, only_distance=only_distance)
1309 except KeyError as e:
1310 raise ValueError(
1311 f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. "
1312 "Please consider making a contribution to this locale."
1313 )
1315 def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow":
1316 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, that represents
1317 the time difference relative to the attributes of the
1318 :class:`Arrow <arrow.arrow.Arrow>` object.
1320 :param timestring: a ``str`` representing a humanized relative time.
1321 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'.
1323 Usage::
1325 >>> arw = arrow.utcnow()
1326 >>> arw
1327 <Arrow [2021-04-20T22:27:34.787885+00:00]>
1328 >>> earlier = arw.dehumanize("2 days ago")
1329 >>> earlier
1330 <Arrow [2021-04-18T22:27:34.787885+00:00]>
1332 >>> arw = arrow.utcnow()
1333 >>> arw
1334 <Arrow [2021-04-20T22:27:34.787885+00:00]>
1335 >>> later = arw.dehumanize("in a month")
1336 >>> later
1337 <Arrow [2021-05-18T22:27:34.787885+00:00]>
1339 """
1341 # Create a locale object based off given local
1342 locale_obj = locales.get_locale(locale)
1344 # Check to see if locale is supported
1345 normalized_locale_name = locale.lower().replace("_", "-")
1347 if normalized_locale_name not in DEHUMANIZE_LOCALES:
1348 raise ValueError(
1349 f"Dehumanize does not currently support the {locale} locale, please consider making a contribution to add support for this locale."
1350 )
1352 current_time = self.fromdatetime(self._datetime)
1354 # Create an object containing the relative time info
1355 time_object_info = dict.fromkeys(
1356 ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0
1357 )
1359 # Create an object representing if unit has been seen
1360 unit_visited = dict.fromkeys(
1361 ["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"],
1362 False,
1363 )
1365 # Create a regex pattern object for numbers
1366 num_pattern = re.compile(r"\d+")
1368 # Search input string for each time unit within locale
1369 for unit, unit_object in locale_obj.timeframes.items():
1371 # Need to check the type of unit_object to create the correct dictionary
1372 if isinstance(unit_object, Mapping):
1373 strings_to_search = unit_object
1374 else:
1375 strings_to_search = {unit: str(unit_object)}
1377 # Search for any matches that exist for that locale's unit.
1378 # Needs to cycle all through strings as some locales have strings that
1379 # could overlap in a regex match, since input validation isn't being performed.
1380 for time_delta, time_string in strings_to_search.items():
1382 # Replace {0} with regex \d representing digits
1383 search_string = str(time_string)
1384 search_string = search_string.format(r"\d+")
1386 # Create search pattern and find within string
1387 pattern = re.compile(rf"(^|\b|\d){search_string}")
1388 match = pattern.search(input_string)
1390 # If there is no match continue to next iteration
1391 if not match:
1392 continue
1394 match_string = match.group()
1395 num_match = num_pattern.search(match_string)
1397 # If no number matches
1398 # Need for absolute value as some locales have signs included in their objects
1399 if not num_match:
1400 change_value = (
1401 1 if not time_delta.isnumeric() else abs(int(time_delta))
1402 )
1403 else:
1404 change_value = int(num_match.group())
1406 # No time to update if now is the unit
1407 if unit == "now":
1408 unit_visited[unit] = True
1409 continue
1411 # Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds)
1412 time_unit_to_change = str(unit)
1413 time_unit_to_change += (
1414 "s" if (str(time_unit_to_change)[-1] != "s") else ""
1415 )
1416 time_object_info[time_unit_to_change] = change_value
1417 unit_visited[time_unit_to_change] = True
1419 # Assert error if string does not modify any units
1420 if not any([True for k, v in unit_visited.items() if v]):
1421 raise ValueError(
1422 "Input string not valid. Note: Some locales do not support the week granularity in Arrow. "
1423 "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error."
1424 )
1426 # Sign logic
1427 future_string = locale_obj.future
1428 future_string = future_string.format(".*")
1429 future_pattern = re.compile(rf"^{future_string}$")
1430 future_pattern_match = future_pattern.findall(input_string)
1432 past_string = locale_obj.past
1433 past_string = past_string.format(".*")
1434 past_pattern = re.compile(rf"^{past_string}$")
1435 past_pattern_match = past_pattern.findall(input_string)
1437 # If a string contains the now unit, there will be no relative units, hence the need to check if the now unit
1438 # was visited before raising a ValueError
1439 if past_pattern_match:
1440 sign_val = -1
1441 elif future_pattern_match:
1442 sign_val = 1
1443 elif unit_visited["now"]:
1444 sign_val = 0
1445 else:
1446 raise ValueError(
1447 "Invalid input String. String does not contain any relative time information. "
1448 "String should either represent a time in the future or a time in the past. "
1449 "Ex: 'in 5 seconds' or '5 seconds ago'."
1450 )
1452 time_changes = {k: sign_val * v for k, v in time_object_info.items()}
1454 return current_time.shift(**time_changes)
1456 # query functions
1458 def is_between(
1459 self,
1460 start: "Arrow",
1461 end: "Arrow",
1462 bounds: _BOUNDS = "()",
1463 ) -> bool:
1464 """Returns a boolean denoting whether the :class:`Arrow <arrow.arrow.Arrow>` object is between
1465 the start and end limits.
1467 :param start: an :class:`Arrow <arrow.arrow.Arrow>` object.
1468 :param end: an :class:`Arrow <arrow.arrow.Arrow>` object.
1469 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
1470 whether to include or exclude the start and end values in the range. '(' excludes
1471 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
1472 If the bounds are not specified, the default bound '()' is used.
1474 Usage::
1476 >>> start = arrow.get(datetime(2013, 5, 5, 12, 30, 10))
1477 >>> end = arrow.get(datetime(2013, 5, 5, 12, 30, 36))
1478 >>> arrow.get(datetime(2013, 5, 5, 12, 30, 27)).is_between(start, end)
1479 True
1481 >>> start = arrow.get(datetime(2013, 5, 5))
1482 >>> end = arrow.get(datetime(2013, 5, 8))
1483 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[]')
1484 True
1486 >>> start = arrow.get(datetime(2013, 5, 5))
1487 >>> end = arrow.get(datetime(2013, 5, 8))
1488 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[)')
1489 False
1491 """
1493 util.validate_bounds(bounds)
1495 if not isinstance(start, Arrow):
1496 raise TypeError(
1497 f"Cannot parse start date argument type of {type(start)!r}."
1498 )
1500 if not isinstance(end, Arrow):
1501 raise TypeError(f"Cannot parse end date argument type of {type(start)!r}.")
1503 include_start = bounds[0] == "["
1504 include_end = bounds[1] == "]"
1506 target_ts = self.float_timestamp
1507 start_ts = start.float_timestamp
1508 end_ts = end.float_timestamp
1510 return (
1511 (start_ts <= target_ts <= end_ts)
1512 and (include_start or start_ts < target_ts)
1513 and (include_end or target_ts < end_ts)
1514 )
1516 # datetime methods
1518 def date(self) -> date:
1519 """Returns a ``date`` object with the same year, month and day.
1521 Usage::
1523 >>> arrow.utcnow().date()
1524 datetime.date(2019, 1, 23)
1526 """
1528 return self._datetime.date()
1530 def time(self) -> dt_time:
1531 """Returns a ``time`` object with the same hour, minute, second, microsecond.
1533 Usage::
1535 >>> arrow.utcnow().time()
1536 datetime.time(12, 15, 34, 68352)
1538 """
1540 return self._datetime.time()
1542 def timetz(self) -> dt_time:
1543 """Returns a ``time`` object with the same hour, minute, second, microsecond and
1544 tzinfo.
1546 Usage::
1548 >>> arrow.utcnow().timetz()
1549 datetime.time(12, 5, 18, 298893, tzinfo=tzutc())
1551 """
1553 return self._datetime.timetz()
1555 def astimezone(self, tz: Optional[dt_tzinfo]) -> dt_datetime:
1556 """Returns a ``datetime`` object, converted to the specified timezone.
1558 :param tz: a ``tzinfo`` object.
1560 Usage::
1562 >>> pacific=arrow.now('US/Pacific')
1563 >>> nyc=arrow.now('America/New_York').tzinfo
1564 >>> pacific.astimezone(nyc)
1565 datetime.datetime(2019, 1, 20, 10, 24, 22, 328172, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York'))
1567 """
1569 return self._datetime.astimezone(tz)
1571 def utcoffset(self) -> Optional[timedelta]:
1572 """Returns a ``timedelta`` object representing the whole number of minutes difference from
1573 UTC time.
1575 Usage::
1577 >>> arrow.now('US/Pacific').utcoffset()
1578 datetime.timedelta(-1, 57600)
1580 """
1582 return self._datetime.utcoffset()
1584 def dst(self) -> Optional[timedelta]:
1585 """Returns the daylight savings time adjustment.
1587 Usage::
1589 >>> arrow.utcnow().dst()
1590 datetime.timedelta(0)
1592 """
1594 return self._datetime.dst()
1596 def timetuple(self) -> struct_time:
1597 """Returns a ``time.struct_time``, in the current timezone.
1599 Usage::
1601 >>> arrow.utcnow().timetuple()
1602 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)
1604 """
1606 return self._datetime.timetuple()
1608 def utctimetuple(self) -> struct_time:
1609 """Returns a ``time.struct_time``, in UTC time.
1611 Usage::
1613 >>> arrow.utcnow().utctimetuple()
1614 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)
1616 """
1618 return self._datetime.utctimetuple()
1620 def toordinal(self) -> int:
1621 """Returns the proleptic Gregorian ordinal of the date.
1623 Usage::
1625 >>> arrow.utcnow().toordinal()
1626 737078
1628 """
1630 return self._datetime.toordinal()
1632 def weekday(self) -> int:
1633 """Returns the day of the week as an integer (0-6).
1635 Usage::
1637 >>> arrow.utcnow().weekday()
1638 5
1640 """
1642 return self._datetime.weekday()
1644 def isoweekday(self) -> int:
1645 """Returns the ISO day of the week as an integer (1-7).
1647 Usage::
1649 >>> arrow.utcnow().isoweekday()
1650 6
1652 """
1654 return self._datetime.isoweekday()
1656 def isocalendar(self) -> Tuple[int, int, int]:
1657 """Returns a 3-tuple, (ISO year, ISO week number, ISO weekday).
1659 Usage::
1661 >>> arrow.utcnow().isocalendar()
1662 (2019, 3, 6)
1664 """
1666 return self._datetime.isocalendar()
1668 def isoformat(self, sep: str = "T", timespec: str = "auto") -> str:
1669 """Returns an ISO 8601 formatted representation of the date and time.
1671 Usage::
1673 >>> arrow.utcnow().isoformat()
1674 '2019-01-19T18:30:52.442118+00:00'
1676 """
1678 return self._datetime.isoformat(sep, timespec)
1680 def ctime(self) -> str:
1681 """Returns a ctime formatted representation of the date and time.
1683 Usage::
1685 >>> arrow.utcnow().ctime()
1686 'Sat Jan 19 18:26:50 2019'
1688 """
1690 return self._datetime.ctime()
1692 def strftime(self, format: str) -> str:
1693 """Formats in the style of ``datetime.strftime``.
1695 :param format: the format string.
1697 Usage::
1699 >>> arrow.utcnow().strftime('%d-%m-%Y %H:%M:%S')
1700 '23-01-2019 12:28:17'
1702 """
1704 return self._datetime.strftime(format)
1706 def for_json(self) -> str:
1707 """Serializes for the ``for_json`` protocol of simplejson.
1709 Usage::
1711 >>> arrow.utcnow().for_json()
1712 '2019-01-19T18:25:36.760079+00:00'
1714 """
1716 return self.isoformat()
1718 # math
1720 def __add__(self, other: Any) -> "Arrow":
1722 if isinstance(other, (timedelta, relativedelta)):
1723 return self.fromdatetime(self._datetime + other, self._datetime.tzinfo)
1725 return NotImplemented
1727 def __radd__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
1728 return self.__add__(other)
1730 @overload
1731 def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
1732 pass # pragma: no cover
1734 @overload
1735 def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta:
1736 pass # pragma: no cover
1738 def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]:
1740 if isinstance(other, (timedelta, relativedelta)):
1741 return self.fromdatetime(self._datetime - other, self._datetime.tzinfo)
1743 elif isinstance(other, dt_datetime):
1744 return self._datetime - other
1746 elif isinstance(other, Arrow):
1747 return self._datetime - other._datetime
1749 return NotImplemented
1751 def __rsub__(self, other: Any) -> timedelta:
1753 if isinstance(other, dt_datetime):
1754 return other - self._datetime
1756 return NotImplemented
1758 # comparisons
1760 def __eq__(self, other: Any) -> bool:
1762 if not isinstance(other, (Arrow, dt_datetime)):
1763 return False
1765 return self._datetime == self._get_datetime(other)
1767 def __ne__(self, other: Any) -> bool:
1769 if not isinstance(other, (Arrow, dt_datetime)):
1770 return True
1772 return not self.__eq__(other)
1774 def __gt__(self, other: Any) -> bool:
1776 if not isinstance(other, (Arrow, dt_datetime)):
1777 return NotImplemented
1779 return self._datetime > self._get_datetime(other)
1781 def __ge__(self, other: Any) -> bool:
1783 if not isinstance(other, (Arrow, dt_datetime)):
1784 return NotImplemented
1786 return self._datetime >= self._get_datetime(other)
1788 def __lt__(self, other: Any) -> bool:
1790 if not isinstance(other, (Arrow, dt_datetime)):
1791 return NotImplemented
1793 return self._datetime < self._get_datetime(other)
1795 def __le__(self, other: Any) -> bool:
1797 if not isinstance(other, (Arrow, dt_datetime)):
1798 return NotImplemented
1800 return self._datetime <= self._get_datetime(other)
1802 # internal methods
1803 @staticmethod
1804 def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo:
1805 """Get normalized tzinfo object from various inputs."""
1806 if tz_expr is None:
1807 return dateutil_tz.tzutc()
1808 if isinstance(tz_expr, dt_tzinfo):
1809 return tz_expr
1810 else:
1811 try:
1812 return parser.TzinfoParser.parse(tz_expr)
1813 except parser.ParserError:
1814 raise ValueError(f"{tz_expr!r} not recognized as a timezone.")
1816 @classmethod
1817 def _get_datetime(
1818 cls, expr: Union["Arrow", dt_datetime, int, float, str]
1819 ) -> dt_datetime:
1820 """Get datetime object from a specified expression."""
1821 if isinstance(expr, Arrow):
1822 return expr.datetime
1823 elif isinstance(expr, dt_datetime):
1824 return expr
1825 elif util.is_timestamp(expr):
1826 timestamp = float(expr)
1827 return cls.utcfromtimestamp(timestamp).datetime
1828 else:
1829 raise ValueError(f"{expr!r} not recognized as a datetime or timestamp.")
1831 @classmethod
1832 def _get_frames(cls, name: _T_FRAMES) -> Tuple[str, str, int]:
1833 """Finds relevant timeframe and steps for use in range and span methods.
1835 Returns a 3 element tuple in the form (frame, plural frame, step), for example ("day", "days", 1)
1837 """
1838 if name in cls._ATTRS:
1839 return name, f"{name}s", 1
1840 elif name[-1] == "s" and name[:-1] in cls._ATTRS:
1841 return name[:-1], name, 1
1842 elif name in ["week", "weeks"]:
1843 return "week", "weeks", 1
1844 elif name in ["quarter", "quarters"]:
1845 return "quarter", "months", 3
1846 else:
1847 supported = ", ".join(
1848 [
1849 "year(s)",
1850 "month(s)",
1851 "day(s)",
1852 "hour(s)",
1853 "minute(s)",
1854 "second(s)",
1855 "microsecond(s)",
1856 "week(s)",
1857 "quarter(s)",
1858 ]
1859 )
1860 raise ValueError(
1861 f"Range or span over frame {name} not supported. Supported frames: {supported}."
1862 )
1864 @classmethod
1865 def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]:
1866 """Sets default end and limit values for range method."""
1867 if end is None:
1869 if limit is None:
1870 raise ValueError("One of 'end' or 'limit' is required.")
1872 return cls.max, limit
1874 else:
1875 if limit is None:
1876 return end, sys.maxsize
1877 return end, limit
1879 @staticmethod
1880 def _is_last_day_of_month(date: "Arrow") -> bool:
1881 """Returns a boolean indicating whether the datetime is the last day of the month."""
1882 return date.day == calendar.monthrange(date.year, date.month)[1]
1885Arrow.min = Arrow.fromdatetime(dt_datetime.min)
1886Arrow.max = Arrow.fromdatetime(dt_datetime.max)