Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/arrow/arrow.py: 26%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime``
3replacement.
5"""
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, timezone
15from datetime import tzinfo as dt_tzinfo
16from math import trunc
17from time import struct_time
18from typing import (
19 Any,
20 ClassVar,
21 Final,
22 Generator,
23 Iterable,
24 List,
25 Literal,
26 Mapping,
27 Optional,
28 Tuple,
29 Union,
30 cast,
31 overload,
32)
34from dateutil import tz as dateutil_tz
35from dateutil.relativedelta import relativedelta
37from arrow import formatter, locales, parser, util
38from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES
39from arrow.locales import TimeFrameLiteral
41TZ_EXPR = Union[dt_tzinfo, str]
43_T_FRAMES = Literal[
44 "year",
45 "years",
46 "month",
47 "months",
48 "day",
49 "days",
50 "hour",
51 "hours",
52 "minute",
53 "minutes",
54 "second",
55 "seconds",
56 "microsecond",
57 "microseconds",
58 "week",
59 "weeks",
60 "quarter",
61 "quarters",
62]
64_BOUNDS = Literal["[)", "()", "(]", "[]"]
66_GRANULARITY = Literal[
67 "auto",
68 "second",
69 "minute",
70 "hour",
71 "day",
72 "week",
73 "month",
74 "quarter",
75 "year",
76]
79class Arrow:
80 """An :class:`Arrow <arrow.arrow.Arrow>` object.
82 Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing
83 additional functionality.
85 :param year: the calendar year.
86 :param month: the calendar month.
87 :param day: the calendar day.
88 :param hour: (optional) the hour. Defaults to 0.
89 :param minute: (optional) the minute, Defaults to 0.
90 :param second: (optional) the second, Defaults to 0.
91 :param microsecond: (optional) the microsecond. Defaults to 0.
92 :param tzinfo: (optional) A timezone expression. Defaults to UTC.
93 :param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0.
95 .. _tz-expr:
97 Recognized timezone expressions:
99 - A ``tzinfo`` object.
100 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
101 - A ``str`` in ISO 8601 style, as in '+07:00'.
102 - A ``str``, one of the following: 'local', 'utc', 'UTC'.
104 Usage::
106 >>> import arrow
107 >>> arrow.Arrow(2013, 5, 5, 12, 30, 45)
108 <Arrow [2013-05-05T12:30:45+00:00]>
110 """
112 resolution: ClassVar[timedelta] = dt_datetime.resolution
113 min: ClassVar["Arrow"]
114 max: ClassVar["Arrow"]
116 _ATTRS: Final[List[str]] = [
117 "year",
118 "month",
119 "day",
120 "hour",
121 "minute",
122 "second",
123 "microsecond",
124 ]
125 _ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS]
126 _MONTHS_PER_QUARTER: Final[int] = 3
127 _SECS_PER_MINUTE: Final[int] = 60
128 _SECS_PER_HOUR: Final[int] = 60 * 60
129 _SECS_PER_DAY: Final[int] = 60 * 60 * 24
130 _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7
131 _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5
132 _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3
133 _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365
135 _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = {
136 "second": 1.0,
137 "minute": _SECS_PER_MINUTE,
138 "hour": _SECS_PER_HOUR,
139 "day": _SECS_PER_DAY,
140 "week": _SECS_PER_WEEK,
141 "month": _SECS_PER_MONTH,
142 "quarter": _SECS_PER_QUARTER,
143 "year": _SECS_PER_YEAR,
144 }
146 _datetime: dt_datetime
148 def __init__(
149 self,
150 year: int,
151 month: int,
152 day: int,
153 hour: int = 0,
154 minute: int = 0,
155 second: int = 0,
156 microsecond: int = 0,
157 tzinfo: Optional[TZ_EXPR] = None,
158 **kwargs: Any,
159 ) -> None:
160 if tzinfo is None:
161 tzinfo = dateutil_tz.tzutc()
162 # detect that tzinfo is a pytz object (issue #626)
163 elif (
164 isinstance(tzinfo, dt_tzinfo)
165 and hasattr(tzinfo, "localize")
166 and hasattr(tzinfo, "zone")
167 and tzinfo.zone
168 ):
169 tzinfo = parser.TzinfoParser.parse(tzinfo.zone)
170 elif isinstance(tzinfo, str):
171 tzinfo = parser.TzinfoParser.parse(tzinfo)
173 fold = kwargs.get("fold", 0)
175 self._datetime = dt_datetime(
176 year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold
177 )
179 # factories: single object, both original and from datetime.
181 @classmethod
182 def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow":
183 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given
184 timezone.
186 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
188 Usage::
190 >>> arrow.now('Asia/Baku')
191 <Arrow [2019-01-24T20:26:31.146412+04:00]>
193 """
195 if tzinfo is None:
196 tzinfo = dateutil_tz.tzlocal()
198 dt = dt_datetime.now(tzinfo)
200 return cls(
201 dt.year,
202 dt.month,
203 dt.day,
204 dt.hour,
205 dt.minute,
206 dt.second,
207 dt.microsecond,
208 dt.tzinfo,
209 fold=getattr(dt, "fold", 0),
210 )
212 @classmethod
213 def utcnow(cls) -> "Arrow":
214 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC
215 time.
217 Usage::
219 >>> arrow.utcnow()
220 <Arrow [2019-01-24T16:31:40.651108+00:00]>
222 """
224 dt = dt_datetime.now(dateutil_tz.tzutc())
226 return cls(
227 dt.year,
228 dt.month,
229 dt.day,
230 dt.hour,
231 dt.minute,
232 dt.second,
233 dt.microsecond,
234 dt.tzinfo,
235 fold=getattr(dt, "fold", 0),
236 )
238 @classmethod
239 def fromtimestamp(
240 cls,
241 timestamp: Union[int, float, str],
242 tzinfo: Optional[TZ_EXPR] = None,
243 ) -> "Arrow":
244 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, converted to
245 the given timezone.
247 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
248 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
250 """
252 if tzinfo is None:
253 tzinfo = dateutil_tz.tzlocal()
254 elif isinstance(tzinfo, str):
255 tzinfo = parser.TzinfoParser.parse(tzinfo)
257 if not util.is_timestamp(timestamp):
258 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")
260 timestamp = util.normalize_timestamp(float(timestamp))
261 dt = dt_datetime.fromtimestamp(timestamp, tzinfo)
263 return cls(
264 dt.year,
265 dt.month,
266 dt.day,
267 dt.hour,
268 dt.minute,
269 dt.second,
270 dt.microsecond,
271 dt.tzinfo,
272 fold=getattr(dt, "fold", 0),
273 )
275 @classmethod
276 def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow":
277 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, in UTC time.
279 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
281 """
283 if not util.is_timestamp(timestamp):
284 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")
286 timestamp = util.normalize_timestamp(float(timestamp))
287 dt = dt_datetime.utcfromtimestamp(timestamp)
289 return cls(
290 dt.year,
291 dt.month,
292 dt.day,
293 dt.hour,
294 dt.minute,
295 dt.second,
296 dt.microsecond,
297 dateutil_tz.tzutc(),
298 fold=getattr(dt, "fold", 0),
299 )
301 @classmethod
302 def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
303 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and
304 optional replacement timezone.
306 :param dt: the ``datetime``
307 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to ``dt``'s
308 timezone, or UTC if naive.
310 Usage::
312 >>> dt
313 datetime.datetime(2021, 4, 7, 13, 48, tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific'))
314 >>> arrow.Arrow.fromdatetime(dt)
315 <Arrow [2021-04-07T13:48:00-07:00]>
317 """
319 if tzinfo is None:
320 if dt.tzinfo is None:
321 tzinfo = dateutil_tz.tzutc()
322 else:
323 tzinfo = dt.tzinfo
325 return cls(
326 dt.year,
327 dt.month,
328 dt.day,
329 dt.hour,
330 dt.minute,
331 dt.second,
332 dt.microsecond,
333 tzinfo,
334 fold=getattr(dt, "fold", 0),
335 )
337 @classmethod
338 def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
339 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional
340 replacement timezone. All time values are set to 0.
342 :param date: the ``date``
343 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to UTC.
345 """
347 if tzinfo is None:
348 tzinfo = dateutil_tz.tzutc()
350 return cls(date.year, date.month, date.day, tzinfo=tzinfo)
352 @classmethod
353 def strptime(
354 cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None
355 ) -> "Arrow":
356 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a date string and format,
357 in the style of ``datetime.strptime``. Optionally replaces the parsed timezone.
359 :param date_str: the date string.
360 :param fmt: the format string using datetime format codes.
361 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to the parsed
362 timezone if ``fmt`` contains a timezone directive, otherwise UTC.
364 Usage::
366 >>> arrow.Arrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S')
367 <Arrow [2019-01-20T15:49:10+00:00]>
369 """
371 dt = dt_datetime.strptime(date_str, fmt)
372 if tzinfo is None:
373 tzinfo = dt.tzinfo
375 return cls(
376 dt.year,
377 dt.month,
378 dt.day,
379 dt.hour,
380 dt.minute,
381 dt.second,
382 dt.microsecond,
383 tzinfo,
384 fold=getattr(dt, "fold", 0),
385 )
387 @classmethod
388 def fromordinal(cls, ordinal: int) -> "Arrow":
389 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object corresponding
390 to the Gregorian Ordinal.
392 :param ordinal: an ``int`` corresponding to a Gregorian Ordinal.
394 Usage::
396 >>> arrow.fromordinal(737741)
397 <Arrow [2020-11-12T00:00:00+00:00]>
399 """
401 util.validate_ordinal(ordinal)
402 dt = dt_datetime.fromordinal(ordinal)
403 return cls(
404 dt.year,
405 dt.month,
406 dt.day,
407 dt.hour,
408 dt.minute,
409 dt.second,
410 dt.microsecond,
411 dt.tzinfo,
412 fold=getattr(dt, "fold", 0),
413 )
415 # factories: ranges and spans
417 @classmethod
418 def range(
419 cls,
420 frame: _T_FRAMES,
421 start: Union["Arrow", dt_datetime],
422 end: Union["Arrow", dt_datetime, None] = None,
423 tz: Optional[TZ_EXPR] = None,
424 limit: Optional[int] = None,
425 ) -> Generator["Arrow", None, None]:
426 """Returns an iterator of :class:`Arrow <arrow.arrow.Arrow>` objects, representing
427 points in time between two inputs.
429 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
430 :param start: A datetime expression, the start of the range.
431 :param end: (optional) A datetime expression, the end of the range.
432 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to
433 ``start``'s timezone, or UTC if ``start`` is naive.
434 :param limit: (optional) A maximum number of tuples to return.
436 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to
437 return the entire range. Call with ``limit`` alone to return a maximum # of results from
438 the start. Call with both to cap a range at a maximum # of results.
440 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
441 iterating. As such, either call with naive objects and ``tz``, or aware objects from the
442 same timezone and no ``tz``.
444 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.
446 Recognized datetime expressions:
448 - An :class:`Arrow <arrow.arrow.Arrow>` object.
449 - A ``datetime`` object.
451 Usage::
453 >>> start = datetime(2013, 5, 5, 12, 30)
454 >>> end = datetime(2013, 5, 5, 17, 15)
455 >>> for r in arrow.Arrow.range('hour', start, end):
456 ... print(repr(r))
457 ...
458 <Arrow [2013-05-05T12:30:00+00:00]>
459 <Arrow [2013-05-05T13:30:00+00:00]>
460 <Arrow [2013-05-05T14:30:00+00:00]>
461 <Arrow [2013-05-05T15:30:00+00:00]>
462 <Arrow [2013-05-05T16:30:00+00:00]>
464 **NOTE**: Unlike Python's ``range``, ``end`` *may* be included in the returned iterator::
466 >>> start = datetime(2013, 5, 5, 12, 30)
467 >>> end = datetime(2013, 5, 5, 13, 30)
468 >>> for r in arrow.Arrow.range('hour', start, end):
469 ... print(repr(r))
470 ...
471 <Arrow [2013-05-05T12:30:00+00:00]>
472 <Arrow [2013-05-05T13:30:00+00:00]>
474 """
476 _, frame_relative, relative_steps = cls._get_frames(frame)
478 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
480 start = cls._get_datetime(start).replace(tzinfo=tzinfo)
481 end, limit = cls._get_iteration_params(end, limit)
482 end = cls._get_datetime(end).replace(tzinfo=tzinfo)
484 current = cls.fromdatetime(start)
485 original_day = start.day
486 day_is_clipped = False
487 i = 0
489 while current <= end and i < limit:
490 i += 1
491 yield current
493 values = [getattr(current, f) for f in cls._ATTRS]
494 current = cls(*values, tzinfo=tzinfo).shift( # type: ignore[misc]
495 **{frame_relative: relative_steps}
496 )
498 if frame in ["month", "quarter", "year"] and current.day < original_day:
499 day_is_clipped = True
501 if day_is_clipped and not cls._is_last_day_of_month(current):
502 current = current.replace(day=original_day)
504 def span(
505 self,
506 frame: _T_FRAMES,
507 count: int = 1,
508 bounds: _BOUNDS = "[)",
509 exact: bool = False,
510 week_start: int = 1,
511 ) -> Tuple["Arrow", "Arrow"]:
512 """Returns a tuple of two new :class:`Arrow <arrow.arrow.Arrow>` objects, representing the timespan
513 of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
515 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
516 :param count: (optional) the number of frames to span.
517 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
518 whether to include or exclude the start and end values in the span. '(' excludes
519 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
520 If the bounds are not specified, the default bound '[)' is used.
521 :param exact: (optional) whether to have the start of the timespan begin exactly
522 at the time specified by ``start`` and the end of the timespan truncated
523 so as not to extend beyond ``end``.
524 :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
525 Monday is 1 and Sunday is 7.
527 Supported frame values: year, quarter, month, week, day, hour, minute, second.
529 Usage::
531 >>> arrow.utcnow()
532 <Arrow [2013-05-09T03:32:36.186203+00:00]>
534 >>> arrow.utcnow().span('hour')
535 (<Arrow [2013-05-09T03:00:00+00:00]>, <Arrow [2013-05-09T03:59:59.999999+00:00]>)
537 >>> arrow.utcnow().span('day')
538 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-09T23:59:59.999999+00:00]>)
540 >>> arrow.utcnow().span('day', count=2)
541 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T23:59:59.999999+00:00]>)
543 >>> arrow.utcnow().span('day', bounds='[]')
544 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T00:00:00+00:00]>)
546 >>> arrow.utcnow().span('week')
547 (<Arrow [2021-02-22T00:00:00+00:00]>, <Arrow [2021-02-28T23:59:59.999999+00:00]>)
549 >>> arrow.utcnow().span('week', week_start=6)
550 (<Arrow [2021-02-20T00:00:00+00:00]>, <Arrow [2021-02-26T23:59:59.999999+00:00]>)
552 """
553 if not 1 <= week_start <= 7:
554 raise ValueError("week_start argument must be between 1 and 7.")
556 util.validate_bounds(bounds)
558 frame_absolute, frame_relative, relative_steps = self._get_frames(frame)
560 if frame_absolute == "week":
561 attr = "day"
562 elif frame_absolute == "quarter":
563 attr = "month"
564 else:
565 attr = frame_absolute
567 floor = self
568 if not exact:
569 index = self._ATTRS.index(attr)
570 frames = self._ATTRS[: index + 1]
572 values = [getattr(self, f) for f in frames]
574 for _ in range(3 - len(values)):
575 values.append(1)
577 floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc]
579 if frame_absolute == "week":
580 # if week_start is greater than self.isoweekday() go back one week by setting delta = 7
581 delta = 7 if week_start > self.isoweekday() else 0
582 floor = floor.shift(days=-(self.isoweekday() - week_start) - delta)
583 elif frame_absolute == "quarter":
584 floor = floor.shift(months=-((self.month - 1) % 3))
586 ceil = floor.shift(**{frame_relative: count * relative_steps})
588 if bounds[0] == "(":
589 floor = floor.shift(microseconds=+1)
591 if bounds[1] == ")":
592 ceil = ceil.shift(microseconds=-1)
594 return floor, ceil
596 def floor(self, frame: _T_FRAMES) -> "Arrow":
597 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "floor"
598 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
599 Equivalent to the first element in the 2-tuple returned by
600 :func:`span <arrow.arrow.Arrow.span>`.
602 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
604 Usage::
606 >>> arrow.utcnow().floor('hour')
607 <Arrow [2013-05-09T03:00:00+00:00]>
609 """
611 return self.span(frame)[0]
613 def ceil(self, frame: _T_FRAMES) -> "Arrow":
614 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "ceiling"
615 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
616 Equivalent to the second element in the 2-tuple returned by
617 :func:`span <arrow.arrow.Arrow.span>`.
619 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...).
621 Usage::
623 >>> arrow.utcnow().ceil('hour')
624 <Arrow [2013-05-09T03:59:59.999999+00:00]>
626 """
628 return self.span(frame)[1]
630 @classmethod
631 def span_range(
632 cls,
633 frame: _T_FRAMES,
634 start: dt_datetime,
635 end: dt_datetime,
636 tz: Optional[TZ_EXPR] = None,
637 limit: Optional[int] = None,
638 bounds: _BOUNDS = "[)",
639 exact: bool = False,
640 ) -> Iterable[Tuple["Arrow", "Arrow"]]:
641 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
642 representing a series of timespans between two inputs.
644 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
645 :param start: A datetime expression, the start of the range.
646 :param end: (optional) A datetime expression, the end of the range.
647 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to
648 ``start``'s timezone, or UTC if ``start`` is naive.
649 :param limit: (optional) A maximum number of tuples to return.
650 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
651 whether to include or exclude the start and end values in each span in the range. '(' excludes
652 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
653 If the bounds are not specified, the default bound '[)' is used.
654 :param exact: (optional) whether to have the first timespan start exactly
655 at the time specified by ``start`` and the final span truncated
656 so as not to extend beyond ``end``.
658 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to
659 return the entire range. Call with ``limit`` alone to return a maximum # of results from
660 the start. Call with both to cap a range at a maximum # of results.
662 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before
663 iterating. As such, either call with naive objects and ``tz``, or aware objects from the
664 same timezone and no ``tz``.
666 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond.
668 Recognized datetime expressions:
670 - An :class:`Arrow <arrow.arrow.Arrow>` object.
671 - A ``datetime`` object.
673 **NOTE**: Unlike Python's ``range``, ``end`` will *always* be included in the returned
674 iterator of timespans.
676 Usage:
678 >>> start = datetime(2013, 5, 5, 12, 30)
679 >>> end = datetime(2013, 5, 5, 17, 15)
680 >>> for r in arrow.Arrow.span_range('hour', start, end):
681 ... print(r)
682 ...
683 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>)
684 (<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
685 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>)
686 (<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
687 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>)
688 (<Arrow [2013-05-05T17:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:00]>)
690 """
692 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
693 start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0]
694 end = cls.fromdatetime(end, tzinfo)
695 _range = cls.range(frame, start, end, tz, limit)
696 if not exact:
697 for r in _range:
698 yield r.span(frame, bounds=bounds, exact=exact)
700 for r in _range:
701 floor, ceil = r.span(frame, bounds=bounds, exact=exact)
702 if ceil > end:
703 ceil = end
704 if bounds[1] == ")":
705 ceil += relativedelta(microseconds=-1)
706 if floor == end:
707 break
708 elif floor + relativedelta(microseconds=-1) == end:
709 break
710 yield floor, ceil
712 @classmethod
713 def interval(
714 cls,
715 frame: _T_FRAMES,
716 start: dt_datetime,
717 end: dt_datetime,
718 interval: int = 1,
719 tz: Optional[TZ_EXPR] = None,
720 bounds: _BOUNDS = "[)",
721 exact: bool = False,
722 ) -> Iterable[Tuple["Arrow", "Arrow"]]:
723 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
724 representing a series of intervals between two inputs.
726 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...).
727 :param start: A datetime expression, the start of the range.
728 :param end: (optional) A datetime expression, the end of the range.
729 :param interval: (optional) Time interval for the given time frame.
730 :param tz: (optional) A timezone expression. Defaults to UTC.
731 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
732 whether to include or exclude the start and end values in the intervals. '(' excludes
733 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
734 If the bounds are not specified, the default bound '[)' is used.
735 :param exact: (optional) whether to have the first timespan start exactly
736 at the time specified by ``start`` and the final interval truncated
737 so as not to extend beyond ``end``.
739 Supported frame values: year, quarter, month, week, day, hour, minute, second
741 Recognized datetime expressions:
743 - An :class:`Arrow <arrow.arrow.Arrow>` object.
744 - A ``datetime`` object.
746 Recognized timezone expressions:
748 - A ``tzinfo`` object.
749 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
750 - A ``str`` in ISO 8601 style, as in '+07:00'.
751 - A ``str``, one of the following: 'local', 'utc', 'UTC'.
753 Usage:
755 >>> start = datetime(2013, 5, 5, 12, 30)
756 >>> end = datetime(2013, 5, 5, 17, 15)
757 >>> for r in arrow.Arrow.interval('hour', start, end, 2):
758 ... print(r)
759 ...
760 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
761 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
762 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:0]>)
763 """
764 if interval < 1:
765 raise ValueError("interval has to be a positive integer")
767 spanRange = iter(
768 cls.span_range(frame, start, end, tz, bounds=bounds, exact=exact)
769 )
770 while True:
771 try:
772 intvlStart, intvlEnd = next(spanRange)
773 for _ in range(interval - 1):
774 try:
775 _, intvlEnd = next(spanRange)
776 except StopIteration:
777 continue
778 yield intvlStart, intvlEnd
779 except StopIteration:
780 return
782 # representations
784 def __repr__(self) -> str:
785 return f"<{self.__class__.__name__} [{self.__str__()}]>"
787 def __str__(self) -> str:
788 return self._datetime.isoformat()
790 def __format__(self, formatstr: str) -> str:
791 if len(formatstr) > 0:
792 return self.format(formatstr)
794 return str(self)
796 def __hash__(self) -> int:
797 return self._datetime.__hash__()
799 # attributes and properties
801 def __getattr__(self, name: str) -> int:
802 if name == "week":
803 return self.isocalendar()[1]
805 if name == "quarter":
806 return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1
808 if not name.startswith("_"):
809 value: Optional[int] = getattr(self._datetime, name, None)
811 if value is not None:
812 return value
814 return cast(int, object.__getattribute__(self, name))
816 @property
817 def tzinfo(self) -> dt_tzinfo:
818 """Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object.
820 Usage::
822 >>> arw=arrow.utcnow()
823 >>> arw.tzinfo
824 tzutc()
826 """
828 # In Arrow, `_datetime` cannot be naive.
829 return cast(dt_tzinfo, self._datetime.tzinfo)
831 @property
832 def datetime(self) -> dt_datetime:
833 """Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object.
835 Usage::
837 >>> arw=arrow.utcnow()
838 >>> arw.datetime
839 datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc())
841 """
843 return self._datetime
845 @property
846 def naive(self) -> dt_datetime:
847 """Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>`
848 object.
850 Usage::
852 >>> nairobi = arrow.now('Africa/Nairobi')
853 >>> nairobi
854 <Arrow [2019-01-23T19:27:12.297999+03:00]>
855 >>> nairobi.naive
856 datetime.datetime(2019, 1, 23, 19, 27, 12, 297999)
858 """
860 return self._datetime.replace(tzinfo=None)
862 def timestamp(self) -> float:
863 """Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
864 UTC time.
866 Usage::
868 >>> arrow.utcnow().timestamp()
869 1616882340.256501
871 """
873 return self._datetime.timestamp()
875 @property
876 def int_timestamp(self) -> int:
877 """Returns an integer timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in
878 UTC time.
880 Usage::
882 >>> arrow.utcnow().int_timestamp
883 1548260567
885 """
887 return int(self.timestamp())
889 @property
890 def float_timestamp(self) -> float:
891 """Returns a floating-point timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>`
892 object, in UTC time.
894 Usage::
896 >>> arrow.utcnow().float_timestamp
897 1548260516.830896
899 """
901 return self.timestamp()
903 @property
904 def fold(self) -> int:
905 """Returns the ``fold`` value of the :class:`Arrow <arrow.arrow.Arrow>` object."""
907 return self._datetime.fold
909 @property
910 def ambiguous(self) -> bool:
911 """Indicates whether the :class:`Arrow <arrow.arrow.Arrow>` object is a repeated wall time in the current
912 timezone.
914 """
916 return dateutil_tz.datetime_ambiguous(self._datetime)
918 @property
919 def imaginary(self) -> bool:
920 """Indicates whether the :class: `Arrow <arrow.arrow.Arrow>` object exists in the current timezone."""
922 return not dateutil_tz.datetime_exists(self._datetime)
924 # mutation and duplication.
926 def clone(self) -> "Arrow":
927 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, cloned from the current one.
929 Usage:
931 >>> arw = arrow.utcnow()
932 >>> cloned = arw.clone()
934 """
936 return self.fromdatetime(self._datetime)
938 def replace(self, **kwargs: Any) -> "Arrow":
939 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
940 according to inputs.
942 Use property names to set their value absolutely::
944 >>> import arrow
945 >>> arw = arrow.utcnow()
946 >>> arw
947 <Arrow [2013-05-11T22:27:34.787885+00:00]>
948 >>> arw.replace(year=2014, month=6)
949 <Arrow [2014-06-11T22:27:34.787885+00:00]>
951 You can also replace the timezone without conversion, using a
952 :ref:`timezone expression <tz-expr>`::
954 >>> arw.replace(tzinfo=tz.tzlocal())
955 <Arrow [2013-05-11T22:27:34.787885-07:00]>
957 """
959 absolute_kwargs = {}
961 for key, value in kwargs.items():
962 if key in self._ATTRS:
963 absolute_kwargs[key] = value
964 elif key in ["week", "quarter"]:
965 raise ValueError(f"Setting absolute {key} is not supported.")
966 elif key not in ["tzinfo", "fold"]:
967 raise ValueError(f"Unknown attribute: {key!r}.")
969 current = self._datetime.replace(**absolute_kwargs)
971 tzinfo = kwargs.get("tzinfo")
973 if tzinfo is not None:
974 tzinfo = self._get_tzinfo(tzinfo)
975 current = current.replace(tzinfo=tzinfo)
977 fold = kwargs.get("fold")
979 if fold is not None:
980 current = current.replace(fold=fold)
982 return self.fromdatetime(current)
984 def shift(self, **kwargs: Any) -> "Arrow":
985 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
986 according to inputs.
988 Use pluralized property names to relatively shift their current value:
990 >>> import arrow
991 >>> arw = arrow.utcnow()
992 >>> arw
993 <Arrow [2013-05-11T22:27:34.787885+00:00]>
994 >>> arw.shift(years=1, months=-1)
995 <Arrow [2014-04-11T22:27:34.787885+00:00]>
997 Day-of-the-week relative shifting can use either Python's weekday numbers
998 (Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's
999 day instances (MO, TU .. SU). When using weekday numbers, the returned
1000 date will always be greater than or equal to the starting date.
1002 Using the above code (which is a Saturday) and asking it to shift to Saturday:
1004 >>> arw.shift(weekday=5)
1005 <Arrow [2013-05-11T22:27:34.787885+00:00]>
1007 While asking for a Monday:
1009 >>> arw.shift(weekday=0)
1010 <Arrow [2013-05-13T22:27:34.787885+00:00]>
1012 """
1014 relative_kwargs = {}
1015 additional_attrs = ["weeks", "quarters", "weekday"]
1017 for key, value in kwargs.items():
1018 if key in self._ATTRS_PLURAL or key in additional_attrs:
1019 relative_kwargs[key] = value
1020 else:
1021 supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs)
1022 raise ValueError(
1023 f"Invalid shift time frame. Please select one of the following: {supported_attr}."
1024 )
1026 # core datetime does not support quarters, translate to months.
1027 relative_kwargs.setdefault("months", 0)
1028 relative_kwargs["months"] += (
1029 relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER
1030 )
1032 current = self._datetime + relativedelta(**relative_kwargs)
1034 if not dateutil_tz.datetime_exists(current):
1035 current = dateutil_tz.resolve_imaginary(current)
1037 return self.fromdatetime(current)
1039 def to(self, tz: TZ_EXPR) -> "Arrow":
1040 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted
1041 to the target timezone.
1043 :param tz: A :ref:`timezone expression <tz-expr>`.
1045 Usage::
1047 >>> utc = arrow.utcnow()
1048 >>> utc
1049 <Arrow [2013-05-09T03:49:12.311072+00:00]>
1051 >>> utc.to('US/Pacific')
1052 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1054 >>> utc.to(tz.tzlocal())
1055 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1057 >>> utc.to('-07:00')
1058 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1060 >>> utc.to('local')
1061 <Arrow [2013-05-08T20:49:12.311072-07:00]>
1063 >>> utc.to('local').to('utc')
1064 <Arrow [2013-05-09T03:49:12.311072+00:00]>
1066 """
1068 if not isinstance(tz, dt_tzinfo):
1069 tz = parser.TzinfoParser.parse(tz)
1071 dt = self._datetime.astimezone(tz)
1073 return self.__class__(
1074 dt.year,
1075 dt.month,
1076 dt.day,
1077 dt.hour,
1078 dt.minute,
1079 dt.second,
1080 dt.microsecond,
1081 dt.tzinfo,
1082 fold=getattr(dt, "fold", 0),
1083 )
1085 # string output and formatting
1087 def format(
1088 self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE
1089 ) -> str:
1090 """Returns a string representation of the :class:`Arrow <arrow.arrow.Arrow>` object,
1091 formatted according to the provided format string. For a list of formatting values,
1092 see :ref:`supported-tokens`
1094 :param fmt: the format string.
1095 :param locale: the locale to format.
1097 Usage::
1099 >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ')
1100 '2013-05-09 03:56:47 -00:00'
1102 >>> arrow.utcnow().format('X')
1103 '1368071882'
1105 >>> arrow.utcnow().format('MMMM DD, YYYY')
1106 'May 09, 2013'
1108 >>> arrow.utcnow().format()
1109 '2013-05-09 03:56:47 -00:00'
1111 """
1113 return formatter.DateTimeFormatter(locale).format(self._datetime, fmt)
1115 def humanize(
1116 self,
1117 other: Union["Arrow", dt_datetime, None] = None,
1118 locale: str = DEFAULT_LOCALE,
1119 only_distance: bool = False,
1120 granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto",
1121 ) -> str:
1122 """Returns a localized, humanized representation of a relative difference in time.
1124 :param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object.
1125 Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone.
1126 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'.
1127 :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part.
1128 :param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute',
1129 'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings
1131 Usage::
1133 >>> earlier = arrow.utcnow().shift(hours=-2)
1134 >>> earlier.humanize()
1135 '2 hours ago'
1137 >>> later = earlier.shift(hours=4)
1138 >>> later.humanize(earlier)
1139 'in 4 hours'
1141 """
1143 locale_name = locale
1144 locale = locales.get_locale(locale)
1146 if other is None:
1147 utc = dt_datetime.now(timezone.utc).replace(tzinfo=dateutil_tz.tzutc())
1148 dt = utc.astimezone(self._datetime.tzinfo)
1150 elif isinstance(other, Arrow):
1151 dt = other._datetime
1153 elif isinstance(other, dt_datetime):
1154 if other.tzinfo is None:
1155 dt = other.replace(tzinfo=self._datetime.tzinfo)
1156 else:
1157 dt = other.astimezone(self._datetime.tzinfo)
1159 else:
1160 raise TypeError(
1161 f"Invalid 'other' argument of type {type(other).__name__!r}. "
1162 "Argument must be of type None, Arrow, or datetime."
1163 )
1165 if isinstance(granularity, list) and len(granularity) == 1:
1166 granularity = granularity[0]
1168 _delta = int(round((self._datetime - dt).total_seconds()))
1169 sign = -1 if _delta < 0 else 1
1170 delta_second = diff = abs(_delta)
1172 try:
1173 if granularity == "auto":
1174 if diff < 10:
1175 return locale.describe("now", only_distance=only_distance)
1177 if diff < self._SECS_PER_MINUTE:
1178 seconds = sign * delta_second
1179 return locale.describe(
1180 "seconds", seconds, only_distance=only_distance
1181 )
1183 elif diff < self._SECS_PER_MINUTE * 2:
1184 return locale.describe("minute", sign, only_distance=only_distance)
1185 elif diff < self._SECS_PER_HOUR:
1186 minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2)
1187 return locale.describe(
1188 "minutes", minutes, only_distance=only_distance
1189 )
1191 elif diff < self._SECS_PER_HOUR * 2:
1192 return locale.describe("hour", sign, only_distance=only_distance)
1193 elif diff < self._SECS_PER_DAY:
1194 hours = sign * max(delta_second // self._SECS_PER_HOUR, 2)
1195 return locale.describe("hours", hours, only_distance=only_distance)
1196 elif diff < self._SECS_PER_DAY * 2:
1197 return locale.describe("day", sign, only_distance=only_distance)
1198 elif diff < self._SECS_PER_WEEK:
1199 days = sign * max(delta_second // self._SECS_PER_DAY, 2)
1200 return locale.describe("days", days, only_distance=only_distance)
1202 elif diff < self._SECS_PER_WEEK * 2:
1203 return locale.describe("week", sign, only_distance=only_distance)
1204 elif diff < self._SECS_PER_MONTH:
1205 weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2)
1206 return locale.describe("weeks", weeks, only_distance=only_distance)
1208 elif diff < self._SECS_PER_MONTH * 2:
1209 return locale.describe("month", sign, only_distance=only_distance)
1210 elif diff < self._SECS_PER_YEAR:
1211 # TODO revisit for humanization during leap years
1212 self_months = self._datetime.year * 12 + self._datetime.month
1213 other_months = dt.year * 12 + dt.month
1215 months = sign * max(abs(other_months - self_months), 2)
1217 return locale.describe(
1218 "months", months, only_distance=only_distance
1219 )
1221 elif diff < self._SECS_PER_YEAR * 2:
1222 return locale.describe("year", sign, only_distance=only_distance)
1223 else:
1224 years = sign * max(delta_second // self._SECS_PER_YEAR, 2)
1225 return locale.describe("years", years, only_distance=only_distance)
1227 elif isinstance(granularity, str):
1228 granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment]
1230 if granularity == "second":
1231 delta = sign * float(delta_second)
1232 if abs(delta) < 2:
1233 return locale.describe("now", only_distance=only_distance)
1234 elif granularity == "minute":
1235 delta = sign * delta_second / self._SECS_PER_MINUTE
1236 elif granularity == "hour":
1237 delta = sign * delta_second / self._SECS_PER_HOUR
1238 elif granularity == "day":
1239 delta = sign * delta_second / self._SECS_PER_DAY
1240 elif granularity == "week":
1241 delta = sign * delta_second / self._SECS_PER_WEEK
1242 elif granularity == "month":
1243 delta = sign * delta_second / self._SECS_PER_MONTH
1244 elif granularity == "quarter":
1245 delta = sign * delta_second / self._SECS_PER_QUARTER
1246 elif granularity == "year":
1247 delta = sign * delta_second / self._SECS_PER_YEAR
1248 else:
1249 raise ValueError(
1250 "Invalid level of granularity. "
1251 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
1252 )
1254 if trunc(abs(delta)) != 1:
1255 granularity += "s" # type: ignore[assignment]
1256 return locale.describe(granularity, delta, only_distance=only_distance)
1258 else:
1259 if not granularity:
1260 raise ValueError(
1261 "Empty granularity list provided. "
1262 "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'."
1263 )
1265 timeframes: List[Tuple[TimeFrameLiteral, float]] = []
1267 def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float:
1268 if _frame in granularity:
1269 value = sign * _delta / self._SECS_MAP[_frame]
1270 _delta %= self._SECS_MAP[_frame]
1271 if trunc(abs(value)) != 1:
1272 timeframes.append(
1273 (cast(TimeFrameLiteral, _frame + "s"), value)
1274 )
1275 else:
1276 timeframes.append((_frame, value))
1277 return _delta
1279 delta = float(delta_second)
1280 frames: Tuple[TimeFrameLiteral, ...] = (
1281 "year",
1282 "quarter",
1283 "month",
1284 "week",
1285 "day",
1286 "hour",
1287 "minute",
1288 "second",
1289 )
1290 for frame in frames:
1291 delta = gather_timeframes(delta, frame)
1293 if len(timeframes) < len(granularity):
1294 raise ValueError(
1295 "Invalid level of granularity. "
1296 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'."
1297 )
1299 return locale.describe_multi(timeframes, only_distance=only_distance)
1301 except KeyError as e:
1302 raise ValueError(
1303 f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. "
1304 "Please consider making a contribution to this locale."
1305 )
1307 def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow":
1308 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, that represents
1309 the time difference relative to the attributes of the
1310 :class:`Arrow <arrow.arrow.Arrow>` object.
1312 :param timestring: a ``str`` representing a humanized relative time.
1313 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'.
1315 Usage::
1317 >>> arw = arrow.utcnow()
1318 >>> arw
1319 <Arrow [2021-04-20T22:27:34.787885+00:00]>
1320 >>> earlier = arw.dehumanize("2 days ago")
1321 >>> earlier
1322 <Arrow [2021-04-18T22:27:34.787885+00:00]>
1324 >>> arw = arrow.utcnow()
1325 >>> arw
1326 <Arrow [2021-04-20T22:27:34.787885+00:00]>
1327 >>> later = arw.dehumanize("in a month")
1328 >>> later
1329 <Arrow [2021-05-18T22:27:34.787885+00:00]>
1331 """
1333 # Create a locale object based off given local
1334 locale_obj = locales.get_locale(locale)
1336 # Check to see if locale is supported
1337 normalized_locale_name = locale.lower().replace("_", "-")
1339 if normalized_locale_name not in DEHUMANIZE_LOCALES:
1340 raise ValueError(
1341 f"Dehumanize does not currently support the {locale} locale, please consider making a contribution to add support for this locale."
1342 )
1344 current_time = self.fromdatetime(self._datetime)
1346 # Create an object containing the relative time info
1347 time_object_info = dict.fromkeys(
1348 ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0
1349 )
1351 # Create an object representing if unit has been seen
1352 unit_visited = dict.fromkeys(
1353 ["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"],
1354 False,
1355 )
1357 # Create a regex pattern object for numbers
1358 num_pattern = re.compile(r"\d+")
1360 # Search input string for each time unit within locale
1361 for unit, unit_object in locale_obj.timeframes.items():
1362 # Need to check the type of unit_object to create the correct dictionary
1363 if isinstance(unit_object, Mapping):
1364 strings_to_search = unit_object
1365 else:
1366 strings_to_search = {unit: str(unit_object)}
1368 # Search for any matches that exist for that locale's unit.
1369 # Needs to cycle all through strings as some locales have strings that
1370 # could overlap in a regex match, since input validation isn't being performed.
1371 for time_delta, time_string in strings_to_search.items():
1372 # Replace {0} with regex \d representing digits
1373 search_string = str(time_string)
1374 search_string = search_string.format(r"\d+")
1376 # Create search pattern and find within string
1377 pattern = re.compile(rf"(^|\b|\d){search_string}")
1378 match = pattern.search(input_string)
1380 # If there is no match continue to next iteration
1381 if not match:
1382 continue
1384 match_string = match.group()
1385 num_match = num_pattern.search(match_string)
1387 # If no number matches
1388 # Need for absolute value as some locales have signs included in their objects
1389 if not num_match:
1390 change_value = (
1391 1 if not time_delta.isnumeric() else abs(int(time_delta))
1392 )
1393 else:
1394 change_value = int(num_match.group())
1396 # No time to update if now is the unit
1397 if unit == "now":
1398 unit_visited[unit] = True
1399 continue
1401 # Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds)
1402 time_unit_to_change = str(unit)
1403 time_unit_to_change += (
1404 "s" if (str(time_unit_to_change)[-1] != "s") else ""
1405 )
1406 time_object_info[time_unit_to_change] = change_value
1407 unit_visited[time_unit_to_change] = True
1409 # Assert error if string does not modify any units
1410 if not any([True for k, v in unit_visited.items() if v]):
1411 raise ValueError(
1412 "Input string not valid. Note: Some locales do not support the week granularity in Arrow. "
1413 "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error."
1414 )
1416 # Sign logic
1417 future_string = locale_obj.future
1418 future_string = future_string.format(".*")
1419 future_pattern = re.compile(rf"^{future_string}$")
1420 future_pattern_match = future_pattern.findall(input_string)
1422 past_string = locale_obj.past
1423 past_string = past_string.format(".*")
1424 past_pattern = re.compile(rf"^{past_string}$")
1425 past_pattern_match = past_pattern.findall(input_string)
1427 # If a string contains the now unit, there will be no relative units, hence the need to check if the now unit
1428 # was visited before raising a ValueError
1429 if past_pattern_match:
1430 sign_val = -1
1431 elif future_pattern_match:
1432 sign_val = 1
1433 elif unit_visited["now"]:
1434 sign_val = 0
1435 else:
1436 raise ValueError(
1437 "Invalid input String. String does not contain any relative time information. "
1438 "String should either represent a time in the future or a time in the past. "
1439 "Ex: 'in 5 seconds' or '5 seconds ago'."
1440 )
1442 time_changes = {k: sign_val * v for k, v in time_object_info.items()}
1444 return current_time.shift(**time_changes)
1446 # query functions
1448 def is_between(
1449 self,
1450 start: "Arrow",
1451 end: "Arrow",
1452 bounds: _BOUNDS = "()",
1453 ) -> bool:
1454 """Returns a boolean denoting whether the :class:`Arrow <arrow.arrow.Arrow>` object is between
1455 the start and end limits.
1457 :param start: an :class:`Arrow <arrow.arrow.Arrow>` object.
1458 :param end: an :class:`Arrow <arrow.arrow.Arrow>` object.
1459 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies
1460 whether to include or exclude the start and end values in the range. '(' excludes
1461 the start, '[' includes the start, ')' excludes the end, and ']' includes the end.
1462 If the bounds are not specified, the default bound '()' is used.
1464 Usage::
1466 >>> start = arrow.get(datetime(2013, 5, 5, 12, 30, 10))
1467 >>> end = arrow.get(datetime(2013, 5, 5, 12, 30, 36))
1468 >>> arrow.get(datetime(2013, 5, 5, 12, 30, 27)).is_between(start, end)
1469 True
1471 >>> start = arrow.get(datetime(2013, 5, 5))
1472 >>> end = arrow.get(datetime(2013, 5, 8))
1473 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[]')
1474 True
1476 >>> start = arrow.get(datetime(2013, 5, 5))
1477 >>> end = arrow.get(datetime(2013, 5, 8))
1478 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[)')
1479 False
1481 """
1483 util.validate_bounds(bounds)
1485 if not isinstance(start, Arrow):
1486 raise TypeError(
1487 f"Cannot parse start date argument type of {type(start)!r}."
1488 )
1490 if not isinstance(end, Arrow):
1491 raise TypeError(f"Cannot parse end date argument type of {type(start)!r}.")
1493 include_start = bounds[0] == "["
1494 include_end = bounds[1] == "]"
1496 target_ts = self.float_timestamp
1497 start_ts = start.float_timestamp
1498 end_ts = end.float_timestamp
1500 return (
1501 (start_ts <= target_ts <= end_ts)
1502 and (include_start or start_ts < target_ts)
1503 and (include_end or target_ts < end_ts)
1504 )
1506 # datetime methods
1508 def date(self) -> date:
1509 """Returns a ``date`` object with the same year, month and day.
1511 Usage::
1513 >>> arrow.utcnow().date()
1514 datetime.date(2019, 1, 23)
1516 """
1518 return self._datetime.date()
1520 def time(self) -> dt_time:
1521 """Returns a ``time`` object with the same hour, minute, second, microsecond.
1523 Usage::
1525 >>> arrow.utcnow().time()
1526 datetime.time(12, 15, 34, 68352)
1528 """
1530 return self._datetime.time()
1532 def timetz(self) -> dt_time:
1533 """Returns a ``time`` object with the same hour, minute, second, microsecond and
1534 tzinfo.
1536 Usage::
1538 >>> arrow.utcnow().timetz()
1539 datetime.time(12, 5, 18, 298893, tzinfo=tzutc())
1541 """
1543 return self._datetime.timetz()
1545 def astimezone(self, tz: Optional[dt_tzinfo]) -> dt_datetime:
1546 """Returns a ``datetime`` object, converted to the specified timezone.
1548 :param tz: a ``tzinfo`` object.
1550 Usage::
1552 >>> pacific=arrow.now('US/Pacific')
1553 >>> nyc=arrow.now('America/New_York').tzinfo
1554 >>> pacific.astimezone(nyc)
1555 datetime.datetime(2019, 1, 20, 10, 24, 22, 328172, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York'))
1557 """
1559 return self._datetime.astimezone(tz)
1561 def utcoffset(self) -> Optional[timedelta]:
1562 """Returns a ``timedelta`` object representing the whole number of minutes difference from
1563 UTC time.
1565 Usage::
1567 >>> arrow.now('US/Pacific').utcoffset()
1568 datetime.timedelta(-1, 57600)
1570 """
1572 return self._datetime.utcoffset()
1574 def dst(self) -> Optional[timedelta]:
1575 """Returns the daylight savings time adjustment.
1577 Usage::
1579 >>> arrow.utcnow().dst()
1580 datetime.timedelta(0)
1582 """
1584 return self._datetime.dst()
1586 def timetuple(self) -> struct_time:
1587 """Returns a ``time.struct_time``, in the current timezone.
1589 Usage::
1591 >>> arrow.utcnow().timetuple()
1592 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)
1594 """
1596 return self._datetime.timetuple()
1598 def utctimetuple(self) -> struct_time:
1599 """Returns a ``time.struct_time``, in UTC time.
1601 Usage::
1603 >>> arrow.utcnow().utctimetuple()
1604 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)
1606 """
1608 return self._datetime.utctimetuple()
1610 def toordinal(self) -> int:
1611 """Returns the proleptic Gregorian ordinal of the date.
1613 Usage::
1615 >>> arrow.utcnow().toordinal()
1616 737078
1618 """
1620 return self._datetime.toordinal()
1622 def weekday(self) -> int:
1623 """Returns the day of the week as an integer (0-6).
1625 Usage::
1627 >>> arrow.utcnow().weekday()
1628 5
1630 """
1632 return self._datetime.weekday()
1634 def isoweekday(self) -> int:
1635 """Returns the ISO day of the week as an integer (1-7).
1637 Usage::
1639 >>> arrow.utcnow().isoweekday()
1640 6
1642 """
1644 return self._datetime.isoweekday()
1646 def isocalendar(self) -> Tuple[int, int, int]:
1647 """Returns a 3-tuple, (ISO year, ISO week number, ISO weekday).
1649 Usage::
1651 >>> arrow.utcnow().isocalendar()
1652 (2019, 3, 6)
1654 """
1656 return self._datetime.isocalendar()
1658 def isoformat(self, sep: str = "T", timespec: str = "auto") -> str:
1659 """Returns an ISO 8601 formatted representation of the date and time.
1661 Usage::
1663 >>> arrow.utcnow().isoformat()
1664 '2019-01-19T18:30:52.442118+00:00'
1666 """
1668 return self._datetime.isoformat(sep, timespec)
1670 def ctime(self) -> str:
1671 """Returns a ctime formatted representation of the date and time.
1673 Usage::
1675 >>> arrow.utcnow().ctime()
1676 'Sat Jan 19 18:26:50 2019'
1678 """
1680 return self._datetime.ctime()
1682 def strftime(self, format: str) -> str:
1683 """Formats in the style of ``datetime.strftime``.
1685 :param format: the format string.
1687 Usage::
1689 >>> arrow.utcnow().strftime('%d-%m-%Y %H:%M:%S')
1690 '23-01-2019 12:28:17'
1692 """
1694 return self._datetime.strftime(format)
1696 def for_json(self) -> str:
1697 """Serializes for the ``for_json`` protocol of simplejson.
1699 Usage::
1701 >>> arrow.utcnow().for_json()
1702 '2019-01-19T18:25:36.760079+00:00'
1704 """
1706 return self.isoformat()
1708 # math
1710 def __add__(self, other: Any) -> "Arrow":
1711 if isinstance(other, (timedelta, relativedelta)):
1712 return self.fromdatetime(self._datetime + other, self._datetime.tzinfo)
1714 return NotImplemented
1716 def __radd__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
1717 return self.__add__(other)
1719 @overload
1720 def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
1721 pass # pragma: no cover
1723 @overload
1724 def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta:
1725 pass # pragma: no cover
1727 def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]:
1728 if isinstance(other, (timedelta, relativedelta)):
1729 return self.fromdatetime(self._datetime - other, self._datetime.tzinfo)
1731 elif isinstance(other, dt_datetime):
1732 return self._datetime - other
1734 elif isinstance(other, Arrow):
1735 return self._datetime - other._datetime
1737 return NotImplemented
1739 def __rsub__(self, other: Any) -> timedelta:
1740 if isinstance(other, dt_datetime):
1741 return other - self._datetime
1743 return NotImplemented
1745 # comparisons
1747 def __eq__(self, other: Any) -> bool:
1748 if not isinstance(other, (Arrow, dt_datetime)):
1749 return False
1751 return self._datetime == self._get_datetime(other)
1753 def __ne__(self, other: Any) -> bool:
1754 if not isinstance(other, (Arrow, dt_datetime)):
1755 return True
1757 return not self.__eq__(other)
1759 def __gt__(self, other: Any) -> bool:
1760 if not isinstance(other, (Arrow, dt_datetime)):
1761 return NotImplemented
1763 return self._datetime > self._get_datetime(other)
1765 def __ge__(self, other: Any) -> bool:
1766 if not isinstance(other, (Arrow, dt_datetime)):
1767 return NotImplemented
1769 return self._datetime >= self._get_datetime(other)
1771 def __lt__(self, other: Any) -> bool:
1772 if not isinstance(other, (Arrow, dt_datetime)):
1773 return NotImplemented
1775 return self._datetime < self._get_datetime(other)
1777 def __le__(self, other: Any) -> bool:
1778 if not isinstance(other, (Arrow, dt_datetime)):
1779 return NotImplemented
1781 return self._datetime <= self._get_datetime(other)
1783 # internal methods
1784 @staticmethod
1785 def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo:
1786 """Get normalized tzinfo object from various inputs."""
1787 if tz_expr is None:
1788 return dateutil_tz.tzutc()
1789 if isinstance(tz_expr, dt_tzinfo):
1790 return tz_expr
1791 else:
1792 try:
1793 return parser.TzinfoParser.parse(tz_expr)
1794 except parser.ParserError:
1795 raise ValueError(f"{tz_expr!r} not recognized as a timezone.")
1797 @classmethod
1798 def _get_datetime(
1799 cls, expr: Union["Arrow", dt_datetime, int, float, str]
1800 ) -> dt_datetime:
1801 """Get datetime object from a specified expression."""
1802 if isinstance(expr, Arrow):
1803 return expr.datetime
1804 elif isinstance(expr, dt_datetime):
1805 return expr
1806 elif util.is_timestamp(expr):
1807 timestamp = float(expr)
1808 return cls.utcfromtimestamp(timestamp).datetime
1809 else:
1810 raise ValueError(f"{expr!r} not recognized as a datetime or timestamp.")
1812 @classmethod
1813 def _get_frames(cls, name: _T_FRAMES) -> Tuple[str, str, int]:
1814 """Finds relevant timeframe and steps for use in range and span methods.
1816 Returns a 3 element tuple in the form (frame, plural frame, step), for example ("day", "days", 1)
1818 """
1819 if name in cls._ATTRS:
1820 return name, f"{name}s", 1
1821 elif name[-1] == "s" and name[:-1] in cls._ATTRS:
1822 return name[:-1], name, 1
1823 elif name in ["week", "weeks"]:
1824 return "week", "weeks", 1
1825 elif name in ["quarter", "quarters"]:
1826 return "quarter", "months", 3
1827 else:
1828 supported = ", ".join(
1829 [
1830 "year(s)",
1831 "month(s)",
1832 "day(s)",
1833 "hour(s)",
1834 "minute(s)",
1835 "second(s)",
1836 "microsecond(s)",
1837 "week(s)",
1838 "quarter(s)",
1839 ]
1840 )
1841 raise ValueError(
1842 f"Range or span over frame {name} not supported. Supported frames: {supported}."
1843 )
1845 @classmethod
1846 def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]:
1847 """Sets default end and limit values for range method."""
1848 if end is None:
1849 if limit is None:
1850 raise ValueError("One of 'end' or 'limit' is required.")
1852 return cls.max, limit
1854 else:
1855 if limit is None:
1856 return end, sys.maxsize
1857 return end, limit
1859 @staticmethod
1860 def _is_last_day_of_month(date: "Arrow") -> bool:
1861 """Returns a boolean indicating whether the datetime is the last day of the month."""
1862 return date.day == calendar.monthrange(date.year, date.month)[1]
1865Arrow.min = Arrow.fromdatetime(dt_datetime.min)
1866Arrow.max = Arrow.fromdatetime(dt_datetime.max)