diff options
Diffstat (limited to 'pendulum/time.py')
-rw-r--r-- | pendulum/time.py | 587 |
1 files changed, 303 insertions, 284 deletions
diff --git a/pendulum/time.py b/pendulum/time.py index e72972d..f979e25 100644 --- a/pendulum/time.py +++ b/pendulum/time.py @@ -1,284 +1,303 @@ -from __future__ import absolute_import
-
-from datetime import time
-from datetime import timedelta
-
-import pendulum
-
-from .constants import SECS_PER_HOUR
-from .constants import SECS_PER_MIN
-from .constants import USECS_PER_SEC
-from .duration import AbsoluteDuration
-from .duration import Duration
-from .mixins.default import FormattableMixin
-
-
-class Time(FormattableMixin, time):
- """
- Represents a time instance as hour, minute, second, microsecond.
- """
-
- # String formatting
- def __repr__(self):
- us = ""
- if self.microsecond:
- us = ", {}".format(self.microsecond)
-
- tzinfo = ""
- if self.tzinfo:
- tzinfo = ", tzinfo={}".format(repr(self.tzinfo))
-
- return "{}({}, {}, {}{}{})".format(
- self.__class__.__name__, self.hour, self.minute, self.second, us, tzinfo
- )
-
- # Comparisons
-
- def closest(self, dt1, dt2):
- """
- Get the closest time from the instance.
-
- :type dt1: Time or time
- :type dt2: Time or time
-
- :rtype: Time
- """
- dt1 = self.__class__(dt1.hour, dt1.minute, dt1.second, dt1.microsecond)
- dt2 = self.__class__(dt2.hour, dt2.minute, dt2.second, dt2.microsecond)
-
- if self.diff(dt1).in_seconds() < self.diff(dt2).in_seconds():
- return dt1
-
- return dt2
-
- def farthest(self, dt1, dt2):
- """
- Get the farthest time from the instance.
-
- :type dt1: Time or time
- :type dt2: Time or time
-
- :rtype: Time
- """
- dt1 = self.__class__(dt1.hour, dt1.minute, dt1.second, dt1.microsecond)
- dt2 = self.__class__(dt2.hour, dt2.minute, dt2.second, dt2.microsecond)
-
- if self.diff(dt1).in_seconds() > self.diff(dt2).in_seconds():
- return dt1
-
- return dt2
-
- # ADDITIONS AND SUBSTRACTIONS
-
- def add(self, hours=0, minutes=0, seconds=0, microseconds=0):
- """
- Add duration to the instance.
-
- :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: Time
- """
- from .datetime import DateTime
-
- return (
- DateTime.EPOCH.at(self.hour, self.minute, self.second, self.microsecond)
- .add(
- hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds
- )
- .time()
- )
-
- def subtract(self, hours=0, minutes=0, seconds=0, microseconds=0):
- """
- Add duration to the instance.
-
- :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: Time
- """
- from .datetime import DateTime
-
- return (
- DateTime.EPOCH.at(self.hour, self.minute, self.second, self.microsecond)
- .subtract(
- hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds
- )
- .time()
- )
-
- def add_timedelta(self, delta):
- """
- Add timedelta duration to the instance.
-
- :param delta: The timedelta instance
- :type delta: datetime.timedelta
-
- :rtype: Time
- """
- if delta.days:
- raise TypeError("Cannot add timedelta with days to Time.")
-
- return self.add(seconds=delta.seconds, microseconds=delta.microseconds)
-
- def subtract_timedelta(self, delta):
- """
- Remove timedelta duration from the instance.
-
- :param delta: The timedelta instance
- :type delta: datetime.timedelta
-
- :rtype: Time
- """
- if delta.days:
- raise TypeError("Cannot subtract timedelta with days to Time.")
-
- return self.subtract(seconds=delta.seconds, microseconds=delta.microseconds)
-
- def __add__(self, other):
- if not isinstance(other, timedelta):
- return NotImplemented
-
- return self.add_timedelta(other)
-
- def __sub__(self, other):
- if not isinstance(other, (Time, time, timedelta)):
- return NotImplemented
-
- if isinstance(other, timedelta):
- return self.subtract_timedelta(other)
-
- if isinstance(other, time):
- if other.tzinfo is not None:
- raise TypeError("Cannot subtract aware times to or from Time.")
-
- other = self.__class__(
- other.hour, other.minute, other.second, other.microsecond
- )
-
- return other.diff(self, False)
-
- def __rsub__(self, other):
- if not isinstance(other, (Time, time)):
- return NotImplemented
-
- if isinstance(other, time):
- if other.tzinfo is not None:
- raise TypeError("Cannot subtract aware times to or from Time.")
-
- other = self.__class__(
- other.hour, other.minute, other.second, other.microsecond
- )
-
- return other.__sub__(self)
-
- # DIFFERENCES
-
- def diff(self, dt=None, abs=True):
- """
- Returns the difference between two Time objects as an Duration.
-
- :type dt: Time or None
-
- :param abs: Whether to return an absolute interval or not
- :type abs: bool
-
- :rtype: Duration
- """
- if dt is None:
- dt = pendulum.now().time()
- else:
- dt = self.__class__(dt.hour, dt.minute, dt.second, dt.microsecond)
-
- us1 = (
- self.hour * SECS_PER_HOUR + self.minute * SECS_PER_MIN + self.second
- ) * USECS_PER_SEC
-
- us2 = (
- dt.hour * SECS_PER_HOUR + dt.minute * SECS_PER_MIN + dt.second
- ) * USECS_PER_SEC
-
- klass = Duration
- if abs:
- klass = AbsoluteDuration
-
- return klass(microseconds=us2 - us1)
-
- def diff_for_humans(self, other=None, absolute=False, locale=None):
- """
- Get the difference in a human readable format in the current locale.
-
- :type other: Time or time
-
- :param absolute: removes time difference modifiers ago, after, etc
- :type absolute: bool
-
- :param locale: The locale to use for localization
- :type locale: str
-
- :rtype: str
- """
- is_now = other is None
-
- if is_now:
- other = pendulum.now().time()
-
- diff = self.diff(other)
-
- return pendulum.format_diff(diff, is_now, absolute, locale)
-
- # Compatibility methods
-
- def replace(
- self, hour=None, minute=None, second=None, microsecond=None, tzinfo=True
- ):
- if tzinfo is True:
- tzinfo = self.tzinfo
-
- hour = hour if hour is not None else self.hour
- minute = minute if minute is not None else self.minute
- second = second if second is not None else self.second
- microsecond = microsecond if microsecond is not None else self.microsecond
-
- t = super(Time, self).replace(hour, minute, second, microsecond, tzinfo=tzinfo)
- return self.__class__(
- t.hour, t.minute, t.second, t.microsecond, tzinfo=t.tzinfo
- )
-
- def __getnewargs__(self):
- return (self,)
-
- def _get_state(self, protocol=3):
- tz = self.tzinfo
-
- return (self.hour, self.minute, self.second, self.microsecond, tz)
-
- def __reduce__(self):
- return self.__reduce_ex__(2)
-
- def __reduce_ex__(self, protocol):
- return self.__class__, self._get_state(protocol)
-
-
-Time.min = Time(0, 0, 0)
-Time.max = Time(23, 59, 59, 999999)
-Time.resolution = Duration(microseconds=1)
+from __future__ import annotations + +import datetime + +from datetime import time +from datetime import timedelta +from typing import TYPE_CHECKING +from typing import Optional +from typing import cast +from typing import overload + +import pendulum + +from pendulum.constants import SECS_PER_HOUR +from pendulum.constants import SECS_PER_MIN +from pendulum.constants import USECS_PER_SEC +from pendulum.duration import AbsoluteDuration +from pendulum.duration import Duration +from pendulum.mixins.default import FormattableMixin + +if TYPE_CHECKING: + from typing import Literal + + +class Time(FormattableMixin, time): + """ + Represents a time instance as hour, minute, second, microsecond. + """ + + # String formatting + def __repr__(self) -> str: + us = "" + if self.microsecond: + us = f", {self.microsecond}" + + tzinfo = "" + if self.tzinfo: + tzinfo = f", tzinfo={repr(self.tzinfo)}" + + return ( + f"{self.__class__.__name__}" + f"({self.hour}, {self.minute}, {self.second}{us}{tzinfo})" + ) + + # Comparisons + + def closest(self, dt1: Time | time, dt2: Time | time) -> Time: + """ + Get the closest time from the instance. + """ + dt1 = self.__class__(dt1.hour, dt1.minute, dt1.second, dt1.microsecond) + dt2 = self.__class__(dt2.hour, dt2.minute, dt2.second, dt2.microsecond) + + if self.diff(dt1).in_seconds() < self.diff(dt2).in_seconds(): + return dt1 + + return dt2 + + def farthest(self, dt1: Time | time, dt2: Time | time) -> Time: + """ + Get the farthest time from the instance. + """ + dt1 = self.__class__(dt1.hour, dt1.minute, dt1.second, dt1.microsecond) + dt2 = self.__class__(dt2.hour, dt2.minute, dt2.second, dt2.microsecond) + + if self.diff(dt1).in_seconds() > self.diff(dt2).in_seconds(): + return dt1 + + return dt2 + + # ADDITIONS AND SUBSTRACTIONS + + def add( + self, hours: int = 0, minutes: int = 0, seconds: int = 0, microseconds: int = 0 + ) -> Time: + """ + Add duration to the instance. + + :param hours: The number of hours + :param minutes: The number of minutes + :param seconds: The number of seconds + :param microseconds: The number of microseconds + """ + from pendulum.datetime import DateTime + + return ( + DateTime.EPOCH.at(self.hour, self.minute, self.second, self.microsecond) + .add( + hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds + ) + .time() + ) + + def subtract( + self, hours: int = 0, minutes: int = 0, seconds: int = 0, microseconds: int = 0 + ) -> Time: + """ + Add duration to the instance. + + :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: Time + """ + from pendulum.datetime import DateTime + + return ( + DateTime.EPOCH.at(self.hour, self.minute, self.second, self.microsecond) + .subtract( + hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds + ) + .time() + ) + + def add_timedelta(self, delta: datetime.timedelta) -> Time: + """ + Add timedelta duration to the instance. + + :param delta: The timedelta instance + """ + if delta.days: + raise TypeError("Cannot add timedelta with days to Time.") + + return self.add(seconds=delta.seconds, microseconds=delta.microseconds) + + def subtract_timedelta(self, delta: datetime.timedelta) -> Time: + """ + Remove timedelta duration from the instance. + + :param delta: The timedelta instance + """ + if delta.days: + raise TypeError("Cannot subtract timedelta with days to Time.") + + return self.subtract(seconds=delta.seconds, microseconds=delta.microseconds) + + def __add__(self, other: datetime.timedelta) -> Time: + if not isinstance(other, timedelta): + return NotImplemented + + return self.add_timedelta(other) + + @overload + def __sub__(self, other: time) -> pendulum.Duration: + ... + + @overload + def __sub__(self, other: datetime.timedelta) -> Time: + ... + + def __sub__(self, other: time | datetime.timedelta) -> pendulum.Duration | Time: + if not isinstance(other, (Time, time, timedelta)): + return NotImplemented + + if isinstance(other, timedelta): + return self.subtract_timedelta(other) + + if isinstance(other, time): + if other.tzinfo is not None: + raise TypeError("Cannot subtract aware times to or from Time.") + + other = self.__class__( + other.hour, other.minute, other.second, other.microsecond + ) + + return other.diff(self, False) + + @overload + def __rsub__(self, other: time) -> pendulum.Duration: + ... + + @overload + def __rsub__(self, other: datetime.timedelta) -> Time: + ... + + def __rsub__(self, other: time | datetime.timedelta) -> pendulum.Duration | Time: + if not isinstance(other, (Time, time)): + return NotImplemented + + if isinstance(other, time): + if other.tzinfo is not None: + raise TypeError("Cannot subtract aware times to or from Time.") + + other = self.__class__( + other.hour, other.minute, other.second, other.microsecond + ) + + return other.__sub__(self) + + # DIFFERENCES + + def diff(self, dt: time | None = None, abs: bool = True) -> Duration: + """ + Returns the difference between two Time objects as an Duration. + + :param dt: The time to subtract from + :param abs: Whether to return an absolute duration or not + """ + if dt is None: + dt = pendulum.now().time() + else: + dt = self.__class__(dt.hour, dt.minute, dt.second, dt.microsecond) + + us1 = ( + self.hour * SECS_PER_HOUR + self.minute * SECS_PER_MIN + self.second + ) * USECS_PER_SEC + + us2 = ( + dt.hour * SECS_PER_HOUR + dt.minute * SECS_PER_MIN + dt.second + ) * USECS_PER_SEC + + klass = Duration + if abs: + klass = AbsoluteDuration + + return klass(microseconds=us2 - us1) + + def diff_for_humans( + self, + other: time | None = None, + absolute: bool = False, + locale: str | None = None, + ) -> str: + """ + Get the difference in a human readable format in the current locale. + + :param dt: The time to subtract from + :param absolute: removes time difference modifiers ago, after, etc + :param locale: The locale to use for localization + """ + is_now = other is None + + if is_now: + other = pendulum.now().time() + + diff = self.diff(other) + + return pendulum.format_diff(diff, is_now, absolute, locale) + + # Compatibility methods + + def replace( + self, + 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 = 0, + ) -> Time: + if tzinfo is True: + tzinfo = self.tzinfo + + hour = hour if hour is not None else self.hour + minute = minute if minute is not None else self.minute + second = second if second is not None else self.second + microsecond = microsecond if microsecond is not None else self.microsecond + + t = super().replace( + hour, + minute, + second, + microsecond, + tzinfo=cast(Optional[datetime.tzinfo], tzinfo), + fold=fold, + ) + return self.__class__( + t.hour, t.minute, t.second, t.microsecond, tzinfo=t.tzinfo + ) + + def __getnewargs__(self) -> tuple[Time]: + return (self,) + + def _get_state( + self, protocol: int = 3 + ) -> tuple[int, int, int, int, datetime.tzinfo | None]: + tz = self.tzinfo + + return self.hour, self.minute, self.second, self.microsecond, tz + + def __reduce__( + self, + ) -> tuple[type[Time], tuple[int, int, int, int, datetime.tzinfo | None]]: + return self.__reduce_ex__(2) + + def __reduce_ex__( # type: ignore[override] + self, protocol: int + ) -> tuple[type[Time], tuple[int, int, int, int, datetime.tzinfo | None]]: + return self.__class__, self._get_state(protocol) + + +Time.min = Time(0, 0, 0) +Time.max = Time(23, 59, 59, 999999) +Time.resolution = Duration(microseconds=1) |