summaryrefslogtreecommitdiffstats
path: root/src/pendulum/time.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pendulum/time.py')
-rw-r--r--src/pendulum/time.py321
1 files changed, 321 insertions, 0 deletions
diff --git a/src/pendulum/time.py b/src/pendulum/time.py
new file mode 100644
index 0000000..23c79c0
--- /dev/null
+++ b/src/pendulum/time.py
@@ -0,0 +1,321 @@
+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
+from pendulum.tz.timezone import UTC
+
+
+if TYPE_CHECKING:
+ from typing_extensions import Literal
+ from typing_extensions import Self
+ from typing_extensions import SupportsIndex
+
+ from pendulum.tz.timezone import FixedTimezone
+ from pendulum.tz.timezone import Timezone
+
+
+class Time(FormattableMixin, time):
+ """
+ Represents a time instance as hour, minute, second, microsecond.
+ """
+
+ @classmethod
+ def instance(
+ cls, t: time, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = UTC
+ ) -> Self:
+ tz = t.tzinfo or tz
+
+ if tz is not None:
+ tz = pendulum._safe_timezone(tz)
+
+ return cls(t.hour, t.minute, t.second, t.microsecond, tzinfo=tz, fold=t.fold)
+
+ # String formatting
+ def __repr__(self) -> str:
+ us = ""
+ if self.microsecond:
+ us = f", {self.microsecond}"
+
+ tzinfo = ""
+ if self.tzinfo:
+ tzinfo = f", tzinfo={self.tzinfo!r}"
+
+ return (
+ f"{self.__class__.__name__}"
+ f"({self.hour}, {self.minute}, {self.second}{us}{tzinfo})"
+ )
+
+ # Comparisons
+
+ def closest(self, dt1: Time | time, dt2: Time | time) -> Self:
+ """
+ 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) -> Self:
+ """
+ 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: SupportsIndex | None = None,
+ minute: SupportsIndex | None = None,
+ second: SupportsIndex | None = None,
+ microsecond: SupportsIndex | None = None,
+ tzinfo: bool | datetime.tzinfo | Literal[True] | None = True,
+ fold: int = 0,
+ ) -> Self:
+ 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: SupportsIndex = 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__(
+ self, protocol: SupportsIndex
+ ) -> 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)