mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-01-06 11:09:57 -08:00
c1b8be0227
* Bump arrow from 1.2.3 to 1.3.0 Bumps [arrow](https://github.com/arrow-py/arrow) from 1.2.3 to 1.3.0. - [Release notes](https://github.com/arrow-py/arrow/releases) - [Changelog](https://github.com/arrow-py/arrow/blob/master/CHANGELOG.rst) - [Commits](https://github.com/arrow-py/arrow/compare/1.2.3...1.3.0) --- updated-dependencies: - dependency-name: arrow dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Update arrow==1.3.0 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci]
1870 lines
62 KiB
Python
1870 lines
62 KiB
Python
"""
|
|
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.
|
|
|
|
@classmethod
|
|
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),
|
|
)
|
|
|
|
@classmethod
|
|
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),
|
|
)
|
|
|
|
@classmethod
|
|
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),
|
|
)
|
|
|
|
@classmethod
|
|
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),
|
|
)
|
|
|
|
@classmethod
|
|
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),
|
|
)
|
|
|
|
@classmethod
|
|
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)
|
|
|
|
@classmethod
|
|
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),
|
|
)
|
|
|
|
@classmethod
|
|
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
|
|
|
|
@classmethod
|
|
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]
|
|
|
|
@classmethod
|
|
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
|
|
|
|
@classmethod
|
|
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))
|
|
|
|
@property
|
|
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)
|
|
|
|
@property
|
|
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
|
|
|
|
@property
|
|
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()
|
|
|
|
@property
|
|
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())
|
|
|
|
@property
|
|
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()
|
|
|
|
@property
|
|
def fold(self) -> int:
|
|
"""Returns the ``fold`` value of the :class:`Arrow <arrow.arrow.Arrow>` object."""
|
|
|
|
return self._datetime.fold
|
|
|
|
@property
|
|
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)
|
|
|
|
@property
|
|
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)
|
|
|
|
@overload
|
|
def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow":
|
|
pass # pragma: no cover
|
|
|
|
@overload
|
|
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
|
|
@staticmethod
|
|
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.")
|
|
|
|
@classmethod
|
|
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.")
|
|
|
|
@classmethod
|
|
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}."
|
|
)
|
|
|
|
@classmethod
|
|
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
|
|
|
|
@staticmethod
|
|
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)
|