Spaces:
Runtime error
Runtime error
""" | |
Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime`` | |
replacement. | |
""" | |
import calendar | |
import re | |
import sys | |
from datetime import date | |
from datetime import datetime as dt_datetime | |
from datetime import time as dt_time | |
from datetime import timedelta | |
from datetime import tzinfo as dt_tzinfo | |
from math import trunc | |
from time import struct_time | |
from typing import ( | |
Any, | |
ClassVar, | |
Generator, | |
Iterable, | |
List, | |
Mapping, | |
Optional, | |
Tuple, | |
Union, | |
cast, | |
overload, | |
) | |
from dateutil import tz as dateutil_tz | |
from dateutil.relativedelta import relativedelta | |
from arrow import formatter, locales, parser, util | |
from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES | |
from arrow.locales import TimeFrameLiteral | |
if sys.version_info < (3, 8): # pragma: no cover | |
from typing_extensions import Final, Literal | |
else: | |
from typing import Final, Literal # pragma: no cover | |
TZ_EXPR = Union[dt_tzinfo, str] | |
_T_FRAMES = Literal[ | |
"year", | |
"years", | |
"month", | |
"months", | |
"day", | |
"days", | |
"hour", | |
"hours", | |
"minute", | |
"minutes", | |
"second", | |
"seconds", | |
"microsecond", | |
"microseconds", | |
"week", | |
"weeks", | |
"quarter", | |
"quarters", | |
] | |
_BOUNDS = Literal["[)", "()", "(]", "[]"] | |
_GRANULARITY = Literal[ | |
"auto", | |
"second", | |
"minute", | |
"hour", | |
"day", | |
"week", | |
"month", | |
"quarter", | |
"year", | |
] | |
class Arrow: | |
"""An :class:`Arrow <arrow.arrow.Arrow>` object. | |
Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing | |
additional functionality. | |
:param year: the calendar year. | |
:param month: the calendar month. | |
:param day: the calendar day. | |
:param hour: (optional) the hour. Defaults to 0. | |
:param minute: (optional) the minute, Defaults to 0. | |
:param second: (optional) the second, Defaults to 0. | |
:param microsecond: (optional) the microsecond. Defaults to 0. | |
:param tzinfo: (optional) A timezone expression. Defaults to UTC. | |
:param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0. | |
.. _tz-expr: | |
Recognized timezone expressions: | |
- A ``tzinfo`` object. | |
- A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'. | |
- A ``str`` in ISO 8601 style, as in '+07:00'. | |
- A ``str``, one of the following: 'local', 'utc', 'UTC'. | |
Usage:: | |
>>> import arrow | |
>>> arrow.Arrow(2013, 5, 5, 12, 30, 45) | |
<Arrow [2013-05-05T12:30:45+00:00]> | |
""" | |
resolution: ClassVar[timedelta] = dt_datetime.resolution | |
min: ClassVar["Arrow"] | |
max: ClassVar["Arrow"] | |
_ATTRS: Final[List[str]] = [ | |
"year", | |
"month", | |
"day", | |
"hour", | |
"minute", | |
"second", | |
"microsecond", | |
] | |
_ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS] | |
_MONTHS_PER_QUARTER: Final[int] = 3 | |
_SECS_PER_MINUTE: Final[int] = 60 | |
_SECS_PER_HOUR: Final[int] = 60 * 60 | |
_SECS_PER_DAY: Final[int] = 60 * 60 * 24 | |
_SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7 | |
_SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5 | |
_SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3 | |
_SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365 | |
_SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = { | |
"second": 1.0, | |
"minute": _SECS_PER_MINUTE, | |
"hour": _SECS_PER_HOUR, | |
"day": _SECS_PER_DAY, | |
"week": _SECS_PER_WEEK, | |
"month": _SECS_PER_MONTH, | |
"quarter": _SECS_PER_QUARTER, | |
"year": _SECS_PER_YEAR, | |
} | |
_datetime: dt_datetime | |
def __init__( | |
self, | |
year: int, | |
month: int, | |
day: int, | |
hour: int = 0, | |
minute: int = 0, | |
second: int = 0, | |
microsecond: int = 0, | |
tzinfo: Optional[TZ_EXPR] = None, | |
**kwargs: Any, | |
) -> None: | |
if tzinfo is None: | |
tzinfo = dateutil_tz.tzutc() | |
# detect that tzinfo is a pytz object (issue #626) | |
elif ( | |
isinstance(tzinfo, dt_tzinfo) | |
and hasattr(tzinfo, "localize") | |
and hasattr(tzinfo, "zone") | |
and tzinfo.zone | |
): | |
tzinfo = parser.TzinfoParser.parse(tzinfo.zone) | |
elif isinstance(tzinfo, str): | |
tzinfo = parser.TzinfoParser.parse(tzinfo) | |
fold = kwargs.get("fold", 0) | |
self._datetime = dt_datetime( | |
year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold | |
) | |
# factories: single object, both original and from datetime. | |
def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow": | |
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given | |
timezone. | |
:param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time. | |
Usage:: | |
>>> arrow.now('Asia/Baku') | |
<Arrow [2019-01-24T20:26:31.146412+04:00]> | |
""" | |
if tzinfo is None: | |
tzinfo = dateutil_tz.tzlocal() | |
dt = dt_datetime.now(tzinfo) | |
return cls( | |
dt.year, | |
dt.month, | |
dt.day, | |
dt.hour, | |
dt.minute, | |
dt.second, | |
dt.microsecond, | |
dt.tzinfo, | |
fold=getattr(dt, "fold", 0), | |
) | |
def utcnow(cls) -> "Arrow": | |
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC | |
time. | |
Usage:: | |
>>> arrow.utcnow() | |
<Arrow [2019-01-24T16:31:40.651108+00:00]> | |
""" | |
dt = dt_datetime.now(dateutil_tz.tzutc()) | |
return cls( | |
dt.year, | |
dt.month, | |
dt.day, | |
dt.hour, | |
dt.minute, | |
dt.second, | |
dt.microsecond, | |
dt.tzinfo, | |
fold=getattr(dt, "fold", 0), | |
) | |
def fromtimestamp( | |
cls, | |
timestamp: Union[int, float, str], | |
tzinfo: Optional[TZ_EXPR] = None, | |
) -> "Arrow": | |
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, converted to | |
the given timezone. | |
:param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. | |
:param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time. | |
""" | |
if tzinfo is None: | |
tzinfo = dateutil_tz.tzlocal() | |
elif isinstance(tzinfo, str): | |
tzinfo = parser.TzinfoParser.parse(tzinfo) | |
if not util.is_timestamp(timestamp): | |
raise ValueError(f"The provided timestamp {timestamp!r} is invalid.") | |
timestamp = util.normalize_timestamp(float(timestamp)) | |
dt = dt_datetime.fromtimestamp(timestamp, tzinfo) | |
return cls( | |
dt.year, | |
dt.month, | |
dt.day, | |
dt.hour, | |
dt.minute, | |
dt.second, | |
dt.microsecond, | |
dt.tzinfo, | |
fold=getattr(dt, "fold", 0), | |
) | |
def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow": | |
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, in UTC time. | |
:param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. | |
""" | |
if not util.is_timestamp(timestamp): | |
raise ValueError(f"The provided timestamp {timestamp!r} is invalid.") | |
timestamp = util.normalize_timestamp(float(timestamp)) | |
dt = dt_datetime.utcfromtimestamp(timestamp) | |
return cls( | |
dt.year, | |
dt.month, | |
dt.day, | |
dt.hour, | |
dt.minute, | |
dt.second, | |
dt.microsecond, | |
dateutil_tz.tzutc(), | |
fold=getattr(dt, "fold", 0), | |
) | |
def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow": | |
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and | |
optional replacement timezone. | |
:param dt: the ``datetime`` | |
:param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to ``dt``'s | |
timezone, or UTC if naive. | |
Usage:: | |
>>> dt | |
datetime.datetime(2021, 4, 7, 13, 48, tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific')) | |
>>> arrow.Arrow.fromdatetime(dt) | |
<Arrow [2021-04-07T13:48:00-07:00]> | |
""" | |
if tzinfo is None: | |
if dt.tzinfo is None: | |
tzinfo = dateutil_tz.tzutc() | |
else: | |
tzinfo = dt.tzinfo | |
return cls( | |
dt.year, | |
dt.month, | |
dt.day, | |
dt.hour, | |
dt.minute, | |
dt.second, | |
dt.microsecond, | |
tzinfo, | |
fold=getattr(dt, "fold", 0), | |
) | |
def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow": | |
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional | |
replacement timezone. All time values are set to 0. | |
:param date: the ``date`` | |
:param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to UTC. | |
""" | |
if tzinfo is None: | |
tzinfo = dateutil_tz.tzutc() | |
return cls(date.year, date.month, date.day, tzinfo=tzinfo) | |
def strptime( | |
cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None | |
) -> "Arrow": | |
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a date string and format, | |
in the style of ``datetime.strptime``. Optionally replaces the parsed timezone. | |
:param date_str: the date string. | |
:param fmt: the format string using datetime format codes. | |
:param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to the parsed | |
timezone if ``fmt`` contains a timezone directive, otherwise UTC. | |
Usage:: | |
>>> arrow.Arrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S') | |
<Arrow [2019-01-20T15:49:10+00:00]> | |
""" | |
dt = dt_datetime.strptime(date_str, fmt) | |
if tzinfo is None: | |
tzinfo = dt.tzinfo | |
return cls( | |
dt.year, | |
dt.month, | |
dt.day, | |
dt.hour, | |
dt.minute, | |
dt.second, | |
dt.microsecond, | |
tzinfo, | |
fold=getattr(dt, "fold", 0), | |
) | |
def fromordinal(cls, ordinal: int) -> "Arrow": | |
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object corresponding | |
to the Gregorian Ordinal. | |
:param ordinal: an ``int`` corresponding to a Gregorian Ordinal. | |
Usage:: | |
>>> arrow.fromordinal(737741) | |
<Arrow [2020-11-12T00:00:00+00:00]> | |
""" | |
util.validate_ordinal(ordinal) | |
dt = dt_datetime.fromordinal(ordinal) | |
return cls( | |
dt.year, | |
dt.month, | |
dt.day, | |
dt.hour, | |
dt.minute, | |
dt.second, | |
dt.microsecond, | |
dt.tzinfo, | |
fold=getattr(dt, "fold", 0), | |
) | |
# factories: ranges and spans | |
def range( | |
cls, | |
frame: _T_FRAMES, | |
start: Union["Arrow", dt_datetime], | |
end: Union["Arrow", dt_datetime, None] = None, | |
tz: Optional[TZ_EXPR] = None, | |
limit: Optional[int] = None, | |
) -> Generator["Arrow", None, None]: | |
"""Returns an iterator of :class:`Arrow <arrow.arrow.Arrow>` objects, representing | |
points in time between two inputs. | |
:param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). | |
:param start: A datetime expression, the start of the range. | |
:param end: (optional) A datetime expression, the end of the range. | |
:param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to | |
``start``'s timezone, or UTC if ``start`` is naive. | |
:param limit: (optional) A maximum number of tuples to return. | |
**NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to | |
return the entire range. Call with ``limit`` alone to return a maximum # of results from | |
the start. Call with both to cap a range at a maximum # of results. | |
**NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before | |
iterating. As such, either call with naive objects and ``tz``, or aware objects from the | |
same timezone and no ``tz``. | |
Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond. | |
Recognized datetime expressions: | |
- An :class:`Arrow <arrow.arrow.Arrow>` object. | |
- A ``datetime`` object. | |
Usage:: | |
>>> start = datetime(2013, 5, 5, 12, 30) | |
>>> end = datetime(2013, 5, 5, 17, 15) | |
>>> for r in arrow.Arrow.range('hour', start, end): | |
... print(repr(r)) | |
... | |
<Arrow [2013-05-05T12:30:00+00:00]> | |
<Arrow [2013-05-05T13:30:00+00:00]> | |
<Arrow [2013-05-05T14:30:00+00:00]> | |
<Arrow [2013-05-05T15:30:00+00:00]> | |
<Arrow [2013-05-05T16:30:00+00:00]> | |
**NOTE**: Unlike Python's ``range``, ``end`` *may* be included in the returned iterator:: | |
>>> start = datetime(2013, 5, 5, 12, 30) | |
>>> end = datetime(2013, 5, 5, 13, 30) | |
>>> for r in arrow.Arrow.range('hour', start, end): | |
... print(repr(r)) | |
... | |
<Arrow [2013-05-05T12:30:00+00:00]> | |
<Arrow [2013-05-05T13:30:00+00:00]> | |
""" | |
_, frame_relative, relative_steps = cls._get_frames(frame) | |
tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz) | |
start = cls._get_datetime(start).replace(tzinfo=tzinfo) | |
end, limit = cls._get_iteration_params(end, limit) | |
end = cls._get_datetime(end).replace(tzinfo=tzinfo) | |
current = cls.fromdatetime(start) | |
original_day = start.day | |
day_is_clipped = False | |
i = 0 | |
while current <= end and i < limit: | |
i += 1 | |
yield current | |
values = [getattr(current, f) for f in cls._ATTRS] | |
current = cls(*values, tzinfo=tzinfo).shift( # type: ignore[misc] | |
**{frame_relative: relative_steps} | |
) | |
if frame in ["month", "quarter", "year"] and current.day < original_day: | |
day_is_clipped = True | |
if day_is_clipped and not cls._is_last_day_of_month(current): | |
current = current.replace(day=original_day) | |
def span( | |
self, | |
frame: _T_FRAMES, | |
count: int = 1, | |
bounds: _BOUNDS = "[)", | |
exact: bool = False, | |
week_start: int = 1, | |
) -> Tuple["Arrow", "Arrow"]: | |
"""Returns a tuple of two new :class:`Arrow <arrow.arrow.Arrow>` objects, representing the timespan | |
of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe. | |
:param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). | |
:param count: (optional) the number of frames to span. | |
:param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies | |
whether to include or exclude the start and end values in the span. '(' excludes | |
the start, '[' includes the start, ')' excludes the end, and ']' includes the end. | |
If the bounds are not specified, the default bound '[)' is used. | |
:param exact: (optional) whether to have the start of the timespan begin exactly | |
at the time specified by ``start`` and the end of the timespan truncated | |
so as not to extend beyond ``end``. | |
:param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where | |
Monday is 1 and Sunday is 7. | |
Supported frame values: year, quarter, month, week, day, hour, minute, second. | |
Usage:: | |
>>> arrow.utcnow() | |
<Arrow [2013-05-09T03:32:36.186203+00:00]> | |
>>> arrow.utcnow().span('hour') | |
(<Arrow [2013-05-09T03:00:00+00:00]>, <Arrow [2013-05-09T03:59:59.999999+00:00]>) | |
>>> arrow.utcnow().span('day') | |
(<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-09T23:59:59.999999+00:00]>) | |
>>> arrow.utcnow().span('day', count=2) | |
(<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T23:59:59.999999+00:00]>) | |
>>> arrow.utcnow().span('day', bounds='[]') | |
(<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T00:00:00+00:00]>) | |
>>> arrow.utcnow().span('week') | |
(<Arrow [2021-02-22T00:00:00+00:00]>, <Arrow [2021-02-28T23:59:59.999999+00:00]>) | |
>>> arrow.utcnow().span('week', week_start=6) | |
(<Arrow [2021-02-20T00:00:00+00:00]>, <Arrow [2021-02-26T23:59:59.999999+00:00]>) | |
""" | |
if not 1 <= week_start <= 7: | |
raise ValueError("week_start argument must be between 1 and 7.") | |
util.validate_bounds(bounds) | |
frame_absolute, frame_relative, relative_steps = self._get_frames(frame) | |
if frame_absolute == "week": | |
attr = "day" | |
elif frame_absolute == "quarter": | |
attr = "month" | |
else: | |
attr = frame_absolute | |
floor = self | |
if not exact: | |
index = self._ATTRS.index(attr) | |
frames = self._ATTRS[: index + 1] | |
values = [getattr(self, f) for f in frames] | |
for _ in range(3 - len(values)): | |
values.append(1) | |
floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc] | |
if frame_absolute == "week": | |
# if week_start is greater than self.isoweekday() go back one week by setting delta = 7 | |
delta = 7 if week_start > self.isoweekday() else 0 | |
floor = floor.shift(days=-(self.isoweekday() - week_start) - delta) | |
elif frame_absolute == "quarter": | |
floor = floor.shift(months=-((self.month - 1) % 3)) | |
ceil = floor.shift(**{frame_relative: count * relative_steps}) | |
if bounds[0] == "(": | |
floor = floor.shift(microseconds=+1) | |
if bounds[1] == ")": | |
ceil = ceil.shift(microseconds=-1) | |
return floor, ceil | |
def floor(self, frame: _T_FRAMES) -> "Arrow": | |
"""Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "floor" | |
of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe. | |
Equivalent to the first element in the 2-tuple returned by | |
:func:`span <arrow.arrow.Arrow.span>`. | |
:param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). | |
Usage:: | |
>>> arrow.utcnow().floor('hour') | |
<Arrow [2013-05-09T03:00:00+00:00]> | |
""" | |
return self.span(frame)[0] | |
def ceil(self, frame: _T_FRAMES) -> "Arrow": | |
"""Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "ceiling" | |
of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe. | |
Equivalent to the second element in the 2-tuple returned by | |
:func:`span <arrow.arrow.Arrow.span>`. | |
:param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). | |
Usage:: | |
>>> arrow.utcnow().ceil('hour') | |
<Arrow [2013-05-09T03:59:59.999999+00:00]> | |
""" | |
return self.span(frame)[1] | |
def span_range( | |
cls, | |
frame: _T_FRAMES, | |
start: dt_datetime, | |
end: dt_datetime, | |
tz: Optional[TZ_EXPR] = None, | |
limit: Optional[int] = None, | |
bounds: _BOUNDS = "[)", | |
exact: bool = False, | |
) -> Iterable[Tuple["Arrow", "Arrow"]]: | |
"""Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects, | |
representing a series of timespans between two inputs. | |
:param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). | |
:param start: A datetime expression, the start of the range. | |
:param end: (optional) A datetime expression, the end of the range. | |
:param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to | |
``start``'s timezone, or UTC if ``start`` is naive. | |
:param limit: (optional) A maximum number of tuples to return. | |
:param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies | |
whether to include or exclude the start and end values in each span in the range. '(' excludes | |
the start, '[' includes the start, ')' excludes the end, and ']' includes the end. | |
If the bounds are not specified, the default bound '[)' is used. | |
:param exact: (optional) whether to have the first timespan start exactly | |
at the time specified by ``start`` and the final span truncated | |
so as not to extend beyond ``end``. | |
**NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to | |
return the entire range. Call with ``limit`` alone to return a maximum # of results from | |
the start. Call with both to cap a range at a maximum # of results. | |
**NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before | |
iterating. As such, either call with naive objects and ``tz``, or aware objects from the | |
same timezone and no ``tz``. | |
Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond. | |
Recognized datetime expressions: | |
- An :class:`Arrow <arrow.arrow.Arrow>` object. | |
- A ``datetime`` object. | |
**NOTE**: Unlike Python's ``range``, ``end`` will *always* be included in the returned | |
iterator of timespans. | |
Usage: | |
>>> start = datetime(2013, 5, 5, 12, 30) | |
>>> end = datetime(2013, 5, 5, 17, 15) | |
>>> for r in arrow.Arrow.span_range('hour', start, end): | |
... print(r) | |
... | |
(<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>) | |
(<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>) | |
(<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>) | |
(<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>) | |
(<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>) | |
(<Arrow [2013-05-05T17:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:00]>) | |
""" | |
tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz) | |
start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0] | |
end = cls.fromdatetime(end, tzinfo) | |
_range = cls.range(frame, start, end, tz, limit) | |
if not exact: | |
for r in _range: | |
yield r.span(frame, bounds=bounds, exact=exact) | |
for r in _range: | |
floor, ceil = r.span(frame, bounds=bounds, exact=exact) | |
if ceil > end: | |
ceil = end | |
if bounds[1] == ")": | |
ceil += relativedelta(microseconds=-1) | |
if floor == end: | |
break | |
elif floor + relativedelta(microseconds=-1) == end: | |
break | |
yield floor, ceil | |
def interval( | |
cls, | |
frame: _T_FRAMES, | |
start: dt_datetime, | |
end: dt_datetime, | |
interval: int = 1, | |
tz: Optional[TZ_EXPR] = None, | |
bounds: _BOUNDS = "[)", | |
exact: bool = False, | |
) -> Iterable[Tuple["Arrow", "Arrow"]]: | |
"""Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects, | |
representing a series of intervals between two inputs. | |
:param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). | |
:param start: A datetime expression, the start of the range. | |
:param end: (optional) A datetime expression, the end of the range. | |
:param interval: (optional) Time interval for the given time frame. | |
:param tz: (optional) A timezone expression. Defaults to UTC. | |
:param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies | |
whether to include or exclude the start and end values in the intervals. '(' excludes | |
the start, '[' includes the start, ')' excludes the end, and ']' includes the end. | |
If the bounds are not specified, the default bound '[)' is used. | |
:param exact: (optional) whether to have the first timespan start exactly | |
at the time specified by ``start`` and the final interval truncated | |
so as not to extend beyond ``end``. | |
Supported frame values: year, quarter, month, week, day, hour, minute, second | |
Recognized datetime expressions: | |
- An :class:`Arrow <arrow.arrow.Arrow>` object. | |
- A ``datetime`` object. | |
Recognized timezone expressions: | |
- A ``tzinfo`` object. | |
- A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'. | |
- A ``str`` in ISO 8601 style, as in '+07:00'. | |
- A ``str``, one of the following: 'local', 'utc', 'UTC'. | |
Usage: | |
>>> start = datetime(2013, 5, 5, 12, 30) | |
>>> end = datetime(2013, 5, 5, 17, 15) | |
>>> for r in arrow.Arrow.interval('hour', start, end, 2): | |
... print(r) | |
... | |
(<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>) | |
(<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>) | |
(<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:0]>) | |
""" | |
if interval < 1: | |
raise ValueError("interval has to be a positive integer") | |
spanRange = iter( | |
cls.span_range(frame, start, end, tz, bounds=bounds, exact=exact) | |
) | |
while True: | |
try: | |
intvlStart, intvlEnd = next(spanRange) | |
for _ in range(interval - 1): | |
try: | |
_, intvlEnd = next(spanRange) | |
except StopIteration: | |
continue | |
yield intvlStart, intvlEnd | |
except StopIteration: | |
return | |
# representations | |
def __repr__(self) -> str: | |
return f"<{self.__class__.__name__} [{self.__str__()}]>" | |
def __str__(self) -> str: | |
return self._datetime.isoformat() | |
def __format__(self, formatstr: str) -> str: | |
if len(formatstr) > 0: | |
return self.format(formatstr) | |
return str(self) | |
def __hash__(self) -> int: | |
return self._datetime.__hash__() | |
# attributes and properties | |
def __getattr__(self, name: str) -> int: | |
if name == "week": | |
return self.isocalendar()[1] | |
if name == "quarter": | |
return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1 | |
if not name.startswith("_"): | |
value: Optional[int] = getattr(self._datetime, name, None) | |
if value is not None: | |
return value | |
return cast(int, object.__getattribute__(self, name)) | |
def tzinfo(self) -> dt_tzinfo: | |
"""Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object. | |
Usage:: | |
>>> arw=arrow.utcnow() | |
>>> arw.tzinfo | |
tzutc() | |
""" | |
# In Arrow, `_datetime` cannot be naive. | |
return cast(dt_tzinfo, self._datetime.tzinfo) | |
def datetime(self) -> dt_datetime: | |
"""Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object. | |
Usage:: | |
>>> arw=arrow.utcnow() | |
>>> arw.datetime | |
datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc()) | |
""" | |
return self._datetime | |
def naive(self) -> dt_datetime: | |
"""Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` | |
object. | |
Usage:: | |
>>> nairobi = arrow.now('Africa/Nairobi') | |
>>> nairobi | |
<Arrow [2019-01-23T19:27:12.297999+03:00]> | |
>>> nairobi.naive | |
datetime.datetime(2019, 1, 23, 19, 27, 12, 297999) | |
""" | |
return self._datetime.replace(tzinfo=None) | |
def timestamp(self) -> float: | |
"""Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in | |
UTC time. | |
Usage:: | |
>>> arrow.utcnow().timestamp() | |
1616882340.256501 | |
""" | |
return self._datetime.timestamp() | |
def int_timestamp(self) -> int: | |
"""Returns an integer timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in | |
UTC time. | |
Usage:: | |
>>> arrow.utcnow().int_timestamp | |
1548260567 | |
""" | |
return int(self.timestamp()) | |
def float_timestamp(self) -> float: | |
"""Returns a floating-point timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` | |
object, in UTC time. | |
Usage:: | |
>>> arrow.utcnow().float_timestamp | |
1548260516.830896 | |
""" | |
return self.timestamp() | |
def fold(self) -> int: | |
"""Returns the ``fold`` value of the :class:`Arrow <arrow.arrow.Arrow>` object.""" | |
return self._datetime.fold | |
def ambiguous(self) -> bool: | |
"""Indicates whether the :class:`Arrow <arrow.arrow.Arrow>` object is a repeated wall time in the current | |
timezone. | |
""" | |
return dateutil_tz.datetime_ambiguous(self._datetime) | |
def imaginary(self) -> bool: | |
"""Indicates whether the :class: `Arrow <arrow.arrow.Arrow>` object exists in the current timezone.""" | |
return not dateutil_tz.datetime_exists(self._datetime) | |
# mutation and duplication. | |
def clone(self) -> "Arrow": | |
"""Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, cloned from the current one. | |
Usage: | |
>>> arw = arrow.utcnow() | |
>>> cloned = arw.clone() | |
""" | |
return self.fromdatetime(self._datetime) | |
def replace(self, **kwargs: Any) -> "Arrow": | |
"""Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated | |
according to inputs. | |
Use property names to set their value absolutely:: | |
>>> import arrow | |
>>> arw = arrow.utcnow() | |
>>> arw | |
<Arrow [2013-05-11T22:27:34.787885+00:00]> | |
>>> arw.replace(year=2014, month=6) | |
<Arrow [2014-06-11T22:27:34.787885+00:00]> | |
You can also replace the timezone without conversion, using a | |
:ref:`timezone expression <tz-expr>`:: | |
>>> arw.replace(tzinfo=tz.tzlocal()) | |
<Arrow [2013-05-11T22:27:34.787885-07:00]> | |
""" | |
absolute_kwargs = {} | |
for key, value in kwargs.items(): | |
if key in self._ATTRS: | |
absolute_kwargs[key] = value | |
elif key in ["week", "quarter"]: | |
raise ValueError(f"Setting absolute {key} is not supported.") | |
elif key not in ["tzinfo", "fold"]: | |
raise ValueError(f"Unknown attribute: {key!r}.") | |
current = self._datetime.replace(**absolute_kwargs) | |
tzinfo = kwargs.get("tzinfo") | |
if tzinfo is not None: | |
tzinfo = self._get_tzinfo(tzinfo) | |
current = current.replace(tzinfo=tzinfo) | |
fold = kwargs.get("fold") | |
if fold is not None: | |
current = current.replace(fold=fold) | |
return self.fromdatetime(current) | |
def shift(self, **kwargs: Any) -> "Arrow": | |
"""Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated | |
according to inputs. | |
Use pluralized property names to relatively shift their current value: | |
>>> import arrow | |
>>> arw = arrow.utcnow() | |
>>> arw | |
<Arrow [2013-05-11T22:27:34.787885+00:00]> | |
>>> arw.shift(years=1, months=-1) | |
<Arrow [2014-04-11T22:27:34.787885+00:00]> | |
Day-of-the-week relative shifting can use either Python's weekday numbers | |
(Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's | |
day instances (MO, TU .. SU). When using weekday numbers, the returned | |
date will always be greater than or equal to the starting date. | |
Using the above code (which is a Saturday) and asking it to shift to Saturday: | |
>>> arw.shift(weekday=5) | |
<Arrow [2013-05-11T22:27:34.787885+00:00]> | |
While asking for a Monday: | |
>>> arw.shift(weekday=0) | |
<Arrow [2013-05-13T22:27:34.787885+00:00]> | |
""" | |
relative_kwargs = {} | |
additional_attrs = ["weeks", "quarters", "weekday"] | |
for key, value in kwargs.items(): | |
if key in self._ATTRS_PLURAL or key in additional_attrs: | |
relative_kwargs[key] = value | |
else: | |
supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs) | |
raise ValueError( | |
f"Invalid shift time frame. Please select one of the following: {supported_attr}." | |
) | |
# core datetime does not support quarters, translate to months. | |
relative_kwargs.setdefault("months", 0) | |
relative_kwargs["months"] += ( | |
relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER | |
) | |
current = self._datetime + relativedelta(**relative_kwargs) | |
if not dateutil_tz.datetime_exists(current): | |
current = dateutil_tz.resolve_imaginary(current) | |
return self.fromdatetime(current) | |
def to(self, tz: TZ_EXPR) -> "Arrow": | |
"""Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted | |
to the target timezone. | |
:param tz: A :ref:`timezone expression <tz-expr>`. | |
Usage:: | |
>>> utc = arrow.utcnow() | |
>>> utc | |
<Arrow [2013-05-09T03:49:12.311072+00:00]> | |
>>> utc.to('US/Pacific') | |
<Arrow [2013-05-08T20:49:12.311072-07:00]> | |
>>> utc.to(tz.tzlocal()) | |
<Arrow [2013-05-08T20:49:12.311072-07:00]> | |
>>> utc.to('-07:00') | |
<Arrow [2013-05-08T20:49:12.311072-07:00]> | |
>>> utc.to('local') | |
<Arrow [2013-05-08T20:49:12.311072-07:00]> | |
>>> utc.to('local').to('utc') | |
<Arrow [2013-05-09T03:49:12.311072+00:00]> | |
""" | |
if not isinstance(tz, dt_tzinfo): | |
tz = parser.TzinfoParser.parse(tz) | |
dt = self._datetime.astimezone(tz) | |
return self.__class__( | |
dt.year, | |
dt.month, | |
dt.day, | |
dt.hour, | |
dt.minute, | |
dt.second, | |
dt.microsecond, | |
dt.tzinfo, | |
fold=getattr(dt, "fold", 0), | |
) | |
# string output and formatting | |
def format( | |
self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE | |
) -> str: | |
"""Returns a string representation of the :class:`Arrow <arrow.arrow.Arrow>` object, | |
formatted according to the provided format string. | |
:param fmt: the format string. | |
:param locale: the locale to format. | |
Usage:: | |
>>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ') | |
'2013-05-09 03:56:47 -00:00' | |
>>> arrow.utcnow().format('X') | |
'1368071882' | |
>>> arrow.utcnow().format('MMMM DD, YYYY') | |
'May 09, 2013' | |
>>> arrow.utcnow().format() | |
'2013-05-09 03:56:47 -00:00' | |
""" | |
return formatter.DateTimeFormatter(locale).format(self._datetime, fmt) | |
def humanize( | |
self, | |
other: Union["Arrow", dt_datetime, None] = None, | |
locale: str = DEFAULT_LOCALE, | |
only_distance: bool = False, | |
granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto", | |
) -> str: | |
"""Returns a localized, humanized representation of a relative difference in time. | |
:param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object. | |
Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone. | |
:param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'. | |
:param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part. | |
:param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute', | |
'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings | |
Usage:: | |
>>> earlier = arrow.utcnow().shift(hours=-2) | |
>>> earlier.humanize() | |
'2 hours ago' | |
>>> later = earlier.shift(hours=4) | |
>>> later.humanize(earlier) | |
'in 4 hours' | |
""" | |
locale_name = locale | |
locale = locales.get_locale(locale) | |
if other is None: | |
utc = dt_datetime.utcnow().replace(tzinfo=dateutil_tz.tzutc()) | |
dt = utc.astimezone(self._datetime.tzinfo) | |
elif isinstance(other, Arrow): | |
dt = other._datetime | |
elif isinstance(other, dt_datetime): | |
if other.tzinfo is None: | |
dt = other.replace(tzinfo=self._datetime.tzinfo) | |
else: | |
dt = other.astimezone(self._datetime.tzinfo) | |
else: | |
raise TypeError( | |
f"Invalid 'other' argument of type {type(other).__name__!r}. " | |
"Argument must be of type None, Arrow, or datetime." | |
) | |
if isinstance(granularity, list) and len(granularity) == 1: | |
granularity = granularity[0] | |
_delta = int(round((self._datetime - dt).total_seconds())) | |
sign = -1 if _delta < 0 else 1 | |
delta_second = diff = abs(_delta) | |
try: | |
if granularity == "auto": | |
if diff < 10: | |
return locale.describe("now", only_distance=only_distance) | |
if diff < self._SECS_PER_MINUTE: | |
seconds = sign * delta_second | |
return locale.describe( | |
"seconds", seconds, only_distance=only_distance | |
) | |
elif diff < self._SECS_PER_MINUTE * 2: | |
return locale.describe("minute", sign, only_distance=only_distance) | |
elif diff < self._SECS_PER_HOUR: | |
minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2) | |
return locale.describe( | |
"minutes", minutes, only_distance=only_distance | |
) | |
elif diff < self._SECS_PER_HOUR * 2: | |
return locale.describe("hour", sign, only_distance=only_distance) | |
elif diff < self._SECS_PER_DAY: | |
hours = sign * max(delta_second // self._SECS_PER_HOUR, 2) | |
return locale.describe("hours", hours, only_distance=only_distance) | |
elif diff < self._SECS_PER_DAY * 2: | |
return locale.describe("day", sign, only_distance=only_distance) | |
elif diff < self._SECS_PER_WEEK: | |
days = sign * max(delta_second // self._SECS_PER_DAY, 2) | |
return locale.describe("days", days, only_distance=only_distance) | |
elif diff < self._SECS_PER_WEEK * 2: | |
return locale.describe("week", sign, only_distance=only_distance) | |
elif diff < self._SECS_PER_MONTH: | |
weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2) | |
return locale.describe("weeks", weeks, only_distance=only_distance) | |
elif diff < self._SECS_PER_MONTH * 2: | |
return locale.describe("month", sign, only_distance=only_distance) | |
elif diff < self._SECS_PER_YEAR: | |
# TODO revisit for humanization during leap years | |
self_months = self._datetime.year * 12 + self._datetime.month | |
other_months = dt.year * 12 + dt.month | |
months = sign * max(abs(other_months - self_months), 2) | |
return locale.describe( | |
"months", months, only_distance=only_distance | |
) | |
elif diff < self._SECS_PER_YEAR * 2: | |
return locale.describe("year", sign, only_distance=only_distance) | |
else: | |
years = sign * max(delta_second // self._SECS_PER_YEAR, 2) | |
return locale.describe("years", years, only_distance=only_distance) | |
elif isinstance(granularity, str): | |
granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment] | |
if granularity == "second": | |
delta = sign * float(delta_second) | |
if abs(delta) < 2: | |
return locale.describe("now", only_distance=only_distance) | |
elif granularity == "minute": | |
delta = sign * delta_second / self._SECS_PER_MINUTE | |
elif granularity == "hour": | |
delta = sign * delta_second / self._SECS_PER_HOUR | |
elif granularity == "day": | |
delta = sign * delta_second / self._SECS_PER_DAY | |
elif granularity == "week": | |
delta = sign * delta_second / self._SECS_PER_WEEK | |
elif granularity == "month": | |
delta = sign * delta_second / self._SECS_PER_MONTH | |
elif granularity == "quarter": | |
delta = sign * delta_second / self._SECS_PER_QUARTER | |
elif granularity == "year": | |
delta = sign * delta_second / self._SECS_PER_YEAR | |
else: | |
raise ValueError( | |
"Invalid level of granularity. " | |
"Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." | |
) | |
if trunc(abs(delta)) != 1: | |
granularity += "s" # type: ignore[assignment] | |
return locale.describe(granularity, delta, only_distance=only_distance) | |
else: | |
if not granularity: | |
raise ValueError( | |
"Empty granularity list provided. " | |
"Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'." | |
) | |
timeframes: List[Tuple[TimeFrameLiteral, float]] = [] | |
def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: | |
if _frame in granularity: | |
value = sign * _delta / self._SECS_MAP[_frame] | |
_delta %= self._SECS_MAP[_frame] | |
if trunc(abs(value)) != 1: | |
timeframes.append( | |
(cast(TimeFrameLiteral, _frame + "s"), value) | |
) | |
else: | |
timeframes.append((_frame, value)) | |
return _delta | |
delta = float(delta_second) | |
frames: Tuple[TimeFrameLiteral, ...] = ( | |
"year", | |
"quarter", | |
"month", | |
"week", | |
"day", | |
"hour", | |
"minute", | |
"second", | |
) | |
for frame in frames: | |
delta = gather_timeframes(delta, frame) | |
if len(timeframes) < len(granularity): | |
raise ValueError( | |
"Invalid level of granularity. " | |
"Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." | |
) | |
return locale.describe_multi(timeframes, only_distance=only_distance) | |
except KeyError as e: | |
raise ValueError( | |
f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. " | |
"Please consider making a contribution to this locale." | |
) | |
def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow": | |
"""Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, that represents | |
the time difference relative to the attributes of the | |
:class:`Arrow <arrow.arrow.Arrow>` object. | |
:param timestring: a ``str`` representing a humanized relative time. | |
:param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'. | |
Usage:: | |
>>> arw = arrow.utcnow() | |
>>> arw | |
<Arrow [2021-04-20T22:27:34.787885+00:00]> | |
>>> earlier = arw.dehumanize("2 days ago") | |
>>> earlier | |
<Arrow [2021-04-18T22:27:34.787885+00:00]> | |
>>> arw = arrow.utcnow() | |
>>> arw | |
<Arrow [2021-04-20T22:27:34.787885+00:00]> | |
>>> later = arw.dehumanize("in a month") | |
>>> later | |
<Arrow [2021-05-18T22:27:34.787885+00:00]> | |
""" | |
# Create a locale object based off given local | |
locale_obj = locales.get_locale(locale) | |
# Check to see if locale is supported | |
normalized_locale_name = locale.lower().replace("_", "-") | |
if normalized_locale_name not in DEHUMANIZE_LOCALES: | |
raise ValueError( | |
f"Dehumanize does not currently support the {locale} locale, please consider making a contribution to add support for this locale." | |
) | |
current_time = self.fromdatetime(self._datetime) | |
# Create an object containing the relative time info | |
time_object_info = dict.fromkeys( | |
["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0 | |
) | |
# Create an object representing if unit has been seen | |
unit_visited = dict.fromkeys( | |
["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"], | |
False, | |
) | |
# Create a regex pattern object for numbers | |
num_pattern = re.compile(r"\d+") | |
# Search input string for each time unit within locale | |
for unit, unit_object in locale_obj.timeframes.items(): | |
# Need to check the type of unit_object to create the correct dictionary | |
if isinstance(unit_object, Mapping): | |
strings_to_search = unit_object | |
else: | |
strings_to_search = {unit: str(unit_object)} | |
# Search for any matches that exist for that locale's unit. | |
# Needs to cycle all through strings as some locales have strings that | |
# could overlap in a regex match, since input validation isn't being performed. | |
for time_delta, time_string in strings_to_search.items(): | |
# Replace {0} with regex \d representing digits | |
search_string = str(time_string) | |
search_string = search_string.format(r"\d+") | |
# Create search pattern and find within string | |
pattern = re.compile(rf"(^|\b|\d){search_string}") | |
match = pattern.search(input_string) | |
# If there is no match continue to next iteration | |
if not match: | |
continue | |
match_string = match.group() | |
num_match = num_pattern.search(match_string) | |
# If no number matches | |
# Need for absolute value as some locales have signs included in their objects | |
if not num_match: | |
change_value = ( | |
1 if not time_delta.isnumeric() else abs(int(time_delta)) | |
) | |
else: | |
change_value = int(num_match.group()) | |
# No time to update if now is the unit | |
if unit == "now": | |
unit_visited[unit] = True | |
continue | |
# Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds) | |
time_unit_to_change = str(unit) | |
time_unit_to_change += ( | |
"s" if (str(time_unit_to_change)[-1] != "s") else "" | |
) | |
time_object_info[time_unit_to_change] = change_value | |
unit_visited[time_unit_to_change] = True | |
# Assert error if string does not modify any units | |
if not any([True for k, v in unit_visited.items() if v]): | |
raise ValueError( | |
"Input string not valid. Note: Some locales do not support the week granularity in Arrow. " | |
"If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error." | |
) | |
# Sign logic | |
future_string = locale_obj.future | |
future_string = future_string.format(".*") | |
future_pattern = re.compile(rf"^{future_string}$") | |
future_pattern_match = future_pattern.findall(input_string) | |
past_string = locale_obj.past | |
past_string = past_string.format(".*") | |
past_pattern = re.compile(rf"^{past_string}$") | |
past_pattern_match = past_pattern.findall(input_string) | |
# If a string contains the now unit, there will be no relative units, hence the need to check if the now unit | |
# was visited before raising a ValueError | |
if past_pattern_match: | |
sign_val = -1 | |
elif future_pattern_match: | |
sign_val = 1 | |
elif unit_visited["now"]: | |
sign_val = 0 | |
else: | |
raise ValueError( | |
"Invalid input String. String does not contain any relative time information. " | |
"String should either represent a time in the future or a time in the past. " | |
"Ex: 'in 5 seconds' or '5 seconds ago'." | |
) | |
time_changes = {k: sign_val * v for k, v in time_object_info.items()} | |
return current_time.shift(**time_changes) | |
# query functions | |
def is_between( | |
self, | |
start: "Arrow", | |
end: "Arrow", | |
bounds: _BOUNDS = "()", | |
) -> bool: | |
"""Returns a boolean denoting whether the :class:`Arrow <arrow.arrow.Arrow>` object is between | |
the start and end limits. | |
:param start: an :class:`Arrow <arrow.arrow.Arrow>` object. | |
:param end: an :class:`Arrow <arrow.arrow.Arrow>` object. | |
:param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies | |
whether to include or exclude the start and end values in the range. '(' excludes | |
the start, '[' includes the start, ')' excludes the end, and ']' includes the end. | |
If the bounds are not specified, the default bound '()' is used. | |
Usage:: | |
>>> start = arrow.get(datetime(2013, 5, 5, 12, 30, 10)) | |
>>> end = arrow.get(datetime(2013, 5, 5, 12, 30, 36)) | |
>>> arrow.get(datetime(2013, 5, 5, 12, 30, 27)).is_between(start, end) | |
True | |
>>> start = arrow.get(datetime(2013, 5, 5)) | |
>>> end = arrow.get(datetime(2013, 5, 8)) | |
>>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[]') | |
True | |
>>> start = arrow.get(datetime(2013, 5, 5)) | |
>>> end = arrow.get(datetime(2013, 5, 8)) | |
>>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[)') | |
False | |
""" | |
util.validate_bounds(bounds) | |
if not isinstance(start, Arrow): | |
raise TypeError( | |
f"Cannot parse start date argument type of {type(start)!r}." | |
) | |
if not isinstance(end, Arrow): | |
raise TypeError(f"Cannot parse end date argument type of {type(start)!r}.") | |
include_start = bounds[0] == "[" | |
include_end = bounds[1] == "]" | |
target_ts = self.float_timestamp | |
start_ts = start.float_timestamp | |
end_ts = end.float_timestamp | |
return ( | |
(start_ts <= target_ts <= end_ts) | |
and (include_start or start_ts < target_ts) | |
and (include_end or target_ts < end_ts) | |
) | |
# datetime methods | |
def date(self) -> date: | |
"""Returns a ``date`` object with the same year, month and day. | |
Usage:: | |
>>> arrow.utcnow().date() | |
datetime.date(2019, 1, 23) | |
""" | |
return self._datetime.date() | |
def time(self) -> dt_time: | |
"""Returns a ``time`` object with the same hour, minute, second, microsecond. | |
Usage:: | |
>>> arrow.utcnow().time() | |
datetime.time(12, 15, 34, 68352) | |
""" | |
return self._datetime.time() | |
def timetz(self) -> dt_time: | |
"""Returns a ``time`` object with the same hour, minute, second, microsecond and | |
tzinfo. | |
Usage:: | |
>>> arrow.utcnow().timetz() | |
datetime.time(12, 5, 18, 298893, tzinfo=tzutc()) | |
""" | |
return self._datetime.timetz() | |
def astimezone(self, tz: Optional[dt_tzinfo]) -> dt_datetime: | |
"""Returns a ``datetime`` object, converted to the specified timezone. | |
:param tz: a ``tzinfo`` object. | |
Usage:: | |
>>> pacific=arrow.now('US/Pacific') | |
>>> nyc=arrow.now('America/New_York').tzinfo | |
>>> pacific.astimezone(nyc) | |
datetime.datetime(2019, 1, 20, 10, 24, 22, 328172, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York')) | |
""" | |
return self._datetime.astimezone(tz) | |
def utcoffset(self) -> Optional[timedelta]: | |
"""Returns a ``timedelta`` object representing the whole number of minutes difference from | |
UTC time. | |
Usage:: | |
>>> arrow.now('US/Pacific').utcoffset() | |
datetime.timedelta(-1, 57600) | |
""" | |
return self._datetime.utcoffset() | |
def dst(self) -> Optional[timedelta]: | |
"""Returns the daylight savings time adjustment. | |
Usage:: | |
>>> arrow.utcnow().dst() | |
datetime.timedelta(0) | |
""" | |
return self._datetime.dst() | |
def timetuple(self) -> struct_time: | |
"""Returns a ``time.struct_time``, in the current timezone. | |
Usage:: | |
>>> arrow.utcnow().timetuple() | |
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) | |
""" | |
return self._datetime.timetuple() | |
def utctimetuple(self) -> struct_time: | |
"""Returns a ``time.struct_time``, in UTC time. | |
Usage:: | |
>>> arrow.utcnow().utctimetuple() | |
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) | |
""" | |
return self._datetime.utctimetuple() | |
def toordinal(self) -> int: | |
"""Returns the proleptic Gregorian ordinal of the date. | |
Usage:: | |
>>> arrow.utcnow().toordinal() | |
737078 | |
""" | |
return self._datetime.toordinal() | |
def weekday(self) -> int: | |
"""Returns the day of the week as an integer (0-6). | |
Usage:: | |
>>> arrow.utcnow().weekday() | |
5 | |
""" | |
return self._datetime.weekday() | |
def isoweekday(self) -> int: | |
"""Returns the ISO day of the week as an integer (1-7). | |
Usage:: | |
>>> arrow.utcnow().isoweekday() | |
6 | |
""" | |
return self._datetime.isoweekday() | |
def isocalendar(self) -> Tuple[int, int, int]: | |
"""Returns a 3-tuple, (ISO year, ISO week number, ISO weekday). | |
Usage:: | |
>>> arrow.utcnow().isocalendar() | |
(2019, 3, 6) | |
""" | |
return self._datetime.isocalendar() | |
def isoformat(self, sep: str = "T", timespec: str = "auto") -> str: | |
"""Returns an ISO 8601 formatted representation of the date and time. | |
Usage:: | |
>>> arrow.utcnow().isoformat() | |
'2019-01-19T18:30:52.442118+00:00' | |
""" | |
return self._datetime.isoformat(sep, timespec) | |
def ctime(self) -> str: | |
"""Returns a ctime formatted representation of the date and time. | |
Usage:: | |
>>> arrow.utcnow().ctime() | |
'Sat Jan 19 18:26:50 2019' | |
""" | |
return self._datetime.ctime() | |
def strftime(self, format: str) -> str: | |
"""Formats in the style of ``datetime.strftime``. | |
:param format: the format string. | |
Usage:: | |
>>> arrow.utcnow().strftime('%d-%m-%Y %H:%M:%S') | |
'23-01-2019 12:28:17' | |
""" | |
return self._datetime.strftime(format) | |
def for_json(self) -> str: | |
"""Serializes for the ``for_json`` protocol of simplejson. | |
Usage:: | |
>>> arrow.utcnow().for_json() | |
'2019-01-19T18:25:36.760079+00:00' | |
""" | |
return self.isoformat() | |
# math | |
def __add__(self, other: Any) -> "Arrow": | |
if isinstance(other, (timedelta, relativedelta)): | |
return self.fromdatetime(self._datetime + other, self._datetime.tzinfo) | |
return NotImplemented | |
def __radd__(self, other: Union[timedelta, relativedelta]) -> "Arrow": | |
return self.__add__(other) | |
def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow": | |
pass # pragma: no cover | |
def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta: | |
pass # pragma: no cover | |
def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]: | |
if isinstance(other, (timedelta, relativedelta)): | |
return self.fromdatetime(self._datetime - other, self._datetime.tzinfo) | |
elif isinstance(other, dt_datetime): | |
return self._datetime - other | |
elif isinstance(other, Arrow): | |
return self._datetime - other._datetime | |
return NotImplemented | |
def __rsub__(self, other: Any) -> timedelta: | |
if isinstance(other, dt_datetime): | |
return other - self._datetime | |
return NotImplemented | |
# comparisons | |
def __eq__(self, other: Any) -> bool: | |
if not isinstance(other, (Arrow, dt_datetime)): | |
return False | |
return self._datetime == self._get_datetime(other) | |
def __ne__(self, other: Any) -> bool: | |
if not isinstance(other, (Arrow, dt_datetime)): | |
return True | |
return not self.__eq__(other) | |
def __gt__(self, other: Any) -> bool: | |
if not isinstance(other, (Arrow, dt_datetime)): | |
return NotImplemented | |
return self._datetime > self._get_datetime(other) | |
def __ge__(self, other: Any) -> bool: | |
if not isinstance(other, (Arrow, dt_datetime)): | |
return NotImplemented | |
return self._datetime >= self._get_datetime(other) | |
def __lt__(self, other: Any) -> bool: | |
if not isinstance(other, (Arrow, dt_datetime)): | |
return NotImplemented | |
return self._datetime < self._get_datetime(other) | |
def __le__(self, other: Any) -> bool: | |
if not isinstance(other, (Arrow, dt_datetime)): | |
return NotImplemented | |
return self._datetime <= self._get_datetime(other) | |
# internal methods | |
def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo: | |
"""Get normalized tzinfo object from various inputs.""" | |
if tz_expr is None: | |
return dateutil_tz.tzutc() | |
if isinstance(tz_expr, dt_tzinfo): | |
return tz_expr | |
else: | |
try: | |
return parser.TzinfoParser.parse(tz_expr) | |
except parser.ParserError: | |
raise ValueError(f"{tz_expr!r} not recognized as a timezone.") | |
def _get_datetime( | |
cls, expr: Union["Arrow", dt_datetime, int, float, str] | |
) -> dt_datetime: | |
"""Get datetime object from a specified expression.""" | |
if isinstance(expr, Arrow): | |
return expr.datetime | |
elif isinstance(expr, dt_datetime): | |
return expr | |
elif util.is_timestamp(expr): | |
timestamp = float(expr) | |
return cls.utcfromtimestamp(timestamp).datetime | |
else: | |
raise ValueError(f"{expr!r} not recognized as a datetime or timestamp.") | |
def _get_frames(cls, name: _T_FRAMES) -> Tuple[str, str, int]: | |
"""Finds relevant timeframe and steps for use in range and span methods. | |
Returns a 3 element tuple in the form (frame, plural frame, step), for example ("day", "days", 1) | |
""" | |
if name in cls._ATTRS: | |
return name, f"{name}s", 1 | |
elif name[-1] == "s" and name[:-1] in cls._ATTRS: | |
return name[:-1], name, 1 | |
elif name in ["week", "weeks"]: | |
return "week", "weeks", 1 | |
elif name in ["quarter", "quarters"]: | |
return "quarter", "months", 3 | |
else: | |
supported = ", ".join( | |
[ | |
"year(s)", | |
"month(s)", | |
"day(s)", | |
"hour(s)", | |
"minute(s)", | |
"second(s)", | |
"microsecond(s)", | |
"week(s)", | |
"quarter(s)", | |
] | |
) | |
raise ValueError( | |
f"Range or span over frame {name} not supported. Supported frames: {supported}." | |
) | |
def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]: | |
"""Sets default end and limit values for range method.""" | |
if end is None: | |
if limit is None: | |
raise ValueError("One of 'end' or 'limit' is required.") | |
return cls.max, limit | |
else: | |
if limit is None: | |
return end, sys.maxsize | |
return end, limit | |
def _is_last_day_of_month(date: "Arrow") -> bool: | |
"""Returns a boolean indicating whether the datetime is the last day of the month.""" | |
return date.day == calendar.monthrange(date.year, date.month)[1] | |
Arrow.min = Arrow.fromdatetime(dt_datetime.min) | |
Arrow.max = Arrow.fromdatetime(dt_datetime.max) | |