summaryrefslogtreecommitdiffstats
path: root/src/pendulum/helpers.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/pendulum/helpers.py')
-rw-r--r--src/pendulum/helpers.py221
1 files changed, 221 insertions, 0 deletions
diff --git a/src/pendulum/helpers.py b/src/pendulum/helpers.py
new file mode 100644
index 0000000..5d5fe8e
--- /dev/null
+++ b/src/pendulum/helpers.py
@@ -0,0 +1,221 @@
+from __future__ import annotations
+
+import os
+import struct
+
+from datetime import date
+from datetime import datetime
+from datetime import timedelta
+from math import copysign
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import overload
+
+import pendulum
+
+from pendulum.constants import DAYS_PER_MONTHS
+from pendulum.day import WeekDay
+from pendulum.formatting.difference_formatter import DifferenceFormatter
+from pendulum.locales.locale import Locale
+
+
+if TYPE_CHECKING:
+ # Prevent import cycles
+ from pendulum.duration import Duration
+
+with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"
+
+_DT = TypeVar("_DT", bound=datetime)
+_D = TypeVar("_D", bound=date)
+
+try:
+ if not with_extensions or struct.calcsize("P") == 4:
+ raise ImportError()
+
+ from pendulum._pendulum import PreciseDiff
+ from pendulum._pendulum import days_in_year
+ from pendulum._pendulum import is_leap
+ from pendulum._pendulum import is_long_year
+ from pendulum._pendulum import local_time
+ from pendulum._pendulum import precise_diff
+ from pendulum._pendulum import week_day
+except ImportError:
+ from pendulum._helpers import PreciseDiff # type: ignore[assignment]
+ from pendulum._helpers import days_in_year
+ from pendulum._helpers import is_leap
+ from pendulum._helpers import is_long_year
+ from pendulum._helpers import local_time
+ from pendulum._helpers import precise_diff # type: ignore[assignment]
+ from pendulum._helpers import week_day
+
+difference_formatter = DifferenceFormatter()
+
+
+@overload
+def add_duration(
+ dt: _DT,
+ years: int = 0,
+ months: int = 0,
+ weeks: int = 0,
+ days: int = 0,
+ hours: int = 0,
+ minutes: int = 0,
+ seconds: float = 0,
+ microseconds: int = 0,
+) -> _DT:
+ ...
+
+
+@overload
+def add_duration(
+ dt: _D,
+ years: int = 0,
+ months: int = 0,
+ weeks: int = 0,
+ days: int = 0,
+) -> _D:
+ pass
+
+
+def add_duration(
+ dt: date | datetime,
+ years: int = 0,
+ months: int = 0,
+ weeks: int = 0,
+ days: int = 0,
+ hours: int = 0,
+ minutes: int = 0,
+ seconds: float = 0,
+ microseconds: int = 0,
+) -> date | datetime:
+ """
+ Adds a duration to a date/datetime instance.
+ """
+ days += weeks * 7
+
+ if (
+ isinstance(dt, date)
+ and not isinstance(dt, datetime)
+ and any([hours, minutes, seconds, microseconds])
+ ):
+ raise RuntimeError("Time elements cannot be added to a date instance.")
+
+ # Normalizing
+ if abs(microseconds) > 999999:
+ s = _sign(microseconds)
+ div, mod = divmod(microseconds * s, 1000000)
+ microseconds = mod * s
+ seconds += div * s
+
+ if abs(seconds) > 59:
+ s = _sign(seconds)
+ div, mod = divmod(seconds * s, 60) # type: ignore[assignment]
+ seconds = mod * s
+ minutes += div * s
+
+ if abs(minutes) > 59:
+ s = _sign(minutes)
+ div, mod = divmod(minutes * s, 60)
+ minutes = mod * s
+ hours += div * s
+
+ if abs(hours) > 23:
+ s = _sign(hours)
+ div, mod = divmod(hours * s, 24)
+ hours = mod * s
+ days += div * s
+
+ if abs(months) > 11:
+ s = _sign(months)
+ div, mod = divmod(months * s, 12)
+ months = mod * s
+ years += div * s
+
+ year = dt.year + years
+ month = dt.month
+
+ if months:
+ month += months
+ if month > 12:
+ year += 1
+ month -= 12
+ elif month < 1:
+ year -= 1
+ month += 12
+
+ day = min(DAYS_PER_MONTHS[int(is_leap(year))][month], dt.day)
+
+ dt = dt.replace(year=year, month=month, day=day)
+
+ return dt + timedelta(
+ days=days,
+ hours=hours,
+ minutes=minutes,
+ seconds=seconds,
+ microseconds=microseconds,
+ )
+
+
+def format_diff(
+ diff: Duration,
+ is_now: bool = True,
+ absolute: bool = False,
+ locale: str | None = None,
+) -> str:
+ if locale is None:
+ locale = get_locale()
+
+ return difference_formatter.format(diff, is_now, absolute, locale)
+
+
+def _sign(x: float) -> int:
+ return int(copysign(1, x))
+
+
+# Global helpers
+
+
+def locale(name: str) -> Locale:
+ return Locale.load(name)
+
+
+def set_locale(name: str) -> None:
+ locale(name)
+
+ pendulum._LOCALE = name
+
+
+def get_locale() -> str:
+ return pendulum._LOCALE
+
+
+def week_starts_at(wday: WeekDay) -> None:
+ if wday < WeekDay.MONDAY or wday > WeekDay.SUNDAY:
+ raise ValueError("Invalid day of week")
+
+ pendulum._WEEK_STARTS_AT = wday
+
+
+def week_ends_at(wday: WeekDay) -> None:
+ if wday < WeekDay.MONDAY or wday > WeekDay.SUNDAY:
+ raise ValueError("Invalid day of week")
+
+ pendulum._WEEK_ENDS_AT = wday
+
+
+__all__ = [
+ "PreciseDiff",
+ "days_in_year",
+ "is_leap",
+ "is_long_year",
+ "local_time",
+ "precise_diff",
+ "week_day",
+ "add_duration",
+ "format_diff",
+ "locale",
+ "set_locale",
+ "get_locale",
+ "week_starts_at",
+ "week_ends_at",
+]