| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903 |
- """
- 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, timezone
- from datetime import tzinfo as dt_tzinfo
- from math import trunc
- from time import struct_time
- from typing import (
- Any,
- ClassVar,
- Final,
- Generator,
- Iterable,
- List,
- Literal,
- 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
- 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
- _MONTHS_PER_YEAR: Final[int] = 12
- _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 = timezone.utc
- # 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 = dt_datetime.now().astimezone().tzinfo
- 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(timezone.utc)
- 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 = dt_datetime.now().astimezone().tzinfo
- 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.fromtimestamp(timestamp, timezone.utc)
- return cls(
- dt.year,
- dt.month,
- dt.day,
- dt.hour,
- dt.minute,
- dt.second,
- dt.microsecond,
- timezone.utc,
- 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 = timezone.utc
- 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 = timezone.utc
- 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]
- check_imaginary=True, **{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]>)
- """
- util.validate_bounds(bounds)
- frame_absolute, frame_relative, relative_steps = self._get_frames(frame)
- if frame_absolute == "week":
- if not 1 <= week_start <= 7:
- raise ValueError("week_start argument must be between 1 and 7.")
- 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(
- check_imaginary=True, **{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, **kwargs: Any) -> "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...).
- :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
- Monday is 1 and Sunday is 7.
- Usage::
- >>> arrow.utcnow().floor('hour')
- <Arrow [2013-05-09T03:00:00+00:00]>
- >>> arrow.utcnow().floor('week', week_start=7)
- <Arrow [2021-02-21T00:00:00+00:00]>
- """
- return self.span(frame, **kwargs)[0]
- def ceil(self, frame: _T_FRAMES, **kwargs: Any) -> "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...).
- :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where
- Monday is 1 and Sunday is 7.
- Usage::
- >>> arrow.utcnow().ceil('hour')
- <Arrow [2013-05-09T03:59:59.999999+00:00]>
- >>> arrow.utcnow().ceil('week', week_start=7)
- <Arrow [2021-02-27T23:59:59.999999+00:00]>
- """
- return self.span(frame, **kwargs)[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) -> Any:
- 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[Any] = 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, check_imaginary: bool = True, **kwargs: Any) -> "Arrow":
- """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
- according to inputs.
- Parameters:
- check_imaginary (bool): If True (default), will check for and resolve
- imaginary times (like during DST transitions). If False, skips this check.
- 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 check_imaginary is True, perform the check for imaginary times (DST transitions)
- if check_imaginary and 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. For a list of formatting values,
- see :ref:`supported-tokens`
- :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.now(timezone.utc).replace(tzinfo=timezone.utc)
- 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)
- calendar_diff = (
- relativedelta(dt, self._datetime)
- if self._datetime < dt
- else relativedelta(self._datetime, dt)
- )
- calendar_months = (
- calendar_diff.years * self._MONTHS_PER_YEAR + calendar_diff.months
- )
- # For months, if more than 2 weeks, count as a full month
- if calendar_diff.days > 14:
- calendar_months += 1
- calendar_months = min(calendar_months, self._MONTHS_PER_YEAR)
- if 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 calendar_months >= 1 and diff < self._SECS_PER_YEAR:
- if calendar_months == 1:
- return locale.describe(
- "month", sign, only_distance=only_distance
- )
- else:
- months = sign * calendar_months
- return locale.describe(
- "months", months, 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_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(check_imaginary=True, **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 timezone.utc
- 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 cast(int, date.day) == calendar.monthrange(date.year, date.month)[1]
- Arrow.min = Arrow.fromdatetime(dt_datetime.min)
- Arrow.max = Arrow.fromdatetime(dt_datetime.max)
|