summaryrefslogtreecommitdiffstats
path: root/pendulum/datetime.py
diff options
context:
space:
mode:
Diffstat (limited to 'pendulum/datetime.py')
-rw-r--r--pendulum/datetime.py2944
1 files changed, 1381 insertions, 1563 deletions
diff --git a/pendulum/datetime.py b/pendulum/datetime.py
index 6f1d5cf..52ad3cc 100644
--- a/pendulum/datetime.py
+++ b/pendulum/datetime.py
@@ -1,1563 +1,1381 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-from __future__ import division
-
-import calendar
-import datetime
-
-from typing import Optional
-from typing import TypeVar
-from typing import Union
-
-import pendulum
-
-from .constants import ATOM
-from .constants import COOKIE
-from .constants import MINUTES_PER_HOUR
-from .constants import MONTHS_PER_YEAR
-from .constants import RFC822
-from .constants import RFC850
-from .constants import RFC1036
-from .constants import RFC1123
-from .constants import RFC2822
-from .constants import RSS
-from .constants import SATURDAY
-from .constants import SECONDS_PER_DAY
-from .constants import SECONDS_PER_MINUTE
-from .constants import SUNDAY
-from .constants import W3C
-from .constants import YEARS_PER_CENTURY
-from .constants import YEARS_PER_DECADE
-from .date import Date
-from .exceptions import PendulumException
-from .helpers import add_duration
-from .helpers import timestamp
-from .period import Period
-from .time import Time
-from .tz import UTC
-from .tz.timezone import Timezone
-from .utils._compat import _HAS_FOLD
-
-
-_D = TypeVar("_D", bound="DateTime")
-
-
-class DateTime(datetime.datetime, Date):
-
- EPOCH = None # type: DateTime
-
- # Formats
-
- _FORMATS = {
- "atom": ATOM,
- "cookie": COOKIE,
- "iso8601": lambda dt: dt.isoformat(),
- "rfc822": RFC822,
- "rfc850": RFC850,
- "rfc1036": RFC1036,
- "rfc1123": RFC1123,
- "rfc2822": RFC2822,
- "rfc3339": lambda dt: dt.isoformat(),
- "rss": RSS,
- "w3c": W3C,
- }
-
- _EPOCH = datetime.datetime(1970, 1, 1, tzinfo=UTC)
-
- _MODIFIERS_VALID_UNITS = [
- "second",
- "minute",
- "hour",
- "day",
- "week",
- "month",
- "year",
- "decade",
- "century",
- ]
-
- if not _HAS_FOLD:
-
- def __new__(
- cls,
- year,
- month,
- day,
- hour=0,
- minute=0,
- second=0,
- microsecond=0,
- tzinfo=None,
- fold=0,
- ):
- self = datetime.datetime.__new__(
- cls, year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
- )
-
- self._fold = fold
-
- return self
-
- @classmethod
- def now(cls, tz=None): # type: (Optional[Union[str, Timezone]]) -> DateTime
- """
- Get a DateTime instance for the current date and time.
- """
- return pendulum.now(tz)
-
- @classmethod
- def utcnow(cls): # type: () -> DateTime
- """
- Get a DateTime instance for the current date and time in UTC.
- """
- return pendulum.now(UTC)
-
- @classmethod
- def today(cls): # type: () -> DateTime
- return pendulum.now()
-
- @classmethod
- def strptime(cls, time, fmt): # type: (str, str) -> DateTime
- return pendulum.instance(datetime.datetime.strptime(time, fmt))
-
- # Getters/Setters
-
- def set(
- self,
- year=None,
- month=None,
- day=None,
- hour=None,
- minute=None,
- second=None,
- microsecond=None,
- tz=None,
- ):
- if year is None:
- year = self.year
- if month is None:
- month = self.month
- if day is None:
- day = self.day
- if hour is None:
- hour = self.hour
- if minute is None:
- minute = self.minute
- if second is None:
- second = self.second
- if microsecond is None:
- microsecond = self.microsecond
- if tz is None:
- tz = self.tz
-
- return pendulum.datetime(
- year, month, day, hour, minute, second, microsecond, tz=tz
- )
-
- if not _HAS_FOLD:
-
- @property
- def fold(self):
- return self._fold
-
- def timestamp(self):
- if self.tzinfo is None:
- s = timestamp(self)
-
- return s + self.microsecond / 1e6
- else:
- kwargs = {"tzinfo": self.tzinfo}
-
- if _HAS_FOLD:
- kwargs["fold"] = self.fold
-
- dt = datetime.datetime(
- self.year,
- self.month,
- self.day,
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- **kwargs
- )
- return (dt - self._EPOCH).total_seconds()
-
- @property
- def float_timestamp(self):
- return self.timestamp()
-
- @property
- def int_timestamp(self):
- # Workaround needed to avoid inaccuracy
- # for far into the future datetimes
- kwargs = {"tzinfo": self.tzinfo}
-
- if _HAS_FOLD:
- kwargs["fold"] = self.fold
-
- dt = datetime.datetime(
- self.year,
- self.month,
- self.day,
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- **kwargs
- )
-
- delta = dt - self._EPOCH
-
- return delta.days * SECONDS_PER_DAY + delta.seconds
-
- @property
- def offset(self):
- return self.get_offset()
-
- @property
- def offset_hours(self):
- return self.get_offset() / SECONDS_PER_MINUTE / MINUTES_PER_HOUR
-
- @property
- def timezone(self): # type: () -> Optional[Timezone]
- if not isinstance(self.tzinfo, Timezone):
- return
-
- return self.tzinfo
-
- @property
- def tz(self): # type: () -> Optional[Timezone]
- return self.timezone
-
- @property
- def timezone_name(self): # type: () -> Optional[str]
- tz = self.timezone
-
- if tz is None:
- return None
-
- return tz.name
-
- @property
- def age(self):
- return self.date().diff(self.now(self.tz).date(), abs=False).in_years()
-
- def is_local(self):
- return self.offset == self.in_timezone(pendulum.local_timezone()).offset
-
- def is_utc(self):
- return self.offset == UTC.offset
-
- def is_dst(self):
- return self.dst() != datetime.timedelta()
-
- def get_offset(self):
- return int(self.utcoffset().total_seconds())
-
- def date(self):
- return Date(self.year, self.month, self.day)
-
- def time(self):
- return Time(self.hour, self.minute, self.second, self.microsecond)
-
- def naive(self): # type: (_D) -> _D
- """
- Return the DateTime without timezone information.
- """
- return self.__class__(
- self.year,
- self.month,
- self.day,
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- )
-
- def on(self, year, month, day):
- """
- Returns a new instance with the current date set to a different date.
-
- :param year: The year
- :type year: int
-
- :param month: The month
- :type month: int
-
- :param day: The day
- :type day: int
-
- :rtype: DateTime
- """
- return self.set(year=int(year), month=int(month), day=int(day))
-
- def at(self, hour, minute=0, second=0, microsecond=0):
- """
- Returns a new instance with the current time to a different time.
-
- :param hour: The hour
- :type hour: int
-
- :param minute: The minute
- :type minute: int
-
- :param second: The second
- :type second: int
-
- :param microsecond: The microsecond
- :type microsecond: int
-
- :rtype: DateTime
- """
- return self.set(
- hour=hour, minute=minute, second=second, microsecond=microsecond
- )
-
- def in_timezone(self, tz): # type: (Union[str, Timezone]) -> DateTime
- """
- Set the instance's timezone from a string or object.
- """
- tz = pendulum._safe_timezone(tz)
-
- return tz.convert(self, dst_rule=pendulum.POST_TRANSITION)
-
- def in_tz(self, tz): # type: (Union[str, Timezone]) -> DateTime
- """
- Set the instance's timezone from a string or object.
- """
- return self.in_timezone(tz)
-
- # STRING FORMATTING
-
- def to_time_string(self):
- """
- Format the instance as time.
-
- :rtype: str
- """
- return self.format("HH:mm:ss")
-
- def to_datetime_string(self):
- """
- Format the instance as date and time.
-
- :rtype: str
- """
- return self.format("YYYY-MM-DD HH:mm:ss")
-
- def to_day_datetime_string(self):
- """
- Format the instance as day, date and time (in english).
-
- :rtype: str
- """
- return self.format("ddd, MMM D, YYYY h:mm A", locale="en")
-
- def to_atom_string(self):
- """
- Format the instance as ATOM.
-
- :rtype: str
- """
- return self._to_string("atom")
-
- def to_cookie_string(self):
- """
- Format the instance as COOKIE.
-
- :rtype: str
- """
- return self._to_string("cookie", locale="en")
-
- def to_iso8601_string(self):
- """
- Format the instance as ISO 8601.
-
- :rtype: str
- """
- string = self._to_string("iso8601")
-
- if self.tz and self.tz.name == "UTC":
- string = string.replace("+00:00", "Z")
-
- return string
-
- def to_rfc822_string(self):
- """
- Format the instance as RFC 822.
-
- :rtype: str
- """
- return self._to_string("rfc822")
-
- def to_rfc850_string(self):
- """
- Format the instance as RFC 850.
-
- :rtype: str
- """
- return self._to_string("rfc850")
-
- def to_rfc1036_string(self):
- """
- Format the instance as RFC 1036.
-
- :rtype: str
- """
- return self._to_string("rfc1036")
-
- def to_rfc1123_string(self):
- """
- Format the instance as RFC 1123.
-
- :rtype: str
- """
- return self._to_string("rfc1123")
-
- def to_rfc2822_string(self):
- """
- Format the instance as RFC 2822.
-
- :rtype: str
- """
- return self._to_string("rfc2822")
-
- def to_rfc3339_string(self):
- """
- Format the instance as RFC 3339.
-
- :rtype: str
- """
- return self._to_string("rfc3339")
-
- def to_rss_string(self):
- """
- Format the instance as RSS.
-
- :rtype: str
- """
- return self._to_string("rss")
-
- def to_w3c_string(self):
- """
- Format the instance as W3C.
-
- :rtype: str
- """
- return self._to_string("w3c")
-
- def _to_string(self, fmt, locale=None):
- """
- Format the instance to a common string format.
-
- :param fmt: The name of the string format
- :type fmt: string
-
- :param locale: The locale to use
- :type locale: str or None
-
- :rtype: str
- """
- if fmt not in self._FORMATS:
- raise ValueError("Format [{}] is not supported".format(fmt))
-
- fmt = self._FORMATS[fmt]
- if callable(fmt):
- return fmt(self)
-
- return self.format(fmt, locale=locale)
-
- def __str__(self):
- return self.isoformat("T")
-
- def __repr__(self):
- us = ""
- if self.microsecond:
- us = ", {}".format(self.microsecond)
-
- repr_ = "{klass}(" "{year}, {month}, {day}, " "{hour}, {minute}, {second}{us}"
-
- if self.tzinfo is not None:
- repr_ += ", tzinfo={tzinfo}"
-
- repr_ += ")"
-
- return repr_.format(
- klass=self.__class__.__name__,
- year=self.year,
- month=self.month,
- day=self.day,
- hour=self.hour,
- minute=self.minute,
- second=self.second,
- us=us,
- tzinfo=self.tzinfo,
- )
-
- # Comparisons
- def closest(self, dt1, dt2, *dts):
- """
- Get the farthest date from the instance.
-
- :type dt1: datetime.datetime
- :type dt2: datetime.datetime
- :type dts: list[datetime.datetime,]
-
- :rtype: DateTime
- """
- dt1 = pendulum.instance(dt1)
- dt2 = pendulum.instance(dt2)
- dts = [dt1, dt2] + [pendulum.instance(x) for x in dts]
- dts = [(abs(self - dt), dt) for dt in dts]
-
- return min(dts)[1]
-
- def farthest(self, dt1, dt2, *dts):
- """
- Get the farthest date from the instance.
-
- :type dt1: datetime.datetime
- :type dt2: datetime.datetime
- :type dts: list[datetime.datetime,]
-
- :rtype: DateTime
- """
- dt1 = pendulum.instance(dt1)
- dt2 = pendulum.instance(dt2)
-
- dts = [dt1, dt2] + [pendulum.instance(x) for x in dts]
- dts = [(abs(self - dt), dt) for dt in dts]
-
- return max(dts)[1]
-
- def is_future(self):
- """
- Determines if the instance is in the future, ie. greater than now.
-
- :rtype: bool
- """
- return self > self.now(self.timezone)
-
- def is_past(self):
- """
- Determines if the instance is in the past, ie. less than now.
-
- :rtype: bool
- """
- return self < self.now(self.timezone)
-
- def is_long_year(self):
- """
- Determines if the instance is a long year
-
- See link `https://en.wikipedia.org/wiki/ISO_8601#Week_dates`_
-
- :rtype: bool
- """
- return (
- pendulum.datetime(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1]
- == 53
- )
-
- def is_same_day(self, dt):
- """
- Checks if the passed in date is the same day
- as the instance current day.
-
- :type dt: DateTime or datetime or str or int
-
- :rtype: bool
- """
- dt = pendulum.instance(dt)
-
- return self.to_date_string() == dt.to_date_string()
-
- def is_anniversary(self, dt=None):
- """
- Check if its the anniversary.
- Compares the date/month values of the two dates.
-
- :rtype: bool
- """
- if dt is None:
- dt = self.now(self.tz)
-
- instance = pendulum.instance(dt)
-
- return (self.month, self.day) == (instance.month, instance.day)
-
- # the additional method for checking if today is the anniversary day
- # the alias is provided to start using a new name and keep the backward compatibility
- # the old name can be completely replaced with the new in one of the future versions
- is_birthday = is_anniversary
-
- # ADDITIONS AND SUBSTRACTIONS
-
- def add(
- self,
- years=0,
- months=0,
- weeks=0,
- days=0,
- hours=0,
- minutes=0,
- seconds=0,
- microseconds=0,
- ): # type: (_D, int, int, int, int, int, int, int, int) -> _D
- """
- Add a duration to the instance.
-
- If we're adding units of variable length (i.e., years, months),
- move forward from curren time,
- otherwise move forward from utc, for accuracy
- when moving across DST boundaries.
- """
- units_of_variable_length = any([years, months, weeks, days])
-
- current_dt = datetime.datetime(
- self.year,
- self.month,
- self.day,
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- )
- if not units_of_variable_length:
- offset = self.utcoffset()
- if offset:
- current_dt = current_dt - offset
-
- dt = add_duration(
- current_dt,
- years=years,
- months=months,
- weeks=weeks,
- days=days,
- hours=hours,
- minutes=minutes,
- seconds=seconds,
- microseconds=microseconds,
- )
-
- if units_of_variable_length or self.tzinfo is None:
- return pendulum.datetime(
- dt.year,
- dt.month,
- dt.day,
- dt.hour,
- dt.minute,
- dt.second,
- dt.microsecond,
- tz=self.tz,
- )
-
- dt = self.__class__(
- dt.year,
- dt.month,
- dt.day,
- dt.hour,
- dt.minute,
- dt.second,
- dt.microsecond,
- tzinfo=UTC,
- )
-
- dt = self.tz.convert(dt)
-
- return self.__class__(
- dt.year,
- dt.month,
- dt.day,
- dt.hour,
- dt.minute,
- dt.second,
- dt.microsecond,
- tzinfo=self.tz,
- fold=dt.fold,
- )
-
- def subtract(
- self,
- years=0,
- months=0,
- weeks=0,
- days=0,
- hours=0,
- minutes=0,
- seconds=0,
- microseconds=0,
- ):
- """
- Remove duration from the instance.
-
- :param years: The number of years
- :type years: int
-
- :param months: The number of months
- :type months: int
-
- :param weeks: The number of weeks
- :type weeks: int
-
- :param days: The number of days
- :type days: int
-
- :param hours: The number of hours
- :type hours: int
-
- :param minutes: The number of minutes
- :type minutes: int
-
- :param seconds: The number of seconds
- :type seconds: int
-
- :param microseconds: The number of microseconds
- :type microseconds: int
-
- :rtype: DateTime
- """
- return self.add(
- years=-years,
- months=-months,
- weeks=-weeks,
- days=-days,
- hours=-hours,
- minutes=-minutes,
- seconds=-seconds,
- microseconds=-microseconds,
- )
-
- # Adding a final underscore to the method name
- # to avoid errors for PyPy which already defines
- # a _add_timedelta method
- def _add_timedelta_(self, delta):
- """
- Add timedelta duration to the instance.
-
- :param delta: The timedelta instance
- :type delta: pendulum.Duration or datetime.timedelta
-
- :rtype: DateTime
- """
- if isinstance(delta, pendulum.Period):
- return self.add(
- years=delta.years,
- months=delta.months,
- weeks=delta.weeks,
- days=delta.remaining_days,
- hours=delta.hours,
- minutes=delta.minutes,
- seconds=delta.remaining_seconds,
- microseconds=delta.microseconds,
- )
- elif isinstance(delta, pendulum.Duration):
- return self.add(
- years=delta.years, months=delta.months, seconds=delta._total
- )
-
- return self.add(seconds=delta.total_seconds())
-
- def _subtract_timedelta(self, delta):
- """
- Remove timedelta duration from the instance.
-
- :param delta: The timedelta instance
- :type delta: pendulum.Duration or datetime.timedelta
-
- :rtype: DateTime
- """
- if isinstance(delta, pendulum.Duration):
- return self.subtract(
- years=delta.years, months=delta.months, seconds=delta._total
- )
-
- return self.subtract(seconds=delta.total_seconds())
-
- # DIFFERENCES
-
- def diff(self, dt=None, abs=True):
- """
- Returns the difference between two DateTime objects represented as a Duration.
-
- :type dt: DateTime or None
-
- :param abs: Whether to return an absolute interval or not
- :type abs: bool
-
- :rtype: Period
- """
- if dt is None:
- dt = self.now(self.tz)
-
- return Period(self, dt, absolute=abs)
-
- def diff_for_humans(
- self,
- other=None, # type: Optional[DateTime]
- absolute=False, # type: bool
- locale=None, # type: Optional[str]
- ): # type: (...) -> str
- """
- Get the difference in a human readable format in the current locale.
-
- When comparing a value in the past to default now:
- 1 day ago
- 5 months ago
-
- When comparing a value in the future to default now:
- 1 day from now
- 5 months from now
-
- When comparing a value in the past to another value:
- 1 day before
- 5 months before
-
- When comparing a value in the future to another value:
- 1 day after
- 5 months after
- """
- is_now = other is None
-
- if is_now:
- other = self.now()
-
- diff = self.diff(other)
-
- return pendulum.format_diff(diff, is_now, absolute, locale)
-
- # Modifiers
- def start_of(self, unit):
- """
- Returns a copy of the instance with the time reset
- with the following rules:
-
- * second: microsecond set to 0
- * minute: second and microsecond set to 0
- * hour: minute, second and microsecond set to 0
- * day: time to 00:00:00
- * week: date to first day of the week and time to 00:00:00
- * month: date to first day of the month and time to 00:00:00
- * year: date to first day of the year and time to 00:00:00
- * decade: date to first day of the decade and time to 00:00:00
- * century: date to first day of century and time to 00:00:00
-
- :param unit: The unit to reset to
- :type unit: str
-
- :rtype: DateTime
- """
- if unit not in self._MODIFIERS_VALID_UNITS:
- raise ValueError('Invalid unit "{}" for start_of()'.format(unit))
-
- return getattr(self, "_start_of_{}".format(unit))()
-
- def end_of(self, unit):
- """
- Returns a copy of the instance with the time reset
- with the following rules:
-
- * second: microsecond set to 999999
- * minute: second set to 59 and microsecond set to 999999
- * hour: minute and second set to 59 and microsecond set to 999999
- * day: time to 23:59:59.999999
- * week: date to last day of the week and time to 23:59:59.999999
- * month: date to last day of the month and time to 23:59:59.999999
- * year: date to last day of the year and time to 23:59:59.999999
- * decade: date to last day of the decade and time to 23:59:59.999999
- * century: date to last day of century and time to 23:59:59.999999
-
- :param unit: The unit to reset to
- :type unit: str
-
- :rtype: DateTime
- """
- if unit not in self._MODIFIERS_VALID_UNITS:
- raise ValueError('Invalid unit "%s" for end_of()' % unit)
-
- return getattr(self, "_end_of_%s" % unit)()
-
- def _start_of_second(self):
- """
- Reset microseconds to 0.
-
- :rtype: DateTime
- """
- return self.set(microsecond=0)
-
- def _end_of_second(self):
- """
- Set microseconds to 999999.
-
- :rtype: DateTime
- """
- return self.set(microsecond=999999)
-
- def _start_of_minute(self):
- """
- Reset seconds and microseconds to 0.
-
- :rtype: DateTime
- """
- return self.set(second=0, microsecond=0)
-
- def _end_of_minute(self):
- """
- Set seconds to 59 and microseconds to 999999.
-
- :rtype: DateTime
- """
- return self.set(second=59, microsecond=999999)
-
- def _start_of_hour(self):
- """
- Reset minutes, seconds and microseconds to 0.
-
- :rtype: DateTime
- """
- return self.set(minute=0, second=0, microsecond=0)
-
- def _end_of_hour(self):
- """
- Set minutes and seconds to 59 and microseconds to 999999.
-
- :rtype: DateTime
- """
- return self.set(minute=59, second=59, microsecond=999999)
-
- def _start_of_day(self):
- """
- Reset the time to 00:00:00
-
- :rtype: DateTime
- """
- return self.at(0, 0, 0, 0)
-
- def _end_of_day(self):
- """
- Reset the time to 23:59:59.999999
-
- :rtype: DateTime
- """
- return self.at(23, 59, 59, 999999)
-
- def _start_of_month(self):
- """
- Reset the date to the first day of the month and the time to 00:00:00.
-
- :rtype: DateTime
- """
- return self.set(self.year, self.month, 1, 0, 0, 0, 0)
-
- def _end_of_month(self):
- """
- Reset the date to the last day of the month
- and the time to 23:59:59.999999.
-
- :rtype: DateTime
- """
- return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999)
-
- def _start_of_year(self):
- """
- Reset the date to the first day of the year and the time to 00:00:00.
-
- :rtype: DateTime
- """
- return self.set(self.year, 1, 1, 0, 0, 0, 0)
-
- def _end_of_year(self):
- """
- Reset the date to the last day of the year
- and the time to 23:59:59.999999
-
- :rtype: DateTime
- """
- return self.set(self.year, 12, 31, 23, 59, 59, 999999)
-
- def _start_of_decade(self):
- """
- Reset the date to the first day of the decade
- and the time to 00:00:00.
-
- :rtype: DateTime
- """
- year = self.year - self.year % YEARS_PER_DECADE
- return self.set(year, 1, 1, 0, 0, 0, 0)
-
- def _end_of_decade(self):
- """
- Reset the date to the last day of the decade
- and the time to 23:59:59.999999.
-
- :rtype: DateTime
- """
- year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1
-
- return self.set(year, 12, 31, 23, 59, 59, 999999)
-
- def _start_of_century(self):
- """
- Reset the date to the first day of the century
- and the time to 00:00:00.
-
- :rtype: DateTime
- """
- year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1
-
- return self.set(year, 1, 1, 0, 0, 0, 0)
-
- def _end_of_century(self):
- """
- Reset the date to the last day of the century
- and the time to 23:59:59.999999.
-
- :rtype: DateTime
- """
- year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY
-
- return self.set(year, 12, 31, 23, 59, 59, 999999)
-
- def _start_of_week(self):
- """
- Reset the date to the first day of the week
- and the time to 00:00:00.
-
- :rtype: DateTime
- """
- dt = self
-
- if self.day_of_week != pendulum._WEEK_STARTS_AT:
- dt = self.previous(pendulum._WEEK_STARTS_AT)
-
- return dt.start_of("day")
-
- def _end_of_week(self):
- """
- Reset the date to the last day of the week
- and the time to 23:59:59.
-
- :rtype: DateTime
- """
- dt = self
-
- if self.day_of_week != pendulum._WEEK_ENDS_AT:
- dt = self.next(pendulum._WEEK_ENDS_AT)
-
- return dt.end_of("day")
-
- def next(self, day_of_week=None, keep_time=False):
- """
- Modify to the next occurrence of a given day of the week.
- If no day_of_week is provided, modify to the next occurrence
- of the current day of the week. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :param day_of_week: The next day of week to reset to.
- :type day_of_week: int or None
-
- :param keep_time: Whether to keep the time information or not.
- :type keep_time: bool
-
- :rtype: DateTime
- """
- if day_of_week is None:
- day_of_week = self.day_of_week
-
- if day_of_week < SUNDAY or day_of_week > SATURDAY:
- raise ValueError("Invalid day of week")
-
- if keep_time:
- dt = self
- else:
- dt = self.start_of("day")
-
- dt = dt.add(days=1)
- while dt.day_of_week != day_of_week:
- dt = dt.add(days=1)
-
- return dt
-
- def previous(self, day_of_week=None, keep_time=False):
- """
- Modify to the previous occurrence of a given day of the week.
- If no day_of_week is provided, modify to the previous occurrence
- of the current day of the week. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :param day_of_week: The previous day of week to reset to.
- :type day_of_week: int or None
-
- :param keep_time: Whether to keep the time information or not.
- :type keep_time: bool
-
- :rtype: DateTime
- """
- if day_of_week is None:
- day_of_week = self.day_of_week
-
- if day_of_week < SUNDAY or day_of_week > SATURDAY:
- raise ValueError("Invalid day of week")
-
- if keep_time:
- dt = self
- else:
- dt = self.start_of("day")
-
- dt = dt.subtract(days=1)
- while dt.day_of_week != day_of_week:
- dt = dt.subtract(days=1)
-
- return dt
-
- def first_of(self, unit, day_of_week=None):
- """
- Returns an instance set to the first occurrence
- of a given day of the week in the current unit.
- If no day_of_week is provided, modify to the first day of the unit.
- Use the supplied consts to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- Supported units are month, quarter and year.
-
- :param unit: The unit to use
- :type unit: str
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- if unit not in ["month", "quarter", "year"]:
- raise ValueError('Invalid unit "{}" for first_of()'.format(unit))
-
- return getattr(self, "_first_of_{}".format(unit))(day_of_week)
-
- def last_of(self, unit, day_of_week=None):
- """
- Returns an instance set to the last occurrence
- of a given day of the week in the current unit.
- If no day_of_week is provided, modify to the last day of the unit.
- Use the supplied consts to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- Supported units are month, quarter and year.
-
- :param unit: The unit to use
- :type unit: str
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- if unit not in ["month", "quarter", "year"]:
- raise ValueError('Invalid unit "{}" for first_of()'.format(unit))
-
- return getattr(self, "_last_of_{}".format(unit))(day_of_week)
-
- def nth_of(self, unit, nth, day_of_week):
- """
- Returns a new instance set to the given occurrence
- of a given day of the week in the current unit.
- If the calculated occurrence is outside the scope of the current unit,
- then raise an error. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- Supported units are month, quarter and year.
-
- :param unit: The unit to use
- :type unit: str
-
- :type nth: int
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- if unit not in ["month", "quarter", "year"]:
- raise ValueError('Invalid unit "{}" for first_of()'.format(unit))
-
- dt = getattr(self, "_nth_of_{}".format(unit))(nth, day_of_week)
- if dt is False:
- raise PendulumException(
- "Unable to find occurence {} of {} in {}".format(
- nth, self._days[day_of_week], unit
- )
- )
-
- return dt
-
- def _first_of_month(self, day_of_week):
- """
- Modify to the first occurrence of a given day of the week
- in the current month. If no day_of_week is provided,
- modify to the first day of the month. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :type day_of_week: int
-
- :rtype: DateTime
- """
- dt = self.start_of("day")
-
- if day_of_week is None:
- return dt.set(day=1)
-
- month = calendar.monthcalendar(dt.year, dt.month)
-
- calendar_day = (day_of_week - 1) % 7
-
- if month[0][calendar_day] > 0:
- day_of_month = month[0][calendar_day]
- else:
- day_of_month = month[1][calendar_day]
-
- return dt.set(day=day_of_month)
-
- def _last_of_month(self, day_of_week=None):
- """
- Modify to the last occurrence of a given day of the week
- in the current month. If no day_of_week is provided,
- modify to the last day of the month. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- dt = self.start_of("day")
-
- if day_of_week is None:
- return dt.set(day=self.days_in_month)
-
- month = calendar.monthcalendar(dt.year, dt.month)
-
- calendar_day = (day_of_week - 1) % 7
-
- if month[-1][calendar_day] > 0:
- day_of_month = month[-1][calendar_day]
- else:
- day_of_month = month[-2][calendar_day]
-
- return dt.set(day=day_of_month)
-
- def _nth_of_month(self, nth, day_of_week):
- """
- Modify to the given occurrence of a given day of the week
- in the current month. If the calculated occurrence is outside,
- the scope of the current month, then return False and no
- modifications are made. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :type nth: int
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- if nth == 1:
- return self.first_of("month", day_of_week)
-
- dt = self.first_of("month")
- check = dt.format("%Y-%M")
- for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
- dt = dt.next(day_of_week)
-
- if dt.format("%Y-%M") == check:
- return self.set(day=dt.day).start_of("day")
-
- return False
-
- def _first_of_quarter(self, day_of_week=None):
- """
- Modify to the first occurrence of a given day of the week
- in the current quarter. If no day_of_week is provided,
- modify to the first day of the quarter. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- return self.on(self.year, self.quarter * 3 - 2, 1).first_of(
- "month", day_of_week
- )
-
- def _last_of_quarter(self, day_of_week=None):
- """
- Modify to the last occurrence of a given day of the week
- in the current quarter. If no day_of_week is provided,
- modify to the last day of the quarter. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
-
- def _nth_of_quarter(self, nth, day_of_week):
- """
- Modify to the given occurrence of a given day of the week
- in the current quarter. If the calculated occurrence is outside,
- the scope of the current quarter, then return False and no
- modifications are made. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :type nth: int
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- if nth == 1:
- return self.first_of("quarter", day_of_week)
-
- dt = self.set(day=1, month=self.quarter * 3)
- last_month = dt.month
- year = dt.year
- dt = dt.first_of("quarter")
- for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
- dt = dt.next(day_of_week)
-
- if last_month < dt.month or year != dt.year:
- return False
-
- return self.on(self.year, dt.month, dt.day).start_of("day")
-
- def _first_of_year(self, day_of_week=None):
- """
- Modify to the first occurrence of a given day of the week
- in the current year. If no day_of_week is provided,
- modify to the first day of the year. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- return self.set(month=1).first_of("month", day_of_week)
-
- def _last_of_year(self, day_of_week=None):
- """
- Modify to the last occurrence of a given day of the week
- in the current year. If no day_of_week is provided,
- modify to the last day of the year. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
-
- def _nth_of_year(self, nth, day_of_week):
- """
- Modify to the given occurrence of a given day of the week
- in the current year. If the calculated occurrence is outside,
- the scope of the current year, then return False and no
- modifications are made. Use the supplied consts
- to indicate the desired day_of_week, ex. DateTime.MONDAY.
-
- :type nth: int
-
- :type day_of_week: int or None
-
- :rtype: DateTime
- """
- if nth == 1:
- return self.first_of("year", day_of_week)
-
- dt = self.first_of("year")
- year = dt.year
- for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
- dt = dt.next(day_of_week)
-
- if year != dt.year:
- return False
-
- return self.on(self.year, dt.month, dt.day).start_of("day")
-
- def average(self, dt=None):
- """
- Modify the current instance to the average
- of a given instance (default now) and the current instance.
-
- :type dt: DateTime or datetime
-
- :rtype: DateTime
- """
- if dt is None:
- dt = self.now(self.tz)
-
- diff = self.diff(dt, False)
- return self.add(
- microseconds=(diff.in_seconds() * 1000000 + diff.microseconds) // 2
- )
-
- def __sub__(self, other):
- if isinstance(other, datetime.timedelta):
- return self._subtract_timedelta(other)
-
- if not isinstance(other, datetime.datetime):
- return NotImplemented
-
- if not isinstance(other, self.__class__):
- if other.tzinfo is None:
- other = pendulum.naive(
- other.year,
- other.month,
- other.day,
- other.hour,
- other.minute,
- other.second,
- other.microsecond,
- )
- else:
- other = pendulum.instance(other)
-
- return other.diff(self, False)
-
- def __rsub__(self, other):
- if not isinstance(other, datetime.datetime):
- return NotImplemented
-
- if not isinstance(other, self.__class__):
- if other.tzinfo is None:
- other = pendulum.naive(
- other.year,
- other.month,
- other.day,
- other.hour,
- other.minute,
- other.second,
- other.microsecond,
- )
- else:
- other = pendulum.instance(other)
-
- return self.diff(other, False)
-
- def __add__(self, other):
- if not isinstance(other, datetime.timedelta):
- return NotImplemented
-
- return self._add_timedelta_(other)
-
- def __radd__(self, other):
- return self.__add__(other)
-
- # Native methods override
-
- @classmethod
- def fromtimestamp(cls, t, tz=None):
- return pendulum.instance(datetime.datetime.fromtimestamp(t, tz=tz), tz=tz)
-
- @classmethod
- def utcfromtimestamp(cls, t):
- return pendulum.instance(datetime.datetime.utcfromtimestamp(t), tz=None)
-
- @classmethod
- def fromordinal(cls, n):
- return pendulum.instance(datetime.datetime.fromordinal(n), tz=None)
-
- @classmethod
- def combine(cls, date, time):
- return pendulum.instance(datetime.datetime.combine(date, time), tz=None)
-
- def astimezone(self, tz=None):
- return pendulum.instance(super(DateTime, self).astimezone(tz))
-
- def replace(
- self,
- year=None,
- month=None,
- day=None,
- hour=None,
- minute=None,
- second=None,
- microsecond=None,
- tzinfo=True,
- fold=None,
- ):
- if year is None:
- year = self.year
- if month is None:
- month = self.month
- if day is None:
- day = self.day
- if hour is None:
- hour = self.hour
- if minute is None:
- minute = self.minute
- if second is None:
- second = self.second
- if microsecond is None:
- microsecond = self.microsecond
- if tzinfo is True:
- tzinfo = self.tzinfo
- if fold is None:
- fold = self.fold
-
- transition_rule = pendulum.POST_TRANSITION
- if fold is not None:
- transition_rule = pendulum.PRE_TRANSITION
- if fold:
- transition_rule = pendulum.POST_TRANSITION
-
- return pendulum.datetime(
- year,
- month,
- day,
- hour,
- minute,
- second,
- microsecond,
- tz=tzinfo,
- dst_rule=transition_rule,
- )
-
- def __getnewargs__(self):
- return (self,)
-
- def _getstate(self, protocol=3):
- return (
- self.year,
- self.month,
- self.day,
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- self.tzinfo,
- )
-
- def __reduce__(self):
- return self.__reduce_ex__(2)
-
- def __reduce_ex__(self, protocol):
- return self.__class__, self._getstate(protocol)
-
- def _cmp(self, other, **kwargs):
- # Fix for pypy which compares using this method
- # which would lead to infinite recursion if we didn't override
- kwargs = {"tzinfo": self.tz}
-
- if _HAS_FOLD:
- kwargs["fold"] = self.fold
-
- dt = datetime.datetime(
- self.year,
- self.month,
- self.day,
- self.hour,
- self.minute,
- self.second,
- self.microsecond,
- **kwargs
- )
-
- return 0 if dt == other else 1 if dt > other else -1
-
-
-DateTime.min = DateTime(1, 1, 1, 0, 0, tzinfo=UTC)
-DateTime.max = DateTime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC)
-DateTime.EPOCH = DateTime(1970, 1, 1)
+from __future__ import annotations
+
+import calendar
+import datetime
+
+from typing import TYPE_CHECKING
+from typing import Any
+from typing import Callable
+from typing import Optional
+from typing import cast
+from typing import overload
+
+import pendulum
+
+from pendulum.constants import ATOM
+from pendulum.constants import COOKIE
+from pendulum.constants import MINUTES_PER_HOUR
+from pendulum.constants import MONTHS_PER_YEAR
+from pendulum.constants import RFC822
+from pendulum.constants import RFC850
+from pendulum.constants import RFC1036
+from pendulum.constants import RFC1123
+from pendulum.constants import RFC2822
+from pendulum.constants import RSS
+from pendulum.constants import SATURDAY
+from pendulum.constants import SECONDS_PER_DAY
+from pendulum.constants import SECONDS_PER_MINUTE
+from pendulum.constants import SUNDAY
+from pendulum.constants import W3C
+from pendulum.constants import YEARS_PER_CENTURY
+from pendulum.constants import YEARS_PER_DECADE
+from pendulum.date import Date
+from pendulum.exceptions import PendulumException
+from pendulum.helpers import add_duration
+from pendulum.interval import Interval
+from pendulum.time import Time
+from pendulum.tz import UTC
+from pendulum.tz import local_timezone
+from pendulum.tz.timezone import FixedTimezone
+from pendulum.tz.timezone import Timezone
+from pendulum.utils._compat import PY38
+
+if TYPE_CHECKING:
+ from typing import Literal
+
+
+class DateTime(datetime.datetime, Date):
+ EPOCH: DateTime
+
+ # Formats
+
+ _FORMATS: dict[str, str | Callable[[datetime.datetime], str]] = {
+ "atom": ATOM,
+ "cookie": COOKIE,
+ "iso8601": lambda dt: dt.isoformat(),
+ "rfc822": RFC822,
+ "rfc850": RFC850,
+ "rfc1036": RFC1036,
+ "rfc1123": RFC1123,
+ "rfc2822": RFC2822,
+ "rfc3339": lambda dt: dt.isoformat(),
+ "rss": RSS,
+ "w3c": W3C,
+ }
+
+ _MODIFIERS_VALID_UNITS: list[str] = [
+ "second",
+ "minute",
+ "hour",
+ "day",
+ "week",
+ "month",
+ "year",
+ "decade",
+ "century",
+ ]
+
+ _EPOCH: datetime.datetime = datetime.datetime(1970, 1, 1, tzinfo=UTC)
+
+ @classmethod
+ def create(
+ cls,
+ year: int,
+ month: int,
+ day: int,
+ hour: int = 0,
+ minute: int = 0,
+ second: int = 0,
+ microsecond: int = 0,
+ tz: str | float | Timezone | FixedTimezone | None | datetime.tzinfo = UTC,
+ fold: int = 1,
+ raise_on_unknown_times: bool = False,
+ ) -> DateTime:
+ """
+ Creates a new DateTime instance from a specific date and time.
+ """
+ if tz is not None:
+ tz = pendulum._safe_timezone(tz)
+
+ dt = datetime.datetime(
+ year, month, day, hour, minute, second, microsecond, fold=fold
+ )
+
+ if tz is not None:
+ dt = tz.convert(dt, raise_on_unknown_times=raise_on_unknown_times)
+
+ return cls(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ dt.microsecond,
+ tzinfo=dt.tzinfo,
+ fold=dt.fold,
+ )
+
+ @overload
+ @classmethod
+ def now(cls, tz: datetime.tzinfo | None = None) -> DateTime:
+ ...
+
+ @overload
+ @classmethod
+ def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> DateTime:
+ ...
+
+ @classmethod
+ def now(
+ cls, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = None
+ ) -> DateTime:
+ """
+ Get a DateTime instance for the current date and time.
+ """
+ if tz is None or tz == "local":
+ dt = datetime.datetime.now(local_timezone())
+ elif tz is UTC or tz == "UTC":
+ dt = datetime.datetime.now(UTC)
+ else:
+ dt = datetime.datetime.now(UTC)
+ tz = pendulum._safe_timezone(tz)
+ dt = dt.astimezone(tz)
+
+ return cls(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ dt.microsecond,
+ tzinfo=dt.tzinfo,
+ fold=dt.fold,
+ )
+
+ @classmethod
+ def utcnow(cls) -> DateTime:
+ """
+ Get a DateTime instance for the current date and time in UTC.
+ """
+ return cls.now(UTC)
+
+ @classmethod
+ def today(cls) -> DateTime:
+ return cls.now()
+
+ @classmethod
+ def strptime(cls, time: str, fmt: str) -> DateTime:
+ return pendulum.instance(datetime.datetime.strptime(time, fmt))
+
+ # Getters/Setters
+
+ def set(
+ self,
+ year: int | None = None,
+ month: int | None = None,
+ day: int | None = None,
+ hour: int | None = None,
+ minute: int | None = None,
+ second: int | None = None,
+ microsecond: int | None = None,
+ tz: str | float | Timezone | FixedTimezone | datetime.tzinfo | None = None,
+ ) -> DateTime:
+ if year is None:
+ year = self.year
+ if month is None:
+ month = self.month
+ if day is None:
+ day = self.day
+ if hour is None:
+ hour = self.hour
+ if minute is None:
+ minute = self.minute
+ if second is None:
+ second = self.second
+ if microsecond is None:
+ microsecond = self.microsecond
+ if tz is None:
+ tz = self.tz
+
+ return DateTime.create(
+ year, month, day, hour, minute, second, microsecond, tz=tz
+ )
+
+ @property
+ def float_timestamp(self) -> float:
+ return self.timestamp()
+
+ @property
+ def int_timestamp(self) -> int:
+ # Workaround needed to avoid inaccuracy
+ # for far into the future datetimes
+ dt = datetime.datetime(
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ tzinfo=self.tzinfo,
+ fold=self.fold,
+ )
+
+ delta = dt - self._EPOCH
+
+ return delta.days * SECONDS_PER_DAY + delta.seconds
+
+ @property
+ def offset(self) -> int | None:
+ return self.get_offset()
+
+ @property
+ def offset_hours(self) -> float | None:
+ offset = self.get_offset()
+
+ if offset is None:
+ return None
+
+ return offset / SECONDS_PER_MINUTE / MINUTES_PER_HOUR
+
+ @property
+ def timezone(self) -> Timezone | FixedTimezone | None:
+ if not isinstance(self.tzinfo, (Timezone, FixedTimezone)):
+ return None
+
+ return self.tzinfo
+
+ @property
+ def tz(self) -> Timezone | FixedTimezone | None:
+ return self.timezone
+
+ @property
+ def timezone_name(self) -> str | None:
+ tz = self.timezone
+
+ if tz is None:
+ return None
+
+ return tz.name
+
+ @property
+ def age(self) -> int:
+ return self.date().diff(self.now(self.tz).date(), abs=False).in_years()
+
+ def is_local(self) -> bool:
+ return self.offset == self.in_timezone(pendulum.local_timezone()).offset
+
+ def is_utc(self) -> bool:
+ return self.offset == 0
+
+ def is_dst(self) -> bool:
+ return self.dst() != datetime.timedelta()
+
+ def get_offset(self) -> int | None:
+ utcoffset = self.utcoffset()
+ if utcoffset is None:
+ return None
+
+ return int(utcoffset.total_seconds())
+
+ def date(self) -> Date:
+ return Date(self.year, self.month, self.day)
+
+ def time(self) -> Time:
+ return Time(self.hour, self.minute, self.second, self.microsecond)
+
+ def naive(self) -> DateTime:
+ """
+ Return the DateTime without timezone information.
+ """
+ return self.__class__(
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ )
+
+ def on(self, year: int, month: int, day: int) -> DateTime:
+ """
+ Returns a new instance with the current date set to a different date.
+ """
+ return self.set(year=int(year), month=int(month), day=int(day))
+
+ def at(
+ self, hour: int, minute: int = 0, second: int = 0, microsecond: int = 0
+ ) -> DateTime:
+ """
+ Returns a new instance with the current time to a different time.
+ """
+ return self.set(
+ hour=hour, minute=minute, second=second, microsecond=microsecond
+ )
+
+ def in_timezone(self, tz: str | Timezone | FixedTimezone) -> DateTime:
+ """
+ Set the instance's timezone from a string or object.
+ """
+ tz = pendulum._safe_timezone(tz)
+
+ dt = self
+ if not self.timezone:
+ dt = dt.replace(fold=1)
+
+ return cast(DateTime, tz.convert(dt))
+
+ def in_tz(self, tz: str | Timezone | FixedTimezone) -> DateTime:
+ """
+ Set the instance's timezone from a string or object.
+ """
+ return self.in_timezone(tz)
+
+ # STRING FORMATTING
+
+ def to_time_string(self) -> str:
+ """
+ Format the instance as time.
+ """
+ return self.format("HH:mm:ss")
+
+ def to_datetime_string(self) -> str:
+ """
+ Format the instance as date and time.
+ """
+ return self.format("YYYY-MM-DD HH:mm:ss")
+
+ def to_day_datetime_string(self) -> str:
+ """
+ Format the instance as day, date and time (in english).
+ """
+ return self.format("ddd, MMM D, YYYY h:mm A", locale="en")
+
+ def to_atom_string(self) -> str:
+ """
+ Format the instance as ATOM.
+ """
+ return self._to_string("atom")
+
+ def to_cookie_string(self) -> str:
+ """
+ Format the instance as COOKIE.
+ """
+ return self._to_string("cookie", locale="en")
+
+ def to_iso8601_string(self) -> str:
+ """
+ Format the instance as ISO 8601.
+ """
+ string = self._to_string("iso8601")
+
+ if self.tz and self.tz.name == "UTC":
+ string = string.replace("+00:00", "Z")
+
+ return string
+
+ def to_rfc822_string(self) -> str:
+ """
+ Format the instance as RFC 822.
+ """
+ return self._to_string("rfc822")
+
+ def to_rfc850_string(self) -> str:
+ """
+ Format the instance as RFC 850.
+ """
+ return self._to_string("rfc850")
+
+ def to_rfc1036_string(self) -> str:
+ """
+ Format the instance as RFC 1036.
+ """
+ return self._to_string("rfc1036")
+
+ def to_rfc1123_string(self) -> str:
+ """
+ Format the instance as RFC 1123.
+ """
+ return self._to_string("rfc1123")
+
+ def to_rfc2822_string(self) -> str:
+ """
+ Format the instance as RFC 2822.
+ """
+ return self._to_string("rfc2822")
+
+ def to_rfc3339_string(self) -> str:
+ """
+ Format the instance as RFC 3339.
+ """
+ return self._to_string("rfc3339")
+
+ def to_rss_string(self) -> str:
+ """
+ Format the instance as RSS.
+ """
+ return self._to_string("rss")
+
+ def to_w3c_string(self) -> str:
+ """
+ Format the instance as W3C.
+ """
+ return self._to_string("w3c")
+
+ def _to_string(self, fmt: str, locale: str | None = None) -> str:
+ """
+ Format the instance to a common string format.
+ """
+ if fmt not in self._FORMATS:
+ raise ValueError(f"Format [{fmt}] is not supported")
+
+ fmt_value = self._FORMATS[fmt]
+ if callable(fmt_value):
+ return fmt_value(self)
+
+ return self.format(fmt_value, locale=locale)
+
+ def __str__(self) -> str:
+ return self.isoformat("T")
+
+ def __repr__(self) -> str:
+ us = ""
+ if self.microsecond:
+ us = f", {self.microsecond}"
+
+ repr_ = "{klass}(" "{year}, {month}, {day}, " "{hour}, {minute}, {second}{us}"
+
+ if self.tzinfo is not None:
+ repr_ += ", tzinfo={tzinfo}"
+
+ repr_ += ")"
+
+ return repr_.format(
+ klass=self.__class__.__name__,
+ year=self.year,
+ month=self.month,
+ day=self.day,
+ hour=self.hour,
+ minute=self.minute,
+ second=self.second,
+ us=us,
+ tzinfo=repr(self.tzinfo),
+ )
+
+ # Comparisons
+ def closest(self, *dts: datetime.datetime) -> DateTime: # type: ignore[override]
+ """
+ Get the farthest date from the instance.
+ """
+ pdts = [pendulum.instance(x) for x in dts]
+
+ return min((abs(self - dt), dt) for dt in pdts)[1]
+
+ def farthest(self, *dts: datetime.datetime) -> DateTime: # type: ignore[override]
+ """
+ Get the farthest date from the instance.
+ """
+ pdts = [pendulum.instance(x) for x in dts]
+
+ return max((abs(self - dt), dt) for dt in pdts)[1]
+
+ def is_future(self) -> bool:
+ """
+ Determines if the instance is in the future, ie. greater than now.
+ """
+ return self > self.now(self.timezone)
+
+ def is_past(self) -> bool:
+ """
+ Determines if the instance is in the past, ie. less than now.
+ """
+ return self < self.now(self.timezone)
+
+ def is_long_year(self) -> bool:
+ """
+ Determines if the instance is a long year
+
+ See link `https://en.wikipedia.org/wiki/ISO_8601#Week_dates`_
+ """
+ return (
+ DateTime.create(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1]
+ == 53
+ )
+
+ def is_same_day(self, dt: datetime.datetime) -> bool: # type: ignore[override]
+ """
+ Checks if the passed in date is the same day
+ as the instance current day.
+ """
+ dt = pendulum.instance(dt)
+
+ return self.to_date_string() == dt.to_date_string()
+
+ def is_anniversary( # type: ignore[override]
+ self, dt: datetime.datetime | None = None
+ ) -> bool:
+ """
+ Check if its the anniversary.
+ Compares the date/month values of the two dates.
+ """
+ if dt is None:
+ dt = self.now(self.tz)
+
+ instance = pendulum.instance(dt)
+
+ return (self.month, self.day) == (instance.month, instance.day)
+
+ # ADDITIONS AND SUBSTRACTIONS
+
+ def add(
+ self,
+ years: int = 0,
+ months: int = 0,
+ weeks: int = 0,
+ days: int = 0,
+ hours: int = 0,
+ minutes: int = 0,
+ seconds: float = 0,
+ microseconds: int = 0,
+ ) -> DateTime:
+ """
+ Add a duration to the instance.
+
+ If we're adding units of variable length (i.e., years, months),
+ move forward from current time, otherwise move forward from utc, for accuracy
+ when moving across DST boundaries.
+ """
+ units_of_variable_length = any([years, months, weeks, days])
+
+ current_dt = datetime.datetime(
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ )
+ if not units_of_variable_length:
+ offset = self.utcoffset()
+ if offset:
+ current_dt = current_dt - offset
+
+ dt = add_duration(
+ current_dt,
+ years=years,
+ months=months,
+ weeks=weeks,
+ days=days,
+ hours=hours,
+ minutes=minutes,
+ seconds=seconds,
+ microseconds=microseconds,
+ )
+
+ if units_of_variable_length or self.tz is None:
+ return DateTime.create(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ dt.microsecond,
+ tz=self.tz,
+ )
+
+ dt = datetime.datetime(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ dt.microsecond,
+ tzinfo=UTC,
+ )
+
+ dt = self.tz.convert(dt)
+
+ return self.__class__(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ dt.microsecond,
+ tzinfo=self.tz,
+ fold=dt.fold,
+ )
+
+ def subtract(
+ self,
+ years: int = 0,
+ months: int = 0,
+ weeks: int = 0,
+ days: int = 0,
+ hours: int = 0,
+ minutes: int = 0,
+ seconds: float = 0,
+ microseconds: int = 0,
+ ) -> DateTime:
+ """
+ Remove duration from the instance.
+ """
+ return self.add(
+ years=-years,
+ months=-months,
+ weeks=-weeks,
+ days=-days,
+ hours=-hours,
+ minutes=-minutes,
+ seconds=-seconds,
+ microseconds=-microseconds,
+ )
+
+ # Adding a final underscore to the method name
+ # to avoid errors for PyPy which already defines
+ # a _add_timedelta method
+ def _add_timedelta_(self, delta: datetime.timedelta) -> DateTime:
+ """
+ Add timedelta duration to the instance.
+ """
+ if isinstance(delta, pendulum.Interval):
+ return self.add(
+ years=delta.years,
+ months=delta.months,
+ weeks=delta.weeks,
+ days=delta.remaining_days,
+ hours=delta.hours,
+ minutes=delta.minutes,
+ seconds=delta.remaining_seconds,
+ microseconds=delta.microseconds,
+ )
+ elif isinstance(delta, pendulum.Duration):
+ return self.add(
+ years=delta.years, months=delta.months, seconds=delta._total
+ )
+
+ return self.add(seconds=delta.total_seconds())
+
+ def _subtract_timedelta(self, delta: datetime.timedelta) -> DateTime:
+ """
+ Remove timedelta duration from the instance.
+ """
+ if isinstance(delta, pendulum.Duration):
+ return self.subtract(
+ years=delta.years, months=delta.months, seconds=delta._total
+ )
+
+ return self.subtract(seconds=delta.total_seconds())
+
+ # DIFFERENCES
+
+ def diff( # type: ignore[override]
+ self, dt: datetime.datetime | None = None, abs: bool = True
+ ) -> Interval:
+ """
+ Returns the difference between two DateTime objects represented as an Interval.
+ """
+ if dt is None:
+ dt = self.now(self.tz)
+
+ return Interval(self, dt, absolute=abs)
+
+ def diff_for_humans( # type: ignore[override]
+ self,
+ other: DateTime | None = None,
+ absolute: bool = False,
+ locale: str | None = None,
+ ) -> str:
+ """
+ Get the difference in a human readable format in the current locale.
+
+ When comparing a value in the past to default now:
+ 1 day ago
+ 5 months ago
+
+ When comparing a value in the future to default now:
+ 1 day from now
+ 5 months from now
+
+ When comparing a value in the past to another value:
+ 1 day before
+ 5 months before
+
+ When comparing a value in the future to another value:
+ 1 day after
+ 5 months after
+ """
+ is_now = other is None
+
+ if is_now:
+ other = self.now()
+
+ diff = self.diff(other)
+
+ return pendulum.format_diff(diff, is_now, absolute, locale)
+
+ # Modifiers
+ def start_of(self, unit: str) -> DateTime:
+ """
+ Returns a copy of the instance with the time reset
+ with the following rules:
+
+ * second: microsecond set to 0
+ * minute: second and microsecond set to 0
+ * hour: minute, second and microsecond set to 0
+ * day: time to 00:00:00
+ * week: date to first day of the week and time to 00:00:00
+ * month: date to first day of the month and time to 00:00:00
+ * year: date to first day of the year and time to 00:00:00
+ * decade: date to first day of the decade and time to 00:00:00
+ * century: date to first day of century and time to 00:00:00
+ """
+ if unit not in self._MODIFIERS_VALID_UNITS:
+ raise ValueError(f'Invalid unit "{unit}" for start_of()')
+
+ return cast(DateTime, getattr(self, f"_start_of_{unit}")())
+
+ def end_of(self, unit: str) -> DateTime:
+ """
+ Returns a copy of the instance with the time reset
+ with the following rules:
+
+ * second: microsecond set to 999999
+ * minute: second set to 59 and microsecond set to 999999
+ * hour: minute and second set to 59 and microsecond set to 999999
+ * day: time to 23:59:59.999999
+ * week: date to last day of the week and time to 23:59:59.999999
+ * month: date to last day of the month and time to 23:59:59.999999
+ * year: date to last day of the year and time to 23:59:59.999999
+ * decade: date to last day of the decade and time to 23:59:59.999999
+ * century: date to last day of century and time to 23:59:59.999999
+ """
+ if unit not in self._MODIFIERS_VALID_UNITS:
+ raise ValueError(f'Invalid unit "{unit}" for end_of()')
+
+ return cast(DateTime, getattr(self, f"_end_of_{unit}")())
+
+ def _start_of_second(self) -> DateTime:
+ """
+ Reset microseconds to 0.
+ """
+ return self.set(microsecond=0)
+
+ def _end_of_second(self) -> DateTime:
+ """
+ Set microseconds to 999999.
+ """
+ return self.set(microsecond=999999)
+
+ def _start_of_minute(self) -> DateTime:
+ """
+ Reset seconds and microseconds to 0.
+ """
+ return self.set(second=0, microsecond=0)
+
+ def _end_of_minute(self) -> DateTime:
+ """
+ Set seconds to 59 and microseconds to 999999.
+ """
+ return self.set(second=59, microsecond=999999)
+
+ def _start_of_hour(self) -> DateTime:
+ """
+ Reset minutes, seconds and microseconds to 0.
+ """
+ return self.set(minute=0, second=0, microsecond=0)
+
+ def _end_of_hour(self) -> DateTime:
+ """
+ Set minutes and seconds to 59 and microseconds to 999999.
+ """
+ return self.set(minute=59, second=59, microsecond=999999)
+
+ def _start_of_day(self) -> DateTime:
+ """
+ Reset the time to 00:00:00.
+ """
+ return self.at(0, 0, 0, 0)
+
+ def _end_of_day(self) -> DateTime:
+ """
+ Reset the time to 23:59:59.999999.
+ """
+ return self.at(23, 59, 59, 999999)
+
+ def _start_of_month(self) -> DateTime:
+ """
+ Reset the date to the first day of the month and the time to 00:00:00.
+ """
+ return self.set(self.year, self.month, 1, 0, 0, 0, 0)
+
+ def _end_of_month(self) -> DateTime:
+ """
+ Reset the date to the last day of the month
+ and the time to 23:59:59.999999.
+ """
+ return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999)
+
+ def _start_of_year(self) -> DateTime:
+ """
+ Reset the date to the first day of the year and the time to 00:00:00.
+ """
+ return self.set(self.year, 1, 1, 0, 0, 0, 0)
+
+ def _end_of_year(self) -> DateTime:
+ """
+ Reset the date to the last day of the year
+ and the time to 23:59:59.999999.
+ """
+ return self.set(self.year, 12, 31, 23, 59, 59, 999999)
+
+ def _start_of_decade(self) -> DateTime:
+ """
+ Reset the date to the first day of the decade
+ and the time to 00:00:00.
+ """
+ year = self.year - self.year % YEARS_PER_DECADE
+ return self.set(year, 1, 1, 0, 0, 0, 0)
+
+ def _end_of_decade(self) -> DateTime:
+ """
+ Reset the date to the last day of the decade
+ and the time to 23:59:59.999999.
+ """
+ year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1
+
+ return self.set(year, 12, 31, 23, 59, 59, 999999)
+
+ def _start_of_century(self) -> DateTime:
+ """
+ Reset the date to the first day of the century
+ and the time to 00:00:00.
+ """
+ year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1
+
+ return self.set(year, 1, 1, 0, 0, 0, 0)
+
+ def _end_of_century(self) -> DateTime:
+ """
+ Reset the date to the last day of the century
+ and the time to 23:59:59.999999.
+ """
+ year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY
+
+ return self.set(year, 12, 31, 23, 59, 59, 999999)
+
+ def _start_of_week(self) -> DateTime:
+ """
+ Reset the date to the first day of the week
+ and the time to 00:00:00.
+ """
+ dt = self
+
+ if self.day_of_week != pendulum._WEEK_STARTS_AT:
+ dt = self.previous(pendulum._WEEK_STARTS_AT)
+
+ return dt.start_of("day")
+
+ def _end_of_week(self) -> DateTime:
+ """
+ Reset the date to the last day of the week
+ and the time to 23:59:59.
+ """
+ dt = self
+
+ if self.day_of_week != pendulum._WEEK_ENDS_AT:
+ dt = self.next(pendulum._WEEK_ENDS_AT)
+
+ return dt.end_of("day")
+
+ def next(self, day_of_week: int | None = None, keep_time: bool = False) -> DateTime:
+ """
+ Modify to the next occurrence of a given day of the week.
+ If no day_of_week is provided, modify to the next occurrence
+ of the current day of the week. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ if day_of_week is None:
+ day_of_week = self.day_of_week
+
+ if day_of_week < SUNDAY or day_of_week > SATURDAY:
+ raise ValueError("Invalid day of week")
+
+ if keep_time:
+ dt = self
+ else:
+ dt = self.start_of("day")
+
+ dt = dt.add(days=1)
+ while dt.day_of_week != day_of_week:
+ dt = dt.add(days=1)
+
+ return dt
+
+ def previous(
+ self, day_of_week: int | None = None, keep_time: bool = False
+ ) -> DateTime:
+ """
+ Modify to the previous occurrence of a given day of the week.
+ If no day_of_week is provided, modify to the previous occurrence
+ of the current day of the week. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ if day_of_week is None:
+ day_of_week = self.day_of_week
+
+ if day_of_week < SUNDAY or day_of_week > SATURDAY:
+ raise ValueError("Invalid day of week")
+
+ if keep_time:
+ dt = self
+ else:
+ dt = self.start_of("day")
+
+ dt = dt.subtract(days=1)
+ while dt.day_of_week != day_of_week:
+ dt = dt.subtract(days=1)
+
+ return dt
+
+ def first_of(self, unit: str, day_of_week: int | None = None) -> DateTime:
+ """
+ Returns an instance set to the first occurrence
+ of a given day of the week in the current unit.
+ If no day_of_week is provided, modify to the first day of the unit.
+ Use the supplied consts to indicate the desired day_of_week, ex. DateTime.MONDAY.
+
+ Supported units are month, quarter and year.
+ """
+ if unit not in ["month", "quarter", "year"]:
+ raise ValueError(f'Invalid unit "{unit}" for first_of()')
+
+ return cast(DateTime, getattr(self, f"_first_of_{unit}")(day_of_week))
+
+ def last_of(self, unit: str, day_of_week: int | None = None) -> DateTime:
+ """
+ Returns an instance set to the last occurrence
+ of a given day of the week in the current unit.
+ If no day_of_week is provided, modify to the last day of the unit.
+ Use the supplied consts to indicate the desired day_of_week, ex. DateTime.MONDAY.
+
+ Supported units are month, quarter and year.
+ """
+ if unit not in ["month", "quarter", "year"]:
+ raise ValueError(f'Invalid unit "{unit}" for first_of()')
+
+ return cast(DateTime, getattr(self, f"_last_of_{unit}")(day_of_week))
+
+ def nth_of(self, unit: str, nth: int, day_of_week: int) -> DateTime:
+ """
+ Returns a new instance set to the given occurrence
+ of a given day of the week in the current unit.
+ If the calculated occurrence is outside the scope of the current unit,
+ then raise an error. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+
+ Supported units are month, quarter and year.
+ """
+ if unit not in ["month", "quarter", "year"]:
+ raise ValueError(f'Invalid unit "{unit}" for first_of()')
+
+ dt = cast(
+ Optional[DateTime], getattr(self, f"_nth_of_{unit}")(nth, day_of_week)
+ )
+ if not dt:
+ raise PendulumException(
+ f"Unable to find occurence {nth}"
+ f" of {self._days[day_of_week]} in {unit}"
+ )
+
+ return dt
+
+ def _first_of_month(self, day_of_week: int | None = None) -> DateTime:
+ """
+ Modify to the first occurrence of a given day of the week
+ in the current month. If no day_of_week is provided,
+ modify to the first day of the month. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ dt = self.start_of("day")
+
+ if day_of_week is None:
+ return dt.set(day=1)
+
+ month = calendar.monthcalendar(dt.year, dt.month)
+
+ calendar_day = (day_of_week - 1) % 7
+
+ if month[0][calendar_day] > 0:
+ day_of_month = month[0][calendar_day]
+ else:
+ day_of_month = month[1][calendar_day]
+
+ return dt.set(day=day_of_month)
+
+ def _last_of_month(self, day_of_week: int | None = None) -> DateTime:
+ """
+ Modify to the last occurrence of a given day of the week
+ in the current month. If no day_of_week is provided,
+ modify to the last day of the month. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ dt = self.start_of("day")
+
+ if day_of_week is None:
+ return dt.set(day=self.days_in_month)
+
+ month = calendar.monthcalendar(dt.year, dt.month)
+
+ calendar_day = (day_of_week - 1) % 7
+
+ if month[-1][calendar_day] > 0:
+ day_of_month = month[-1][calendar_day]
+ else:
+ day_of_month = month[-2][calendar_day]
+
+ return dt.set(day=day_of_month)
+
+ def _nth_of_month(
+ self, nth: int, day_of_week: int | None = None
+ ) -> DateTime | None:
+ """
+ Modify to the given occurrence of a given day of the week
+ in the current month. If the calculated occurrence is outside,
+ the scope of the current month, then return False and no
+ modifications are made. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ if nth == 1:
+ return self.first_of("month", day_of_week)
+
+ dt = self.first_of("month")
+ check = dt.format("%Y-%M")
+ for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
+ dt = dt.next(day_of_week)
+
+ if dt.format("%Y-%M") == check:
+ return self.set(day=dt.day).start_of("day")
+
+ return None
+
+ def _first_of_quarter(self, day_of_week: int | None = None) -> DateTime:
+ """
+ Modify to the first occurrence of a given day of the week
+ in the current quarter. If no day_of_week is provided,
+ modify to the first day of the quarter. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ return self.on(self.year, self.quarter * 3 - 2, 1).first_of(
+ "month", day_of_week
+ )
+
+ def _last_of_quarter(self, day_of_week: int | None = None) -> DateTime:
+ """
+ Modify to the last occurrence of a given day of the week
+ in the current quarter. If no day_of_week is provided,
+ modify to the last day of the quarter. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
+
+ def _nth_of_quarter(
+ self, nth: int, day_of_week: int | None = None
+ ) -> DateTime | None:
+ """
+ Modify to the given occurrence of a given day of the week
+ in the current quarter. If the calculated occurrence is outside,
+ the scope of the current quarter, then return False and no
+ modifications are made. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ if nth == 1:
+ return self.first_of("quarter", day_of_week)
+
+ dt = self.set(day=1, month=self.quarter * 3)
+ last_month = dt.month
+ year = dt.year
+ dt = dt.first_of("quarter")
+ for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
+ dt = dt.next(day_of_week)
+
+ if last_month < dt.month or year != dt.year:
+ return None
+
+ return self.on(self.year, dt.month, dt.day).start_of("day")
+
+ def _first_of_year(self, day_of_week: int | None = None) -> DateTime:
+ """
+ Modify to the first occurrence of a given day of the week
+ in the current year. If no day_of_week is provided,
+ modify to the first day of the year. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ return self.set(month=1).first_of("month", day_of_week)
+
+ def _last_of_year(self, day_of_week: int | None = None) -> DateTime:
+ """
+ Modify to the last occurrence of a given day of the week
+ in the current year. If no day_of_week is provided,
+ modify to the last day of the year. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
+
+ def _nth_of_year(self, nth: int, day_of_week: int | None = None) -> DateTime | None:
+ """
+ Modify to the given occurrence of a given day of the week
+ in the current year. If the calculated occurrence is outside,
+ the scope of the current year, then return False and no
+ modifications are made. Use the supplied consts
+ to indicate the desired day_of_week, ex. DateTime.MONDAY.
+ """
+ if nth == 1:
+ return self.first_of("year", day_of_week)
+
+ dt = self.first_of("year")
+ year = dt.year
+ for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
+ dt = dt.next(day_of_week)
+
+ if year != dt.year:
+ return None
+
+ return self.on(self.year, dt.month, dt.day).start_of("day")
+
+ def average( # type: ignore[override]
+ self, dt: datetime.datetime | None = None
+ ) -> DateTime:
+ """
+ Modify the current instance to the average
+ of a given instance (default now) and the current instance.
+ """
+ if dt is None:
+ dt = self.now(self.tz)
+
+ diff = self.diff(dt, False)
+ return self.add(
+ microseconds=(diff.in_seconds() * 1000000 + diff.microseconds) // 2
+ )
+
+ @overload # type: ignore[override]
+ def __sub__(self, other: datetime.timedelta) -> DateTime:
+ ...
+
+ @overload
+ def __sub__(self, other: DateTime) -> Interval:
+ ...
+
+ def __sub__(
+ self, other: datetime.datetime | datetime.timedelta
+ ) -> DateTime | Interval:
+ if isinstance(other, datetime.timedelta):
+ return self._subtract_timedelta(other)
+
+ if not isinstance(other, datetime.datetime):
+ return NotImplemented
+
+ if not isinstance(other, self.__class__):
+ if other.tzinfo is None:
+ other = pendulum.naive(
+ other.year,
+ other.month,
+ other.day,
+ other.hour,
+ other.minute,
+ other.second,
+ other.microsecond,
+ )
+ else:
+ other = pendulum.instance(other)
+
+ return other.diff(self, False)
+
+ def __rsub__(self, other: datetime.datetime) -> Interval:
+ if not isinstance(other, datetime.datetime):
+ return NotImplemented
+
+ if not isinstance(other, self.__class__):
+ if other.tzinfo is None:
+ other = pendulum.naive(
+ other.year,
+ other.month,
+ other.day,
+ other.hour,
+ other.minute,
+ other.second,
+ other.microsecond,
+ )
+ else:
+ other = pendulum.instance(other)
+
+ return self.diff(other, False)
+
+ def __add__(self, other: datetime.timedelta) -> DateTime:
+ if not isinstance(other, datetime.timedelta):
+ return NotImplemented
+
+ if PY38:
+ # This is a workaround for Python 3.8+
+ # since calling astimezone() will call this method
+ # instead of the base datetime class one.
+ import inspect
+
+ caller = inspect.stack()[1][3]
+ if caller == "astimezone":
+ return cast(DateTime, super().__add__(other))
+
+ return self._add_timedelta_(other)
+
+ def __radd__(self, other: datetime.timedelta) -> DateTime:
+ return self.__add__(other)
+
+ # Native methods override
+
+ @classmethod
+ def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> DateTime:
+ tzinfo = pendulum._safe_timezone(tz)
+
+ return pendulum.instance(
+ datetime.datetime.fromtimestamp(t, tz=tzinfo), tz=tzinfo
+ )
+
+ @classmethod
+ def utcfromtimestamp(cls, t: float) -> DateTime:
+ return pendulum.instance(datetime.datetime.utcfromtimestamp(t), tz=None)
+
+ @classmethod
+ def fromordinal(cls, n: int) -> DateTime:
+ return pendulum.instance(datetime.datetime.fromordinal(n), tz=None)
+
+ @classmethod
+ def combine(
+ cls,
+ date: datetime.date,
+ time: datetime.time,
+ tzinfo: datetime.tzinfo | None = None,
+ ) -> DateTime:
+ return pendulum.instance(datetime.datetime.combine(date, time), tz=tzinfo)
+
+ def astimezone(self, tz: datetime.tzinfo | None = None) -> DateTime:
+ dt = super().astimezone(tz)
+
+ return self.__class__(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ dt.microsecond,
+ fold=dt.fold,
+ tzinfo=dt.tzinfo,
+ )
+
+ def replace(
+ self,
+ year: int | None = None,
+ month: int | None = None,
+ day: int | None = None,
+ hour: int | None = None,
+ minute: int | None = None,
+ second: int | None = None,
+ microsecond: int | None = None,
+ tzinfo: bool | datetime.tzinfo | Literal[True] | None = True,
+ fold: int | None = None,
+ ) -> DateTime:
+ if year is None:
+ year = self.year
+ if month is None:
+ month = self.month
+ if day is None:
+ day = self.day
+ if hour is None:
+ hour = self.hour
+ if minute is None:
+ minute = self.minute
+ if second is None:
+ second = self.second
+ if microsecond is None:
+ microsecond = self.microsecond
+ if tzinfo is True:
+ tzinfo = self.tzinfo
+ if fold is None:
+ fold = self.fold
+
+ if tzinfo is not None:
+ tzinfo = pendulum._safe_timezone(tzinfo)
+
+ return DateTime.create(
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ microsecond,
+ tz=tzinfo,
+ fold=fold,
+ )
+
+ def __getnewargs__(self) -> tuple[DateTime]:
+ return (self,)
+
+ def _getstate(
+ self, protocol: int = 3
+ ) -> tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]:
+ return (
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ self.tzinfo,
+ )
+
+ def __reduce__(
+ self,
+ ) -> tuple[
+ type[DateTime], tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]
+ ]:
+ return self.__reduce_ex__(2)
+
+ def __reduce_ex__( # type: ignore[override]
+ self, protocol: int
+ ) -> tuple[
+ type[DateTime], tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]
+ ]:
+ return self.__class__, self._getstate(protocol)
+
+ def _cmp(self, other: datetime.datetime, **kwargs: Any) -> int:
+ # Fix for pypy which compares using this method
+ # which would lead to infinite recursion if we didn't override
+ dt = datetime.datetime(
+ self.year,
+ self.month,
+ self.day,
+ self.hour,
+ self.minute,
+ self.second,
+ self.microsecond,
+ tzinfo=self.tz,
+ fold=self.fold,
+ )
+
+ return 0 if dt == other else 1 if dt > other else -1
+
+
+DateTime.min: DateTime = DateTime(1, 1, 1, 0, 0, tzinfo=UTC) # type: ignore[misc]
+DateTime.max: DateTime = DateTime( # type: ignore[misc]
+ 9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC
+)
+DateTime.EPOCH: DateTime = DateTime(1970, 1, 1, tzinfo=UTC) # type: ignore[misc]