| """ |
| 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): |
| from typing_extensions import Final, Literal |
| else: |
| from typing import Final, Literal |
|
|
|
|
| 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() |
| |
| 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 |
| ) |
|
|
| |
|
|
| @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), |
| ) |
|
|
| |
|
|
| @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( |
| **{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) |
|
|
| if frame_absolute == "week": |
| |
| 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 |
|
|
| |
|
|
| 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__() |
|
|
| |
|
|
| 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() |
| |
| """ |
|
|
| |
| 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) |
|
|
| |
|
|
| 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}." |
| ) |
|
|
| |
| 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), |
| ) |
|
|
| |
|
|
| 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: |
| |
| 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) |
|
|
| 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" |
| 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]> |
| |
| """ |
|
|
| |
| locale_obj = locales.get_locale(locale) |
|
|
| |
| 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) |
|
|
| |
| time_object_info = dict.fromkeys( |
| ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0 |
| ) |
|
|
| |
| unit_visited = dict.fromkeys( |
| ["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"], |
| False, |
| ) |
|
|
| |
| num_pattern = re.compile(r"\d+") |
|
|
| |
| for unit, unit_object in locale_obj.timeframes.items(): |
| |
| if isinstance(unit_object, Mapping): |
| strings_to_search = unit_object |
| else: |
| strings_to_search = {unit: str(unit_object)} |
|
|
| |
| |
| |
| for time_delta, time_string in strings_to_search.items(): |
| |
| search_string = str(time_string) |
| search_string = search_string.format(r"\d+") |
|
|
| |
| pattern = re.compile(rf"(^|\b|\d){search_string}") |
| match = pattern.search(input_string) |
|
|
| |
| if not match: |
| continue |
|
|
| match_string = match.group() |
| num_match = num_pattern.search(match_string) |
|
|
| |
| |
| if not num_match: |
| change_value = ( |
| 1 if not time_delta.isnumeric() else abs(int(time_delta)) |
| ) |
| else: |
| change_value = int(num_match.group()) |
|
|
| |
| if unit == "now": |
| unit_visited[unit] = True |
| continue |
|
|
| |
| 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 |
|
|
| |
| 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." |
| ) |
|
|
| |
| 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 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) |
|
|
| |
|
|
| 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) |
| ) |
|
|
| |
|
|
| 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() |
|
|
| |
|
|
| 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 |
|
|
| @overload |
| def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta: |
| pass |
|
|
| 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 |
|
|
| |
|
|
| 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) |
|
|
| |
| @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) |
|
|