"""
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
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, asin'+07:00'.
- A ``str``, one of the following: 'local', 'utc', 'UTC'.
# factories: single object, both original and from datetime.
@classmethod def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow": """Constructs an :class:`Arrow ` object, representing "now" in the given
timezone.
:param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
@classmethod def fromtimestamp(
cls,
timestamp: Union[int, float, str],
tzinfo: Optional[TZ_EXPR] = None,
) -> "Arrow": """Constructs an :class:`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.
@classmethod def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow": """Constructs an :class:`Arrow ` object from a timestamp, in UTC time.
:param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
"""
ifnot util.is_timestamp(timestamp): raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")
@classmethod def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow": """Constructs an :class:`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.
@classmethod def strptime(
cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None
) -> "Arrow": """Constructs an :class:`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.
@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 ` 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``.
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 andnot 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 ` 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 asnot 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.
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 ` 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...).
def ceil(self, frame: _T_FRAMES) -> "Arrow": """Returns a new :class:`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...).
@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 ` 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 asnot 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``.
tzinfo = cls._get_tzinfo(start.tzinfo if tz isNoneelse tz)
start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0]
end = cls.fromdatetime(end, tzinfo)
_range = cls.range(frame, start, end, tz, limit) ifnot 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 ` 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 asnot to extend beyond ``end``.
- 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, asin'+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")
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 notin ["tzinfo", "fold"]: raise ValueError(f"Unknown attribute: {key!r}.")
current = self._datetime.replace(**absolute_kwargs)
tzinfo = kwargs.get("tzinfo")
if tzinfo isnotNone:
tzinfo = self._get_tzinfo(tzinfo)
current = current.replace(tzinfo=tzinfo)
fold = kwargs.get("fold")
if fold isnotNone:
current = current.replace(fold=fold)
return self.fromdatetime(current)
def shift(self, **kwargs: Any) -> "Arrow": """Returns a new :class:`Arrow ` object with attributes updated
according to inputs.
Use pluralized property names to relatively shift their current value:
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:
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)
ifnot 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 ` 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]>
def format(
self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE
) -> str: """Returns a string representation of the :class:`Arrow ` object,
formatted according to the provided format string.
:param fmt: the format string.
:param locale: the locale to format.
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
else: ifnot granularity: raise ValueError( "Empty granularity list provided. " "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'."
)
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 ` 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'.
>>> 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 notin 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 ifnot match: continue
# If no number matches # Need for absolute value as some locales have signs included in their objects ifnot num_match:
change_value = (
1 ifnot 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 ifnot any([Truefor 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."
)
# 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 ` 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.
# internal methods
@staticmethod def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo: """Get normalized tzinfo object from various inputs.""" if tz_expr isNone: 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 isNone: if limit isNone: raise ValueError("One of 'end' or 'limit' is required.")
return cls.max, limit
else: if limit isNone: 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]
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.34Bemerkung:
Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.