From abdc3f5948d2922529d8a1dbada14f013e3927fb Mon Sep 17 00:00:00 2001 From: Arie Bovenberg Date: Wed, 27 Nov 2024 19:30:14 +0100 Subject: [PATCH] ensure docstrings and error messages consistent between Rust/Python --- .github/workflows/checks.yml | 48 +- CHANGELOG.rst | 12 + Makefile | 10 +- docs/api.rst | 1 - generate_docstrings.py | 194 ++++ pyproject.toml | 2 +- pysrc/whenever/_pywhenever.py | 141 ++- src/common.rs | 11 +- src/date.rs | 52 +- src/date_delta.rs | 23 +- src/datetime_delta.rs | 25 +- src/docstrings.rs | 1953 +++++++++++++++++++++++++++++++++ src/instant.rs | 117 +- src/lib.rs | 121 +- src/local_datetime.rs | 113 +- src/monthday.rs | 30 +- src/offset_datetime.rs | 159 +-- src/system_datetime.rs | 111 +- src/time.rs | 32 +- src/time_delta.rs | 42 +- src/yearmonth.rs | 28 +- src/zoned_datetime.rs | 116 +- tests/test_instant.py | 9 + tests/test_offset_datetime.py | 3 + tests/test_system_datetime.py | 6 + tests/test_zoned_datetime.py | 5 +- 26 files changed, 2618 insertions(+), 746 deletions(-) create mode 100644 generate_docstrings.py create mode 100644 src/docstrings.rs diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 850ea15d..ae14674c 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -42,7 +42,7 @@ jobs: pip install -r requirements/test.txt pytest tests/ - Test-os: + test-os: name: Test on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: @@ -116,7 +116,8 @@ jobs: env: WHENEVER_NO_BUILD_RUST_EXT: "1" - Linting: + lint: + name: Linting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -135,7 +136,29 @@ jobs: env: WHENEVER_NO_BUILD_RUST_EXT: "1" - Typecheck: + check-docstrings: + name: Ensure docstrings in Rust/Python are synced + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + - run: | + pip install . + python generate_docstrings.py > fresh_docstrings.rs + if diff -q fresh_docstrings.rs src/docstrings.rs > /dev/null; then + echo "OK" + else + echo "Rust docstrings are stale. Please run 'python generate_docstrings.py > src/docstrings.rs'"; + # output the actual diff + diff -u fresh_docstrings.rs src/docstrings.rs + exit 1 + fi + env: + WHENEVER_NO_BUILD_RUST_EXT: "1" + typecheck: + name: Typecheck Python code runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -149,3 +172,22 @@ jobs: make typecheck env: WHENEVER_NO_BUILD_RUST_EXT: "1" + + # https://github.com/marketplace/actions/alls-green#why + all-green: + name: Are all checks green? + if: always() + needs: + - test-python-versions + - test-os + - test-pure-python + - lint + - check-docstrings + - typecheck + runs-on: ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e64ee73f..57e09d8d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,18 @@ 🚀 Changelog ============ +0.6.14 (2024-11-27) +------------------- + +**Fixed** + +- Ensure docstrings and error messages are consistent in Rust extension + as well as the pure-Python version +- Remove ondocumented properties ``hour/minute/etc`` from ``Instant`` + that were accidentally left in the Rust extension. +- ``exact_eq()`` now also raises ``TypeError`` in the pure Python version + when comparing different types. + 0.6.13 (2024-11-17) ------------------- diff --git a/Makefile b/Makefile index 45dd420a..2be4828a 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,8 @@ typecheck: .PHONY: format format: - black pysrc/ tests/ - isort pysrc/ tests/ + black pysrc/ tests/ generate_docstrings.py + isort pysrc/ tests/ generate_docstrings.py cargo fmt .PHONY: docs @@ -40,9 +40,9 @@ test: test-py test-rs .PHONY: ci-lint ci-lint: check-readme - flake8 pysrc/ tests/ - black --check pysrc/ tests/ - isort --check pysrc/ tests/ + flake8 pysrc/ tests/ generate_docstrings.py + black --check pysrc/ tests/ generate_docstrings.py + isort --check pysrc/ tests/ generate_docstrings.py cargo fmt -- --check env PYTHONPATH=pysrc/ slotscheck pysrc cargo clippy -- -D warnings diff --git a/docs/api.rst b/docs/api.rst index 6fb9298e..ed782616 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -118,7 +118,6 @@ Deltas .. autoclass:: whenever.DateTimeDelta :members: - :undoc-members: date_part, time_part :special-members: __eq__, __neg__, __abs__, __add__, __sub__, __bool__, __mul__ :member-order: bysource diff --git a/generate_docstrings.py b/generate_docstrings.py new file mode 100644 index 00000000..333754f7 --- /dev/null +++ b/generate_docstrings.py @@ -0,0 +1,194 @@ +"""This script ensures the Rust extension docstrings are identical to the +Python ones. + +It does so by parsing the Python docstrings and generating a Rust file with the +same docstrings. This file is then included in the Rust extension. +""" + +import enum +import inspect +import sys +from itertools import chain + +from whenever import _pywhenever as W + +assert sys.version_info >= ( + 3, + 13, +), "This script requires Python 3.13 or later due to how docstrings are rendered." + +classes = { + cls + for name, cls in W.__dict__.items() + if ( + not name.startswith("_") + and inspect.isclass(cls) + and cls.__module__ == "whenever" + and not issubclass(cls, enum.Enum) + ) +} +functions = { + func + for name, func in inspect.getmembers(W) + if ( + not name.startswith("_") + and inspect.isfunction(func) + and func.__module__ == "whenever" + ) +} + + +methods = { + getattr(cls, name) + for cls in chain( + classes, + ( + # some methods are documented in their ABCs + W._BasicConversions, + W._KnowsLocal, + W._KnowsInstant, + W._KnowsInstantAndLocal, + ), + ) + for name, m in cls.__dict__.items() + if ( + not name.startswith("_") + and ( + inspect.isfunction(m) + or + # this catches classmethods + inspect.ismethod(getattr(cls, name)) + ) + ) +} + +MAGIC_STRINGS = { + (name, value) + for name, value in W.__dict__.items() + if isinstance(value, str) and name.isupper() and not name.startswith("_") +} + +CSTR_TEMPLATE = 'pub(crate) const {varname}: &CStr = c"\\\n{doc}";' +STR_TEMPLATE = 'pub(crate) const {varname}: &str = "{value}";' +SIG_TEMPLATE = "{name}({self}, offset=0, /)\n--\n\n{doc}" +HEADER = """\ +// Do not manually edit this file. +// It has been autogenerated by generate_docstrings.py +use std::ffi::CStr; +""" + +MANUALLY_DEFINED_SIGS: dict[object, str] = { + W.ZonedDateTime.add: """\ +($self, delta=None, /, *, years=0, months=0, days=0, hours=0, \ +minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ +disambiguate=None)""", + W.ZonedDateTime.replace: """\ +($self, /, *, year=None, month=None, day=None, hour=None, \ +minute=None, second=None, nanosecond=None, tz=None, disambiguate)""", + W.OffsetDateTime.add: """\ +($self, delta=None, /, *, years=0, months=0, weeks=0, days=0, \ +hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ +ignore_dst=False)""", + W.OffsetDateTime.replace: """\ +($self, /, *, year=None, month=None, day=None, hour=None, \ +minute=None, second=None, nanosecond=None, offset=None, ignore_dst=False)""", + W.LocalDateTime.add: """\ +($self, delta=None, /, *, years=0, months=0, days=0, \ +hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ +ignore_dst=False)""", + W.LocalDateTime.replace: """\ +($self, /, *, year=None, month=None, day=None, hour=None, \ +minute=None, second=None, nanosecond=None)""", + W.Date.replace: "($self, /, *, year=None, month=None, day=None)", + W.MonthDay.replace: "($self, /, *, month=None, day=None)", + W.Time.replace: "($self, /, *, hour=None, minute=None, second=None, nanosecond=None)", + W.YearMonth.replace: "($self, /, *, year=None, month=None)", + W.Instant.add: """\ +($self, delta=None, /, *, hours=0, minutes=0, seconds=0, \ +milliseconds=0, microseconds=0, nanoseconds=0)""", +} +MANUALLY_DEFINED_SIGS.update( + { + W.ZonedDateTime.subtract: MANUALLY_DEFINED_SIGS[W.ZonedDateTime.add], + W.SystemDateTime.add: MANUALLY_DEFINED_SIGS[W.ZonedDateTime.add], + W.SystemDateTime.subtract: MANUALLY_DEFINED_SIGS[W.ZonedDateTime.add], + W.SystemDateTime.replace: MANUALLY_DEFINED_SIGS[ + W.ZonedDateTime.replace + ], + W.OffsetDateTime.subtract: MANUALLY_DEFINED_SIGS[W.OffsetDateTime.add], + W.LocalDateTime.subtract: MANUALLY_DEFINED_SIGS[W.LocalDateTime.add], + W.Instant.subtract: MANUALLY_DEFINED_SIGS[W.Instant.add], + } +) +SKIP = { + W._BasicConversions.format_common_iso, + W._BasicConversions.from_py_datetime, + W._BasicConversions.parse_common_iso, + W._KnowsInstant.from_timestamp, + W._KnowsInstant.from_timestamp_millis, + W._KnowsInstant.from_timestamp_nanos, + W._KnowsInstant.now, + W._KnowsLocal.add, + W._KnowsLocal.subtract, + W._KnowsLocal.replace, + W._KnowsLocal.replace_date, + W._KnowsLocal.replace_time, +} + + +def method_doc(method): + method.__annotations__.clear() + try: + sig = MANUALLY_DEFINED_SIGS[method] + except KeyError: + sig = ( + str(inspect.signature(method)) + # We use unicode escape of '(' to avoid messing up LSP in editors + .replace("\u0028self", "\u0028$self").replace( + "\u0028cls", "\u0028$type" + ) + ) + doc = method.__doc__.replace('"', '\\"') + return f"{method.__name__}{sig}\n--\n\n{doc}" + + +def print_everything(): + print(HEADER) + for cls in sorted(classes, key=lambda x: x.__name__): + assert cls.__doc__ + print( + CSTR_TEMPLATE.format( + varname=cls.__name__.upper(), + doc=cls.__doc__.replace('"', '\\"'), + ) + ) + + for func in sorted(functions, key=lambda x: x.__name__): + assert func.__doc__ + print( + CSTR_TEMPLATE.format( + varname=func.__name__.upper(), + doc=func.__doc__.replace('"', '\\"'), + ) + ) + + for method in sorted(methods, key=lambda x: x.__qualname__): + if method.__doc__ is None or method in SKIP: + continue + + qualname = method.__qualname__ + if qualname.startswith("_"): + qualname = qualname[1:] + print( + CSTR_TEMPLATE.format( + varname=qualname.replace(".", "_").upper(), + doc=method_doc(method), + ) + ) + + for name, value in sorted(MAGIC_STRINGS): + print(STR_TEMPLATE.format(varname=name, value=value)) + + +if __name__ == "__main__": + print_everything() diff --git a/pyproject.toml b/pyproject.toml index 56e25526..86434045 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ maintainers = [ {name = "Arie Bovenberg", email = "a.c.bovenberg@gmail.com"}, ] readme = "README.md" -version = "0.6.13" +version = "0.6.14" description = "Modern datetime library for Python" requires-python = ">=3.9" classifiers = [ diff --git a/pysrc/whenever/_pywhenever.py b/pysrc/whenever/_pywhenever.py index 04fcb51f..932c5724 100644 --- a/pysrc/whenever/_pywhenever.py +++ b/pysrc/whenever/_pywhenever.py @@ -32,7 +32,7 @@ # - It saves some overhead from __future__ import annotations -__version__ = "0.6.13" +__version__ = "0.6.14" import enum import re @@ -181,8 +181,8 @@ class Date(_ImmutableBase): def __init__(self, year: int, month: int, day: int) -> None: self._py_date = _date(year, month, day) - @staticmethod - def today_in_system_tz() -> Date: + @classmethod + def today_in_system_tz(cls) -> Date: """Get the current date in the system's local timezone. Alias for ``SystemDateTime.now().date()``. @@ -321,8 +321,8 @@ def replace(self, **kwargs: Any) -> Date: Example ------- >>> d = Date(2021, 1, 2) - >>> d.replace(day=3) - Date(2021-01-03) + >>> d.replace(day=4) + Date(2021-01-04) """ return Date._from_py_unchecked(self._py_date.replace(**kwargs)) @@ -1944,9 +1944,11 @@ def __init__( """A delta of zero""" def date_part(self) -> DateDelta: + """The date part of the delta""" return self._date_part def time_part(self) -> TimeDelta: + """The time part of the delta""" return self._time_part def in_months_days_secs_nanos(self) -> tuple[int, int, int, int]: @@ -2687,7 +2689,11 @@ def exact_eq(self: _T, other: _T, /) -> bool: True # equivalent instants >>> a.exact_eq(b) False # different values (hour and offset) + >>> a.exact_eq(Instant.now()) + TypeError # different types """ + if type(self) is not type(other): + raise TypeError("Cannot compare different types") return ( self._py_dt, # type: ignore[attr-defined] self._py_dt.utcoffset(), # type: ignore[attr-defined] @@ -2872,7 +2878,7 @@ def offset(self) -> TimeDelta: ) def instant(self) -> Instant: - """Get the underlying instant + """Get the underlying instant in time Example ------- @@ -3346,14 +3352,7 @@ def now( `the documentation `_. """ if ignore_dst is not True: - raise ImplicitlyIgnoringDST( - "Getting the current time with a fixed offset implicitly ignores DST " - "and other timezone changes. Instead, use `Instant.now()` or " - "`ZonedDateTime.now()` if you know the timezone. " - "Or, if you want to ignore DST and accept potentially incorrect offsets, " - "pass `ignore_dst=True` to this method. For more information, see " - "whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic" - ) + raise ImplicitlyIgnoringDST(OFFSET_NOW_DST_MSG) secs, nanos = divmod(time_ns(), 1_000_000_000) return cls._from_py_unchecked( _fromtimestamp(secs, _load_offset(offset)), nanos @@ -3426,7 +3425,7 @@ def from_timestamp( `the documentation `_. """ if ignore_dst is not True: - raise _EXC_TIMESTAMP_DST + raise ImplicitlyIgnoringDST(TIMESTAMP_DST_MSG) secs, fract = divmod(i, 1) return cls._from_py_unchecked( _fromtimestamp(secs, _load_offset(offset)), @@ -3453,7 +3452,7 @@ def from_timestamp_millis( `the documentation `_. """ if ignore_dst is not True: - raise _EXC_TIMESTAMP_DST + raise ImplicitlyIgnoringDST(TIMESTAMP_DST_MSG) if not isinstance(i, int): raise TypeError("method requires an integer") secs, millis = divmod(i, 1_000) @@ -3481,7 +3480,7 @@ def from_timestamp_nanos( `the documentation `_. """ if ignore_dst is not True: - raise _EXC_TIMESTAMP_DST + raise ImplicitlyIgnoringDST(TIMESTAMP_DST_MSG) if not isinstance(i, int): raise TypeError("method requires an integer") secs, nanos = divmod(i, 1_000_000_000) @@ -3533,7 +3532,7 @@ def replace( """ _check_invalid_replace_kwargs(kwargs) if ignore_dst is not True: - raise _EXC_ADJUST_OFFSET_DATETIME + raise ImplicitlyIgnoringDST(ADJUST_OFFSET_DATETIME_MSG) try: kwargs["tzinfo"] = _load_offset(kwargs.pop("offset")) except KeyError: @@ -3558,7 +3557,7 @@ def replace_date( pass ``ignore_dst=True`` to this method. """ if ignore_dst is not True: - raise _EXC_ADJUST_OFFSET_DATETIME + raise ImplicitlyIgnoringDST(ADJUST_OFFSET_DATETIME_MSG) return self._from_py_unchecked( _check_utc_bounds( _datetime.combine(date._py_date, self._py_dt.timetz()) @@ -3581,7 +3580,7 @@ def replace_time( pass ``ignore_dst=True`` to this method. """ if ignore_dst is not True: - raise _EXC_ADJUST_OFFSET_DATETIME + raise ImplicitlyIgnoringDST(ADJUST_OFFSET_DATETIME_MSG) return self._from_py_unchecked( _check_utc_bounds( _datetime.combine( @@ -3597,7 +3596,7 @@ def __hash__(self) -> int: def __sub__(self, other: _KnowsInstant) -> TimeDelta: """Calculate the duration relative to another exact time.""" if isinstance(other, (TimeDelta, DateDelta, DateTimeDelta)): - raise _EXC_ADJUST_OFFSET_DATETIME + raise ImplicitlyIgnoringDST(ADJUST_OFFSET_DATETIME_MSG) return super().__sub__(other) # type: ignore[misc, no-any-return] @classmethod @@ -3786,7 +3785,7 @@ def _shift( **kwargs, ) -> OffsetDateTime: if ignore_dst is not True: - raise _EXC_ADJUST_OFFSET_DATETIME + raise ImplicitlyIgnoringDST(ADJUST_OFFSET_DATETIME_MSG) elif kwargs: if arg is _UNSET: return self._shift_kwargs(sign, **kwargs) @@ -4170,11 +4169,7 @@ def __add__(self, delta: TimeDelta) -> ZonedDateTime: nanos, ) elif isinstance(delta, (DateDelta, DateTimeDelta)): - raise TypeError( - "Addition/subtraction of calendar units on a ZonedDateTime requires " - "explicit disambiguation. Use the `add`/`subtract` methods instead. " - "For example, instead of `dt + delta` use `dt.add(delta, disambiguate=...)`." - ) + raise TypeError(SHIFT_OPERATOR_CALENDAR_ZONED_MSG) return NotImplemented @overload @@ -4575,11 +4570,7 @@ def __add__(self, delta: TimeDelta) -> SystemDateTime: (py_dt + _timedelta(seconds=delta_secs)).astimezone(), nanos ) elif isinstance(delta, (DateDelta, DateTimeDelta)): - raise TypeError( - "Addition/subtraction of calendar units on a SystemDateTime requires " - "explicit disambiguation. Use the `add`/`subtract` methods instead. " - "For example, instead of `dt + delta` use `dt.add(delta, disambiguate=...)`." - ) + raise TypeError(SHIFT_OPERATOR_CALENDAR_ZONED_MSG) return NotImplemented @overload @@ -4890,11 +4881,7 @@ def __add__(self, delta: DateDelta) -> LocalDateTime: self._nanos, ) elif isinstance(delta, (TimeDelta, DateTimeDelta)): - raise ImplicitlyIgnoringDST( - "Adding or subtracting a (date)time delta to a local datetime " - "implicitly ignores DST transitions and other timezone " - "changes. Instead, use the `add` or `subtract` method." - ) + raise ImplicitlyIgnoringDST(SHIFT_LOCAL_MSG) return NotImplemented def __sub__(self, other: DateDelta) -> LocalDateTime: @@ -4906,11 +4893,7 @@ def __sub__(self, other: DateDelta) -> LocalDateTime: if isinstance(other, (DateDelta, TimeDelta, DateTimeDelta)): return self + -other elif isinstance(other, LocalDateTime): - raise ImplicitlyIgnoringDST( - "The difference between two local datetimes implicitly ignores " - "DST transitions and other timezone changes. " - "Use the `difference` method instead." - ) + raise ImplicitlyIgnoringDST(DIFF_OPERATOR_LOCAL_MSG) return NotImplemented def difference( @@ -4929,11 +4912,7 @@ def difference( see `the docs `_. """ if ignore_dst is not True: - raise ImplicitlyIgnoringDST( - "The difference between two local datetimes implicitly ignores " - "DST transitions and other timezone changes. " - + _IGNORE_DST_SUGGESTION - ) + raise ImplicitlyIgnoringDST(DIFF_LOCAL_MSG) py_delta = self._py_dt - other._py_dt return TimeDelta( @@ -5032,7 +5011,7 @@ def _shift_kwargs( nanoseconds=nanoseconds, ) if tdelta and ignore_dst is not True: - raise _EXC_ADJUST_LOCAL_DATETIME + raise ImplicitlyIgnoringDST(ADJUST_LOCAL_DATETIME_MSG) delta_secs, nanos = divmod( tdelta._total_ns + self._nanos, 1_000_000_000 @@ -5171,7 +5150,6 @@ def _unpkl_local(data: bytes) -> LocalDateTime: return LocalDateTime._from_py_unchecked(_datetime(*args), nanos) -# RepeatedTime class RepeatedTime(Exception): """A datetime is repeated in a timezone, e.g. because of DST""" @@ -5212,7 +5190,40 @@ class ImplicitlyIgnoringDST(TypeError): """A calculation was performed that implicitly ignored DST""" -_EXC_TIMESTAMP_DST = ImplicitlyIgnoringDST( +_IGNORE_DST_SUGGESTION = ( + "To perform DST-safe operations, convert to a ZonedDateTime first. " + "Or, if you don't know the timezone and accept potentially incorrect results " + "during DST transitions, pass `ignore_dst=True`. For more information, see " + "whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic" +) + + +SHIFT_LOCAL_MSG = ( + "Adding or subtracting a (date)time delta to a local datetime " + "implicitly ignores DST transitions and other timezone " + "changes. Instead, use the `add` or `subtract` method." +) + +DIFF_OPERATOR_LOCAL_MSG = ( + "The difference between two local datetimes implicitly ignores " + "DST transitions and other timezone changes. " + "Use the `difference` method instead." +) + +DIFF_LOCAL_MSG = ( + "The difference between two local datetimes implicitly ignores " + "DST transitions and other timezone changes. " + _IGNORE_DST_SUGGESTION +) + + +SHIFT_OPERATOR_CALENDAR_ZONED_MSG = ( + "Addition/subtraction of calendar units on a Zoned/System-DateTime requires " + "explicit disambiguation. Use the `add`/`subtract` methods instead. " + "For example, instead of `dt + delta` use `dt.add(delta, disambiguate=...)`." +) + + +TIMESTAMP_DST_MSG = ( "Converting from a timestamp with a fixed offset implicitly ignores DST " "and other timezone changes. To perform a DST-safe conversion, use " "ZonedDateTime.from_timestamp() instead. " @@ -5222,20 +5233,21 @@ class ImplicitlyIgnoringDST(TypeError): ) -_IGNORE_DST_SUGGESTION = """\ -To perform DST-safe operations, convert to a ZonedDateTime first. \ -Or, if you don't know the timezone and accept potentially incorrect results \ -during DST transitions, pass `ignore_dst=True`. For more information, see \ -whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic" -""" - +OFFSET_NOW_DST_MSG = ( + "Getting the current time with a fixed offset implicitly ignores DST " + "and other timezone changes. Instead, use `Instant.now()` or " + "`ZonedDateTime.now()` if you know the timezone. " + "Or, if you want to ignore DST and accept potentially incorrect offsets, " + "pass `ignore_dst=True` to this method. For more information, see " + "whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic" +) -_EXC_ADJUST_OFFSET_DATETIME = ImplicitlyIgnoringDST( +ADJUST_OFFSET_DATETIME_MSG = ( "Adjusting a fixed offset datetime implicitly ignores DST and other timezone changes. " + _IGNORE_DST_SUGGESTION ) -_EXC_ADJUST_LOCAL_DATETIME = ImplicitlyIgnoringDST( +ADJUST_LOCAL_DATETIME_MSG = ( "Adjusting a local datetime by time units (e.g. hours and minutess) ignores " "DST and other timezone changes. " + _IGNORE_DST_SUGGESTION ) @@ -5505,11 +5517,20 @@ def nanoseconds(i: int, /) -> TimeDelta: return TimeDelta(nanoseconds=i) -for name in __all__: +# We expose the public members in the root of the module. +# For clarity, we remove the "_pywhenever" part from the names, +# since this is an implementation detail. +for name in ( + __all__ + "_KnowsLocal _KnowsInstant _KnowsInstantAndLocal".split() +): member = locals()[name] if getattr(member, "__module__", None) == __name__: # pragma: no branch member.__module__ = "whenever" +# clear up loop variables so they don't leak into the namespace +del name +del member + for _unpkl in ( _unpkl_date, _unpkl_ym, diff --git a/src/common.rs b/src/common.rs index de66f818..a325a8cd 100644 --- a/src/common.rs +++ b/src/common.rs @@ -41,7 +41,7 @@ macro_rules! py_err( ); macro_rules! value_err( - ($msg:literal) => { + ($msg:expr) => { py_err!(PyExc_ValueError, $msg) }; ($msg:literal, $($args:expr),*) => { @@ -50,7 +50,7 @@ macro_rules! value_err( ); macro_rules! type_err( - ($msg:literal) => { + ($msg:expr) => { py_err!(PyExc_TypeError, $msg) }; ($msg:literal, $($args:expr),*) => { @@ -125,7 +125,7 @@ macro_rules! method( }, }, ml_flags: $flags, - ml_doc: cstr!($doc), + ml_doc: $doc.as_ptr() } }; ); @@ -162,7 +162,7 @@ macro_rules! method_vararg( }, }, ml_flags: METH_FASTCALL | $flags, - ml_doc: cstr!($doc), + ml_doc: $doc.as_ptr() } }; ); @@ -206,6 +206,7 @@ impl Iterator for KwargIter { } } +// FUTURE: the method macros could probably become plain functions macro_rules! method_kwargs( ($meth:ident, $doc:expr) => { method_kwargs!($meth named stringify!($meth), $doc) @@ -243,7 +244,7 @@ macro_rules! method_kwargs( }, }, ml_flags: $flags | METH_METHOD | METH_FASTCALL | METH_KEYWORDS, - ml_doc: cstr!($doc), + ml_doc: $doc.as_ptr() } }; ); diff --git a/src/date.rs b/src/date.rs index c10f1c25..9f7c723e 100644 --- a/src/date.rs +++ b/src/date.rs @@ -4,6 +4,7 @@ use pyo3_ffi::*; use std::fmt::{self, Display, Formatter}; use crate::common::*; +use crate::docstrings as doc; use crate::{ date_delta::DateDelta, instant::Instant, local_datetime::DateTime, monthday::MonthDay, time::Time, yearmonth::YearMonth, State, @@ -369,7 +370,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_nb_add, __add__, 2), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A calendar date type".as_ptr() as *mut c_void, + pfunc: doc::DATE.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_methods, @@ -713,48 +714,29 @@ unsafe fn today_in_system_tz(cls: *mut PyObject, _: *mut PyObject) -> PyReturn { } static mut METHODS: &[PyMethodDef] = &[ - method!(py_date, "Convert to a Python datetime.date"), + method!(py_date, doc::DATE_PY_DATE), method!( today_in_system_tz, - "Return the current date in the system timezone", + doc::DATE_TODAY_IN_SYSTEM_TZ, METH_CLASS | METH_NOARGS ), - method!( - format_common_iso, - "Return the date in the common ISO 8601 format" - ), + method!(format_common_iso, doc::DATE_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Create a date from the common ISO 8601 format", - METH_O | METH_CLASS - ), - method!( - from_py_date, - "Create a date from a Python datetime.date", + doc::DATE_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!(day_of_week, "Return the day of the week"), - method!(at, "Combine with a time to create a datetime", METH_O), - method!(year_month, "Return the year and month"), - method!(month_day, "Return the month and day"), - method!(__reduce__, ""), - method_kwargs!( - add, - "add($self, *, years=0, months=0, weeks=0, days=0)\n--\n\n\ - Add various units to the date" - ), - method_kwargs!( - subtract, - "subtract($self, *, years=0, months=0, weeks=0, days=0)\n--\n\n\ - Subtract various units from the date" - ), - method_kwargs!( - replace, - "replace($self, *, year=None, month=None, day=None)\n--\n\n\ - Return a new date with the specified components replaced" - ), + method!(from_py_date, doc::DATE_FROM_PY_DATE, METH_O | METH_CLASS), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(day_of_week, doc::DATE_DAY_OF_WEEK), + method!(at, doc::DATE_AT, METH_O), + method!(year_month, doc::DATE_YEAR_MONTH), + method!(month_day, doc::DATE_MONTH_DAY), + method!(__reduce__, c""), + method_kwargs!(add, doc::DATE_ADD), + method_kwargs!(subtract, doc::DATE_SUBTRACT), + method_kwargs!(replace, doc::DATE_REPLACE), PyMethodDef::zeroed(), ]; diff --git a/src/date_delta.rs b/src/date_delta.rs index c6bd2013..0bf47fe0 100644 --- a/src/date_delta.rs +++ b/src/date_delta.rs @@ -9,6 +9,7 @@ use std::ptr::null_mut as NULL; use crate::common::*; use crate::date::MAX_YEAR; use crate::datetime_delta::DateTimeDelta; +use crate::docstrings as doc; use crate::time_delta::TimeDelta; use crate::State; @@ -373,7 +374,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_nb_subtract, __sub__, 2), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A delta for calendar units".as_ptr() as *mut c_void, + pfunc: doc::DATEDELTA.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_methods, @@ -565,23 +566,17 @@ pub(crate) unsafe fn unpickle(module: *mut PyObject, args: &[*mut PyObject]) -> } static mut METHODS: &[PyMethodDef] = &[ - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!(format_common_iso, "Format as common ISO8601 period format"), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(format_common_iso, doc::DATEDELTA_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Parse from the common ISO8601 period format", + doc::DATEDELTA_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!( - in_months_days, - "Return the date delta as a tuple of months and days" - ), - method!( - in_years_months_days, - "Return the date delta as a tuple of years, months, and days" - ), - method!(__reduce__, ""), + method!(in_months_days, doc::DATEDELTA_IN_MONTHS_DAYS), + method!(in_years_months_days, doc::DATEDELTA_IN_YEARS_MONTHS_DAYS), + method!(__reduce__, c""), PyMethodDef::zeroed(), ]; diff --git a/src/datetime_delta.rs b/src/datetime_delta.rs index 5334e04a..21ba63d8 100644 --- a/src/datetime_delta.rs +++ b/src/datetime_delta.rs @@ -7,6 +7,7 @@ use std::ptr::null_mut as NULL; use crate::common::*; use crate::date_delta::{self, parse_prefix, DateDelta, InitError, Unit as DateUnit}; +use crate::docstrings as doc; use crate::time_delta::{ self, TimeDelta, MAX_HOURS, MAX_MICROSECONDS, MAX_MILLISECONDS, MAX_MINUTES, MAX_SECS, }; @@ -377,7 +378,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_nb_subtract, __sub__, 2), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A delta for calendar and exact units".as_ptr() as *mut c_void, + pfunc: doc::DATETIMEDELTA.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_methods, @@ -530,26 +531,20 @@ pub(crate) unsafe fn unpickle(module: *mut PyObject, args: &[*mut PyObject]) -> } static mut METHODS: &[PyMethodDef] = &[ - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!(format_common_iso, "Format as common ISO8601 period"), - method!( - date_part, - "Return the date part of the delta as a DateDelta" - ), - method!( - time_part, - "Return the time part of the delta as a TimeDelta" - ), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(format_common_iso, doc::DATETIMEDELTA_FORMAT_COMMON_ISO), + method!(date_part, doc::DATETIMEDELTA_DATE_PART), + method!(time_part, doc::DATETIMEDELTA_TIME_PART), method!( parse_common_iso, - "Parse from the common ISO8601 period format", + doc::DATETIMEDELTA_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!(__reduce__, ""), + method!(__reduce__, c""), method!( in_months_days_secs_nanos, - "Extract the components of the delta" + doc::DATETIMEDELTA_IN_MONTHS_DAYS_SECS_NANOS ), PyMethodDef::zeroed(), ]; diff --git a/src/docstrings.rs b/src/docstrings.rs new file mode 100644 index 00000000..5c2dd8b1 --- /dev/null +++ b/src/docstrings.rs @@ -0,0 +1,1953 @@ +// Do not manually edit this file. +// It has been autogenerated by generate_docstrings.py +use std::ffi::CStr; + +pub(crate) const DATE: &CStr = c"\ +A date without a time component + +Example +------- +>>> d = Date(2021, 1, 2) +Date(2021-01-02) +"; +pub(crate) const DATEDELTA: &CStr = c"\ +A duration of time consisting of calendar units +(years, months, weeks, and days) +"; +pub(crate) const DATETIMEDELTA: &CStr = c"\ +A duration with both a date and time component."; +pub(crate) const IMPLICITLYIGNORINGDST: &CStr = c"\ +A calculation was performed that implicitly ignored DST"; +pub(crate) const INSTANT: &CStr = c"\ +Represents a moment in time with nanosecond precision. + +This class is great for representing a specific point in time independent +of location. It maps 1:1 to UTC or a UNIX timestamp. + +Example +------- +>>> from whenever import Instant +>>> py311_release = Instant.from_utc(2022, 10, 24, hour=17) +Instant(2022-10-24 17:00:00Z) +>>> py311_release.add(hours=3).timestamp() +1666641600 +"; +pub(crate) const INVALIDOFFSET: &CStr = c"\ +A string has an invalid offset for the given zone"; +pub(crate) const LOCALDATETIME: &CStr = c"\ +A local date and time, i.e. it would appear to people on a wall clock. + +It can't be mixed with aware datetimes. +Conversion to aware datetimes can only be done by +explicitly assuming a timezone or offset. + +Examples of when to use this type: + +- You need to express a date and time as it would be observed locally + on the \"wall clock\" or calendar. +- You receive a date and time without any timezone information, + and you need a type to represent this lack of information. +- In the rare case you truly don't need to account for timezones, + or Daylight Saving Time transitions. For example, when modeling + time in a simulation game. +"; +pub(crate) const MONTHDAY: &CStr = c"\ +A month and day without a year component. + +Useful for representing recurring events or birthdays. + +Example +------- +>>> MonthDay(11, 23) +MonthDay(--11-23) +"; +pub(crate) const OFFSETDATETIME: &CStr = c"\ +A datetime with a fixed UTC offset. +Useful for representing the local time at a specific location. + +Example +------- +>>> # Midnight in Salt Lake City +>>> OffsetDateTime(2023, 4, 21, offset=-6) +OffsetDateTime(2023-04-21 00:00:00-06:00) + +Note +---- +Adjusting instances of this class do *not* account for daylight saving time. +If you need to add or subtract durations from an offset datetime +and account for DST, convert to a ``ZonedDateTime`` first, +This class knows when the offset changes. +"; +pub(crate) const REPEATEDTIME: &CStr = c"\ +A datetime is repeated in a timezone, e.g. because of DST"; +pub(crate) const SKIPPEDTIME: &CStr = c"\ +A datetime is skipped in a timezone, e.g. because of DST"; +pub(crate) const SYSTEMDATETIME: &CStr = c"\ +Represents a time in the system timezone. +It is similar to ``OffsetDateTime``, +but it knows about the system timezone and its DST transitions. + +Example +------- +>>> # 8:00 in the system timezone—Paris in this case +>>> alarm = SystemDateTime(2024, 3, 31, hour=6) +SystemDateTime(2024-03-31 06:00:00+02:00) +>>> # Conversion based on Paris' offset +>>> alarm.instant() +Instant(2024-03-31 04:00:00Z) +>>> # DST-safe arithmetic +>>> bedtime = alarm - hours(8) +SystemDateTime(2024-03-30 21:00:00+01:00) + +Attention +--------- +To use this type properly, read more about `ambiguity `_ +and `working with the system timezone `_. +"; +pub(crate) const TIME: &CStr = c"\ +Time of day without a date component + +Example +------- +>>> t = Time(12, 30, 0) +Time(12:30:00) + +"; +pub(crate) const TIMEDELTA: &CStr = c"\ +A duration consisting of a precise time: hours, minutes, (micro)seconds + +The inputs are normalized, so 90 minutes becomes 1 hour and 30 minutes, +for example. + +Examples +-------- +>>> d = TimeDelta(hours=1, minutes=30) +TimeDelta(01:30:00) +>>> d.in_minutes() +90.0 + +Note +---- +A shorter was to instantiate a timedelta is to use the helper functions +:func:`~whenever.hours`, :func:`~whenever.minutes`, etc. + +"; +pub(crate) const YEARMONTH: &CStr = c"\ +A year and month without a day component + +Useful for representing recurring events or billing periods. + +Example +------- +>>> ym = YearMonth(2021, 1) +YearMonth(2021-01) +"; +pub(crate) const ZONEDDATETIME: &CStr = c"\ +A datetime associated with a timezone in the IANA database. +Useful for representing the exact time at a specific location. + +Example +------- +>>> ZonedDateTime(2024, 12, 8, hour=11, tz=\"Europe/Paris\") +ZonedDateTime(2024-12-08 11:00:00+01:00[Europe/Paris]) +>>> # Explicitly resolve ambiguities during DST transitions +>>> ZonedDateTime(2023, 10, 29, 1, 15, tz=\"Europe/London\", disambiguate=\"earlier\") +ZonedDateTime(2023-10-29 01:15:00+01:00[Europe/London]) + +Important +--------- +To use this type properly, read more about +`ambiguity in timezones `_. +"; +pub(crate) const DAYS: &CStr = c"\ +Create a :class:`~DateDelta` with the given number of days. +``days(1) == DateDelta(days=1)`` +"; +pub(crate) const HOURS: &CStr = c"\ +Create a :class:`~TimeDelta` with the given number of hours. +``hours(1) == TimeDelta(hours=1)`` +"; +pub(crate) const MICROSECONDS: &CStr = c"\ +Create a :class:`TimeDelta` with the given number of microseconds. +``microseconds(1) == TimeDelta(microseconds=1)`` +"; +pub(crate) const MILLISECONDS: &CStr = c"\ +Create a :class:`TimeDelta` with the given number of milliseconds. +``milliseconds(1) == TimeDelta(milliseconds=1)`` +"; +pub(crate) const MINUTES: &CStr = c"\ +Create a :class:`TimeDelta` with the given number of minutes. +``minutes(1) == TimeDelta(minutes=1)`` +"; +pub(crate) const MONTHS: &CStr = c"\ +Create a :class:`~DateDelta` with the given number of months. +``months(1) == DateDelta(months=1)`` +"; +pub(crate) const NANOSECONDS: &CStr = c"\ +Create a :class:`TimeDelta` with the given number of nanoseconds. +``nanoseconds(1) == TimeDelta(nanoseconds=1)`` +"; +pub(crate) const SECONDS: &CStr = c"\ +Create a :class:`TimeDelta` with the given number of seconds. +``seconds(1) == TimeDelta(seconds=1)`` +"; +pub(crate) const WEEKS: &CStr = c"\ +Create a :class:`~DateDelta` with the given number of weeks. +``weeks(1) == DateDelta(weeks=1)`` +"; +pub(crate) const YEARS: &CStr = c"\ +Create a :class:`~DateDelta` with the given number of years. +``years(1) == DateDelta(years=1)`` +"; +pub(crate) const DATE_ADD: &CStr = c"\ +add($self, *, years=0, months=0, weeks=0, days=0) +-- + +Add a components to a date. + +See :ref:`the docs on arithmetic ` for more information. + +Example +------- +>>> d = Date(2021, 1, 2) +>>> d.add(years=1, months=2, days=3) +Date(2022-03-05) +>>> Date(2020, 2, 29).add(years=1) +Date(2021-02-28) +"; +pub(crate) const DATE_AT: &CStr = c"\ +at($self, t, /) +-- + +Combine a date with a time to create a datetime + +Example +------- +>>> d = Date(2021, 1, 2) +>>> d.at(Time(12, 30)) +LocalDateTime(2021-01-02 12:30:00) + +You can use methods like :meth:`~LocalDateTime.assume_utc` +or :meth:`~LocalDateTime.assume_tz` to make the result aware. +"; +pub(crate) const DATE_DAY_OF_WEEK: &CStr = c"\ +day_of_week($self) +-- + +The day of the week + +Example +------- +>>> Date(2021, 1, 2).day_of_week() +Weekday.SATURDAY +>>> Weekday.SATURDAY.value +6 # the ISO value +"; +pub(crate) const DATE_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Format as the common ISO 8601 date format. + +Inverse of :meth:`parse_common_iso`. + +Example +------- +>>> Date(2021, 1, 2).format_common_iso() +'2021-01-02' +"; +pub(crate) const DATE_FROM_PY_DATE: &CStr = c"\ +from_py_date(d, /) +-- + +Create from a :class:`~datetime.date` + +Example +------- +>>> Date.from_py_date(date(2021, 1, 2)) +Date(2021-01-02) +"; +pub(crate) const DATE_MONTH_DAY: &CStr = c"\ +month_day($self) +-- + +The month and day (without a year component) + +Example +------- +>>> Date(2021, 1, 2).month_day() +MonthDay(--01-02) +"; +pub(crate) const DATE_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Create from the common ISO 8601 date format ``YYYY-MM-DD``. +Does not accept more \"exotic\" ISO 8601 formats. + +Inverse of :meth:`format_common_iso` + +Example +------- +>>> Date.parse_common_iso(\"2021-01-02\") +Date(2021-01-02) +"; +pub(crate) const DATE_PY_DATE: &CStr = c"\ +py_date($self) +-- + +Convert to a standard library :class:`~datetime.date`"; +pub(crate) const DATE_REPLACE: &CStr = c"\ +replace($self, /, *, year=None, month=None, day=None) +-- + +Create a new instance with the given fields replaced + +Example +------- +>>> d = Date(2021, 1, 2) +>>> d.replace(day=4) +Date(2021-01-04) +"; +pub(crate) const DATE_SUBTRACT: &CStr = c"\ +subtract($self, *, years=0, months=0, weeks=0, days=0) +-- + +Subtract a components from a date. + +See :ref:`the docs on arithmetic ` for more information. + +Example +------- +>>> d = Date(2021, 1, 2) +>>> d.subtract(years=1, months=2, days=3) +Date(2019-10-30) +>>> Date(2021, 3, 1).subtract(years=1) +Date(2020-03-01) +"; +pub(crate) const DATE_TODAY_IN_SYSTEM_TZ: &CStr = c"\ +today_in_system_tz() +-- + +Get the current date in the system's local timezone. + +Alias for ``SystemDateTime.now().date()``. + +Example +------- +>>> Date.today_in_system_tz() +Date(2021-01-02) +"; +pub(crate) const DATE_YEAR_MONTH: &CStr = c"\ +year_month($self) +-- + +The year and month (without a day component) + +Example +------- +>>> Date(2021, 1, 2).year_month() +YearMonth(2021-01) +"; +pub(crate) const DATEDELTA_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Format as the *popular interpretation* of the ISO 8601 duration format. +May not strictly adhere to (all versions of) the standard. +See :ref:`here ` for more information. + +Inverse of :meth:`parse_common_iso`. + +The format looks like this: + +.. code-block:: text + + P(nY)(nM)(nD) + +For example: + +.. code-block:: text + + P1D + P2M + P1Y2M3W4D + +Example +------- +>>> p = DateDelta(years=1, months=2, weeks=3, days=11) +>>> p.common_iso() +'P1Y2M3W11D' +>>> DateDelta().common_iso() +'P0D' +"; +pub(crate) const DATEDELTA_IN_MONTHS_DAYS: &CStr = c"\ +in_months_days($self) +-- + +Convert to a tuple of months and days. + +Example +------- +>>> p = DateDelta(months=25, days=9) +>>> p.in_months_days() +(25, 9) +>>> DateDelta(months=-13, weeks=-5) +(-13, -35) +"; +pub(crate) const DATEDELTA_IN_YEARS_MONTHS_DAYS: &CStr = c"\ +in_years_months_days($self) +-- + +Convert to a tuple of years, months, and days. + +Example +------- +>>> p = DateDelta(years=1, months=2, days=11) +>>> p.in_years_months_days() +(1, 2, 11) +"; +pub(crate) const DATEDELTA_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Parse the *popular interpretation* of the ISO 8601 duration format. +Does not parse all possible ISO 8601 durations. +See :ref:`here ` for more information. + +Inverse of :meth:`format_common_iso` + +Example +------- +>>> DateDelta.parse_common_iso(\"P1W11D\") +DateDelta(P1W11D) +>>> DateDelta.parse_common_iso(\"-P3M\") +DateDelta(-P3M) + +Note +---- +Only durations without time component are accepted. +``P0D`` is valid, but ``PT0S`` is not. + +Note +---- +The number of digits in each component is limited to 8. +"; +pub(crate) const DATETIMEDELTA_DATE_PART: &CStr = c"\ +date_part($self) +-- + +The date part of the delta"; +pub(crate) const DATETIMEDELTA_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Format as the *popular interpretation* of the ISO 8601 duration format. +May not strictly adhere to (all versions of) the standard. +See :ref:`here ` for more information. + +Inverse of :meth:`parse_common_iso`. + +The format is: + +.. code-block:: text + + P(nY)(nM)(nD)T(nH)(nM)(nS) + +Example +------- +>>> d = DateTimeDelta( +... weeks=1, +... days=11, +... hours=4, +... milliseconds=12, +... ) +>>> d.format_common_iso() +'P1W11DT4H0.012S' +"; +pub(crate) const DATETIMEDELTA_IN_MONTHS_DAYS_SECS_NANOS: &CStr = c"\ +in_months_days_secs_nanos($self) +-- + +Convert to a tuple of (months, days, seconds, nanoseconds) + +Example +------- +>>> d = DateTimeDelta(weeks=1, days=11, hours=4, microseconds=2) +>>> d.in_months_days_secs_nanos() +(0, 18, 14_400, 2000) +"; +pub(crate) const DATETIMEDELTA_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Parse the *popular interpretation* of the ISO 8601 duration format. +Does not parse all possible ISO 8601 durations. +See :ref:`here ` for more information. + +Examples: + +.. code-block:: text + + P4D # 4 days + PT4H # 4 hours + PT3M40.5S # 3 minutes and 40.5 seconds + P1W11DT4H # 1 week, 11 days, and 4 hours + -PT7H4M # -7 hours and -4 minutes (-7:04:00) + +PT7H4M # 7 hours and 4 minutes (7:04:00) + +Inverse of :meth:`format_common_iso` + +Example +------- +>>> DateTimeDelta.parse_common_iso(\"-P1W11DT4H\") +DateTimeDelta(-P1W11DT4H) +"; +pub(crate) const DATETIMEDELTA_TIME_PART: &CStr = c"\ +time_part($self) +-- + +The time part of the delta"; +pub(crate) const INSTANT_ADD: &CStr = c"\ +add($self, delta=None, /, *, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0) +-- + +Add a time amount to this instant. + +See the `docs on arithmetic `_ for more information. +"; +pub(crate) const INSTANT_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Convert to the popular ISO format ``YYYY-MM-DDTHH:MM:SSZ`` + +The inverse of the ``parse_common_iso()`` method. +"; +pub(crate) const INSTANT_FORMAT_RFC2822: &CStr = c"\ +format_rfc2822($self) +-- + +Format as an RFC 2822 string. + +The inverse of the ``parse_rfc2822()`` method. + +Example +------- +>>> Instant.from_utc(2020, 8, 15, hour=23, minute=12).format_rfc2822() +\"Sat, 15 Aug 2020 23:12:00 GMT\" +"; +pub(crate) const INSTANT_FORMAT_RFC3339: &CStr = c"\ +format_rfc3339($self) +-- + +Format as an RFC 3339 string ``YYYY-MM-DD HH:MM:SSZ`` + +If you prefer the ``T`` separator, use `format_common_iso()` instead. + +The inverse of the ``parse_rfc3339()`` method. + +Example +------- +>>> Instant.from_utc(2020, 8, 15, hour=23, minute=12).format_rfc3339() +\"2020-08-15 23:12:00Z\" +"; +pub(crate) const INSTANT_FROM_PY_DATETIME: &CStr = c"\ +from_py_datetime(d, /) +-- + +Create an Instant from a standard library ``datetime`` object. +The datetime must be aware. + +The inverse of the ``py_datetime()`` method. +"; +pub(crate) const INSTANT_FROM_TIMESTAMP: &CStr = c"\ +from_timestamp(i, /) +-- + +Create an Instant from a UNIX timestamp (in seconds). + +The inverse of the ``timestamp()`` method. +"; +pub(crate) const INSTANT_FROM_TIMESTAMP_MILLIS: &CStr = c"\ +from_timestamp_millis(i, /) +-- + +Create an Instant from a UNIX timestamp (in milliseconds). + +The inverse of the ``timestamp_millis()`` method. +"; +pub(crate) const INSTANT_FROM_TIMESTAMP_NANOS: &CStr = c"\ +from_timestamp_nanos(i, /) +-- + +Create an Instant from a UNIX timestamp (in nanoseconds). + +The inverse of the ``timestamp_nanos()`` method. +"; +pub(crate) const INSTANT_FROM_UTC: &CStr = c"\ +from_utc(year, month, day, hour=0, minute=0, second=0, *, nanosecond=0) +-- + +Create an Instant defined by a UTC date and time."; +pub(crate) const INSTANT_NOW: &CStr = c"\ +now() +-- + +Create an Instant from the current time."; +pub(crate) const INSTANT_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Parse the popular ISO format ``YYYY-MM-DDTHH:MM:SSZ`` + +The inverse of the ``format_common_iso()`` method. + +Important +--------- +Nonzero offsets will *not* be implicitly converted to UTC, +but will raise a ``ValueError``. +Use ``OffsetDateTime.parse_common_iso`` if you'd like to +parse an ISO 8601 string with a nonzero offset. +"; +pub(crate) const INSTANT_PARSE_RFC2822: &CStr = c"\ +parse_rfc2822(s, /) +-- + +Parse a UTC datetime in RFC 2822 format. + +The inverse of the ``format_rfc2822()`` method. + +Example +------- +>>> Instant.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 GMT\") +Instant(2020-08-15 23:12:00Z) + +>>> # also valid: +>>> Instant.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 +0000\") +>>> Instant.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 -0000\") +>>> Instant.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 UT\") +>>> Instant.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 UTC\") + +>>> # Error: includes offset. Use OffsetDateTime.parse_rfc2822() instead +>>> Instant.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 +0200\") + +Important +--------- +- This function parses, but **does not validate** the input (yet). + This is due to the limitations of the underlying + function ``email.utils.parsedate_to_datetime()``. +- Nonzero offsets will not be implicitly converted to UTC. + Use ``OffsetDateTime.parse_rfc2822()`` if you'd like to + parse an RFC 2822 string with a nonzero offset. +"; +pub(crate) const INSTANT_PARSE_RFC3339: &CStr = c"\ +parse_rfc3339(s, /) +-- + +Parse a UTC datetime in RFC 3339 format. + +The inverse of the ``format_rfc3339()`` method. + +Example +------- +>>> Instant.parse_rfc3339(\"2020-08-15 23:12:00Z\") +Instant(2020-08-15 23:12:00Z) +>>> +>>> # also valid: +>>> Instant.parse_rfc3339(\"2020-08-15T23:12:00+00:00\") +>>> Instant.parse_rfc3339(\"2020-08-15_23:12:00.34Z\") +>>> Instant.parse_rfc3339(\"2020-08-15t23:12:00z\") +>>> +>>> # not valid (nonzero offset): +>>> Instant.parse_rfc3339(\"2020-08-15T23:12:00+02:00\") + +Important +--------- +Nonzero offsets will not be implicitly converted to UTC, +but will raise a ValueError. +Use :meth:`OffsetDateTime.parse_rfc3339` if you'd like to +parse an RFC 3339 string with a nonzero offset. +"; +pub(crate) const INSTANT_SUBTRACT: &CStr = c"\ +subtract($self, delta=None, /, *, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0) +-- + +Subtract a time amount from this instant. + +See the `docs on arithmetic `_ for more information. +"; +pub(crate) const LOCALDATETIME_ADD: &CStr = c"\ +add($self, delta=None, /, *, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, ignore_dst=False) +-- + +Add a time amount to this datetime. + +Important +--------- +Shifting a ``LocalDateTime`` with **exact units** (e.g. hours, seconds) +implicitly ignores DST transitions and other timezone changes. +If you need to account for these, convert to a ``ZonedDateTime`` first. +Or, if you don't know the timezone and accept potentially incorrect results +during DST transitions, pass ``ignore_dst=True``. + +See `the documentation `_ +for more information. +"; +pub(crate) const LOCALDATETIME_ASSUME_FIXED_OFFSET: &CStr = c"\ +assume_fixed_offset($self, offset, /) +-- + +Assume the datetime has the given offset, creating an ``OffsetDateTime``. + +Example +------- +>>> LocalDateTime(2020, 8, 15, 23, 12).assume_fixed_offset(+2) +OffsetDateTime(2020-08-15 23:12:00+02:00) +"; +pub(crate) const LOCALDATETIME_ASSUME_SYSTEM_TZ: &CStr = c"\ +assume_system_tz($self, disambiguate) +-- + +Assume the datetime is in the system timezone, +creating a ``SystemDateTime``. + +Note +---- +The local datetime may be ambiguous in the system timezone +(e.g. during a DST transition). Therefore, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. +See `the documentation `_ +for more information. + +Example +------- +>>> d = LocalDateTime(2020, 8, 15, 23, 12) +>>> # assuming system timezone is America/New_York +>>> d.assume_system_tz(disambiguate=\"raise\") +SystemDateTime(2020-08-15 23:12:00-04:00) +"; +pub(crate) const LOCALDATETIME_ASSUME_TZ: &CStr = c"\ +assume_tz($self, tz, /, disambiguate) +-- + +Assume the datetime is in the given timezone, +creating a ``ZonedDateTime``. + +Note +---- +The local datetime may be ambiguous in the given timezone +(e.g. during a DST transition). Therefore, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. +See `the documentation `_ +for more information. + +Example +------- +>>> d = LocalDateTime(2020, 8, 15, 23, 12) +>>> d.assume_tz(\"Europe/Amsterdam\", disambiguate=\"raise\") +ZonedDateTime(2020-08-15 23:12:00+02:00[Europe/Amsterdam]) +"; +pub(crate) const LOCALDATETIME_ASSUME_UTC: &CStr = c"\ +assume_utc($self) +-- + +Assume the datetime is in UTC, creating an ``Instant``. + +Example +------- +>>> LocalDateTime(2020, 8, 15, 23, 12).assume_utc() +Instant(2020-08-15 23:12:00Z) +"; +pub(crate) const LOCALDATETIME_DIFFERENCE: &CStr = c"\ +difference($self, other, /, *, ignore_dst=False) +-- + +Calculate the difference between two local datetimes. + +Important +--------- +The difference between two local datetimes implicitly ignores +DST transitions and other timezone changes. +To perform DST-safe operations, convert to a ``ZonedDateTime`` first. +Or, if you don't know the timezone and accept potentially incorrect results +during DST transitions, pass ``ignore_dst=True``. +For more information, +see `the docs `_. +"; +pub(crate) const LOCALDATETIME_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Convert to the popular ISO format ``YYYY-MM-DDTHH:MM:SS`` + +The inverse of the ``parse_common_iso()`` method. +"; +pub(crate) const LOCALDATETIME_FROM_PY_DATETIME: &CStr = c"\ +from_py_datetime(d, /) +-- + +Create an instance from a \"naive\" standard library ``datetime`` object"; +pub(crate) const LOCALDATETIME_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Parse the popular ISO format ``YYYY-MM-DDTHH:MM:SS`` + +The inverse of the ``format_common_iso()`` method. + +Example +------- +>>> LocalDateTime.parse_common_iso(\"2020-08-15T23:12:00\") +LocalDateTime(2020-08-15 23:12:00) +"; +pub(crate) const LOCALDATETIME_REPLACE: &CStr = c"\ +replace($self, /, *, year=None, month=None, day=None, hour=None, minute=None, second=None, nanosecond=None) +-- + +Construct a new instance with the given fields replaced."; +pub(crate) const LOCALDATETIME_REPLACE_DATE: &CStr = c"\ +replace_date($self, d, /) +-- + +Construct a new instance with the date replaced."; +pub(crate) const LOCALDATETIME_REPLACE_TIME: &CStr = c"\ +replace_time($self, t, /) +-- + +Construct a new instance with the time replaced."; +pub(crate) const LOCALDATETIME_STRPTIME: &CStr = c"\ +strptime(s, /, fmt) +-- + +Simple alias for +``LocalDateTime.from_py_datetime(datetime.strptime(s, fmt))`` + +Example +------- +>>> LocalDateTime.strptime(\"2020-08-15\", \"%Y-%m-%d\") +LocalDateTime(2020-08-15 00:00:00) + +Note +---- +The parsed ``tzinfo`` must be be ``None``. +This means you CANNOT include the directives ``%z``, ``%Z``, or ``%:z`` +in the format string. +"; +pub(crate) const LOCALDATETIME_SUBTRACT: &CStr = c"\ +subtract($self, delta=None, /, *, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, ignore_dst=False) +-- + +Subtract a time amount from this datetime. + +Important +--------- +Shifting a ``LocalDateTime`` with **exact units** (e.g. hours, seconds) +implicitly ignores DST transitions and other timezone changes. +If you need to account for these, convert to a ``ZonedDateTime`` first. +Or, if you don't know the timezone and accept potentially incorrect results +during DST transitions, pass ``ignore_dst=True``. + +See `the documentation `_ +for more information. +"; +pub(crate) const MONTHDAY_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Format as the common ISO 8601 month-day format. + +Inverse of ``parse_common_iso``. + +Example +------- +>>> MonthDay(10, 8).format_common_iso() +'--10-08' + +Note +---- +This format is officially only part of the 2000 edition of the +ISO 8601 standard. There is no alternative for month-day +in the newer editions. However, it is still widely used in other libraries. +"; +pub(crate) const MONTHDAY_IN_YEAR: &CStr = c"\ +in_year($self, year, /) +-- + +Create a date from this month-day with a given day + +Example +------- +>>> MonthDay(8, 1).in_year(2025) +Date(2025-08-01) + +Note +---- +This method will raise a ``ValueError`` if the month-day is a leap day +and the year is not a leap year. +"; +pub(crate) const MONTHDAY_IS_LEAP: &CStr = c"\ +is_leap($self) +-- + +Check if the month-day is February 29th + +Example +------- +>>> MonthDay(2, 29).is_leap() +True +>>> MonthDay(3, 1).is_leap() +False +"; +pub(crate) const MONTHDAY_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Create from the common ISO 8601 format ``--MM-DD``. +Does not accept more \"exotic\" ISO 8601 formats. + +Inverse of :meth:`format_common_iso` + +Example +------- +>>> MonthDay.parse_common_iso(\"--11-23\") +MonthDay(--11-23) +"; +pub(crate) const MONTHDAY_REPLACE: &CStr = c"\ +replace($self, /, *, month=None, day=None) +-- + +Create a new instance with the given fields replaced + +Example +------- +>>> d = MonthDay(11, 23) +>>> d.replace(month=3) +MonthDay(--03-23) +"; +pub(crate) const OFFSETDATETIME_ADD: &CStr = c"\ +add($self, delta=None, /, *, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, ignore_dst=False) +-- + +Add a time amount to this datetime. + +Important +--------- +Shifting a fixed-offset datetime implicitly ignore DST +and other timezone changes. This because it isn't guaranteed that +the same offset will be valid at the resulting time. +If you want to account for DST, convert to a ``ZonedDateTime`` first. +Or, if you want to ignore DST and accept potentially incorrect offsets, +pass ``ignore_dst=True`` to this method. + +For more information, see +`the documentation `_. +"; +pub(crate) const OFFSETDATETIME_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Convert to the popular ISO format ``YYYY-MM-DDTHH:MM:SS±HH:MM`` + +The inverse of the ``parse_common_iso()`` method. +"; +pub(crate) const OFFSETDATETIME_FORMAT_RFC2822: &CStr = c"\ +format_rfc2822($self) +-- + +Format as an RFC 2822 string. + +The inverse of the ``parse_rfc2822()`` method. + +Example +------- +>>> OffsetDateTime(2020, 8, 15, 23, 12, offset=hours(2)).format_rfc2822() +\"Sat, 15 Aug 2020 23:12:00 +0200\" +"; +pub(crate) const OFFSETDATETIME_FORMAT_RFC3339: &CStr = c"\ +format_rfc3339($self) +-- + +Format as an RFC 3339 string ``YYYY-MM-DD HH:MM:SS±HH:MM`` + +If you prefer the ``T`` separator, use ``format_common_iso()`` instead. + +The inverse of the ``parse_rfc3339()`` method. + +Example +------- +>>> OffsetDateTime(2020, 8, 15, hour=23, minute=12, offset=hours(4)).format_rfc3339() +\"2020-08-15 23:12:00+04:00\" + +Note +---- +The RFC3339 format does not allow for second-level precision of the UTC offset. +This should not be a problem in practice, unless you're dealing with +pre-1950s timezones. +The ``format_common_iso()`` does support this precision. +"; +pub(crate) const OFFSETDATETIME_FROM_PY_DATETIME: &CStr = c"\ +from_py_datetime(d, /) +-- + +Create an instance from a standard library ``datetime`` object. +The datetime must be aware. + +The inverse of the ``py_datetime()`` method. + +"; +pub(crate) const OFFSETDATETIME_FROM_TIMESTAMP: &CStr = c"\ +from_timestamp(i, /, *, offset, ignore_dst=False) +-- + +Create an instance from a UNIX timestamp (in seconds). + +The inverse of the ``timestamp()`` method. + +Important +--------- +Creating an instance from a UNIX timestamp implicitly ignores DST +and other timezone changes. This because you don't strictly +know if the given offset is correct for an arbitrary timestamp. +Instead, use ``Instant.from_timestamp()`` +or ``ZonedDateTime.from_timestamp()`` if you know the timezone. +Or, if you want to ignore DST and accept potentially incorrect offsets, +pass ``ignore_dst=True`` to this method. For more information, see +`the documentation `_. +"; +pub(crate) const OFFSETDATETIME_FROM_TIMESTAMP_MILLIS: &CStr = c"\ +from_timestamp_millis(i, /, *, offset, ignore_dst=False) +-- + +Create an instance from a UNIX timestamp (in milliseconds). + +The inverse of the ``timestamp_millis()`` method. + +Important +--------- +Creating an instance from a UNIX timestamp implicitly ignores DST +and other timezone changes. This because you don't strictly +know if the given offset is correct for an arbitrary timestamp. +Instead, use ``Instant.from_timestamp_millis()`` +or ``ZonedDateTime.from_timestamp_millis()`` if you know the timezone. +Or, if you want to ignore DST and accept potentially incorrect offsets, +pass ``ignore_dst=True`` to this method. For more information, see +`the documentation `_. +"; +pub(crate) const OFFSETDATETIME_FROM_TIMESTAMP_NANOS: &CStr = c"\ +from_timestamp_nanos(i, /, *, offset, ignore_dst=False) +-- + +Create an instance from a UNIX timestamp (in nanoseconds). + +The inverse of the ``timestamp_nanos()`` method. + +Important +--------- +Creating an instance from a UNIX timestamp implicitly ignores DST +and other timezone changes. This because you don't strictly +know if the given offset is correct for an arbitrary timestamp. +Instead, use ``Instant.from_timestamp_nanos()`` +or ``ZonedDateTime.from_timestamp_nanos()`` if you know the timezone. +Or, if you want to ignore DST and accept potentially incorrect offsets, +pass ``ignore_dst=True`` to this method. For more information, see +`the documentation `_. +"; +pub(crate) const OFFSETDATETIME_NOW: &CStr = c"\ +now(offset, /, *, ignore_dst=False) +-- + +Create an instance from the current time. + +Important +--------- +Getting the current time with a fixed offset implicitly ignores DST +and other timezone changes. Instead, use ``Instant.now()`` or +``ZonedDateTime.now()`` if you know the timezone. +Or, if you want to ignore DST and accept potentially incorrect offsets, +pass ``ignore_dst=True`` to this method. For more information, see +`the documentation `_. +"; +pub(crate) const OFFSETDATETIME_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Parse the popular ISO format ``YYYY-MM-DDTHH:MM:SS±HH:MM`` + +The inverse of the ``format_common_iso()`` method. + +Example +------- +>>> OffsetDateTime.parse_common_iso(\"2020-08-15T23:12:00+02:00\") +OffsetDateTime(2020-08-15 23:12:00+02:00) +"; +pub(crate) const OFFSETDATETIME_PARSE_RFC2822: &CStr = c"\ +parse_rfc2822(s, /) +-- + +Parse an offset datetime in RFC 2822 format. + +The inverse of the ``format_rfc2822()`` method. + +Example +------- +>>> OffsetDateTime.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 +0200\") +OffsetDateTime(2020-08-15 23:12:00+02:00) +>>> # also valid: +>>> OffsetDateTime.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 UT\") +>>> OffsetDateTime.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 GMT\") +>>> OffsetDateTime.parse_rfc2822(\"Sat, 15 Aug 2020 23:12:00 MST\") + +Warning +------- +- This function parses, but **does not validate** the input (yet). + This is due to the limitations of the underlying + function ``email.utils.parsedate_to_datetime()``. +- The offset ``-0000`` has special meaning in RFC 2822, + indicating a UTC time with unknown local offset. + Thus, it cannot be parsed to an :class:`OffsetDateTime`. +"; +pub(crate) const OFFSETDATETIME_PARSE_RFC3339: &CStr = c"\ +parse_rfc3339(s, /) +-- + +Parse a fixed-offset datetime in RFC 3339 format. + +The inverse of the ``format_rfc3339()`` method. + +Example +------- +>>> OffsetDateTime.parse_rfc3339(\"2020-08-15 23:12:00+02:00\") +OffsetDateTime(2020-08-15 23:12:00+02:00) +>>> # also valid: +>>> OffsetDateTime.parse_rfc3339(\"2020-08-15T23:12:00Z\") +>>> OffsetDateTime.parse_rfc3339(\"2020-08-15_23:12:00.23-12:00\") +>>> OffsetDateTime.parse_rfc3339(\"2020-08-15t23:12:00z\") +"; +pub(crate) const OFFSETDATETIME_REPLACE: &CStr = c"\ +replace($self, /, *, year=None, month=None, day=None, hour=None, minute=None, second=None, nanosecond=None, offset=None, ignore_dst=False) +-- + +Construct a new instance with the given fields replaced. + +Important +--------- +Replacing fields of an offset datetime implicitly ignores DST +and other timezone changes. This because it isn't guaranteed that +the same offset will be valid at the new time. +If you want to account for DST, convert to a ``ZonedDateTime`` first. +Or, if you want to ignore DST and accept potentially incorrect offsets, +pass ``ignore_dst=True`` to this method. +"; +pub(crate) const OFFSETDATETIME_REPLACE_DATE: &CStr = c"\ +replace_date($self, date, /, *, ignore_dst=False) +-- + +Construct a new instance with the date replaced. + +Important +--------- +Replacing the date of an offset datetime implicitly ignores DST +and other timezone changes. This because it isn't guaranteed that +the same offset will be valid at the new date. +If you want to account for DST, convert to a ``ZonedDateTime`` first. +Or, if you want to ignore DST and accept potentially incorrect offsets, +pass ``ignore_dst=True`` to this method. +"; +pub(crate) const OFFSETDATETIME_REPLACE_TIME: &CStr = c"\ +replace_time($self, time, /, *, ignore_dst=False) +-- + +Construct a new instance with the time replaced. + +Important +--------- +Replacing the time of an offset datetime implicitly ignores DST +and other timezone changes. This because it isn't guaranteed that +the same offset will be valid at the new time. +If you want to account for DST, convert to a ``ZonedDateTime`` first. +Or, if you want to ignore DST and accept potentially incorrect offsets, +pass ``ignore_dst=True`` to this method. +"; +pub(crate) const OFFSETDATETIME_STRPTIME: &CStr = c"\ +strptime(s, /, fmt) +-- + +Simple alias for +``OffsetDateTime.from_py_datetime(datetime.strptime(s, fmt))`` + +Example +------- +>>> OffsetDateTime.strptime(\"2020-08-15+0200\", \"%Y-%m-%d%z\") +OffsetDateTime(2020-08-15 00:00:00+02:00) + +Important +--------- +The parsed ``tzinfo`` must be a fixed offset +(``datetime.timezone`` instance). +This means you MUST include the directive ``%z``, ``%Z``, or ``%:z`` +in the format string. +"; +pub(crate) const OFFSETDATETIME_SUBTRACT: &CStr = c"\ +subtract($self, delta=None, /, *, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, ignore_dst=False) +-- + +Subtract a time amount from this datetime. + +Important +--------- +Shifting a fixed-offset datetime implicitly ignore DST +and other timezone changes. This because it isn't guaranteed that +the same offset will be valid at the resulting time. +If you want to account for DST, convert to a ``ZonedDateTime`` first. +Or, if you want to ignore DST and accept potentially incorrect offsets, +pass ``ignore_dst=True`` to this method. + +For more information, see +`the documentation `_. +"; +pub(crate) const SYSTEMDATETIME_ADD: &CStr = c"\ +add($self, delta=None, /, *, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, disambiguate=None) +-- + +Add a time amount to this datetime. + +Important +--------- +Shifting a ``SystemDateTime`` with **calendar units** (e.g. months, weeks) +may result in an ambiguous time (e.g. during a DST transition). +Therefore, when adding calendar units, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const SYSTEMDATETIME_FROM_PY_DATETIME: &CStr = c"\ +from_py_datetime(d, /) +-- + +Create an instance from a standard library ``datetime`` object. +The datetime must be aware. + +The inverse of the ``py_datetime()`` method. +"; +pub(crate) const SYSTEMDATETIME_FROM_TIMESTAMP: &CStr = c"\ +from_timestamp(i, /) +-- + +Create an instance from a UNIX timestamp (in seconds). + +The inverse of the ``timestamp()`` method. +"; +pub(crate) const SYSTEMDATETIME_FROM_TIMESTAMP_MILLIS: &CStr = c"\ +from_timestamp_millis(i, /) +-- + +Create an instance from a UNIX timestamp (in milliseconds). + +The inverse of the ``timestamp_millis()`` method. +"; +pub(crate) const SYSTEMDATETIME_FROM_TIMESTAMP_NANOS: &CStr = c"\ +from_timestamp_nanos(i, /) +-- + +Create an instance from a UNIX timestamp (in nanoseconds). + +The inverse of the ``timestamp_nanos()`` method. +"; +pub(crate) const SYSTEMDATETIME_NOW: &CStr = c"\ +now() +-- + +Create an instance from the current time in the system timezone."; +pub(crate) const SYSTEMDATETIME_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Parse from the popular ISO format ``YYYY-MM-DDTHH:MM:SS±HH:MM`` + +Important +--------- +The offset isn't adjusted to the current system timezone. +See `the docs `_ +for more information. +"; +pub(crate) const SYSTEMDATETIME_REPLACE: &CStr = c"\ +replace($self, /, *, year=None, month=None, day=None, hour=None, minute=None, second=None, nanosecond=None, tz=None, disambiguate) +-- + +Construct a new instance with the given fields replaced. + +Important +--------- +Replacing fields of a SystemDateTime may result in an ambiguous time +(e.g. during a DST transition). Therefore, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const SYSTEMDATETIME_REPLACE_DATE: &CStr = c"\ +replace_date($self, date, /, disambiguate) +-- + +Construct a new instance with the date replaced. + +Important +--------- +Replacing the date of a SystemDateTime may result in an ambiguous time +(e.g. during a DST transition). Therefore, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const SYSTEMDATETIME_REPLACE_TIME: &CStr = c"\ +replace_time($self, time, /, disambiguate) +-- + +Construct a new instance with the time replaced. + +Important +--------- +Replacing the time of a SystemDateTime may result in an ambiguous time +(e.g. during a DST transition). Therefore, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const SYSTEMDATETIME_SUBTRACT: &CStr = c"\ +subtract($self, delta=None, /, *, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, disambiguate=None) +-- + +Subtract a time amount from this datetime. + +Important +--------- +Shifting a ``SystemDateTime`` with **calendar units** (e.g. months, weeks) +may result in an ambiguous time (e.g. during a DST transition). +Therefore, when adding calendar units, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const TIME_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Format as the common ISO 8601 time format. + +Inverse of :meth:`parse_common_iso`. + +Example +------- +>>> Time(12, 30, 0).format_common_iso() +'12:30:00' +"; +pub(crate) const TIME_FROM_PY_TIME: &CStr = c"\ +from_py_time(t, /) +-- + +Create from a :class:`~datetime.time` + +Example +------- +>>> Time.from_py_time(time(12, 30, 0)) +Time(12:30:00) + +`fold` value is ignored. +"; +pub(crate) const TIME_ON: &CStr = c"\ +on($self, d, /) +-- + +Combine a time with a date to create a datetime + +Example +------- +>>> t = Time(12, 30) +>>> t.on(Date(2021, 1, 2)) +LocalDateTime(2021-01-02 12:30:00) + +Then, use methods like :meth:`~LocalDateTime.assume_utc` +or :meth:`~LocalDateTime.assume_tz` +to make the result aware. +"; +pub(crate) const TIME_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Create from the common ISO 8601 time format ``HH:MM:SS``. +Does not accept more \"exotic\" ISO 8601 formats. + +Inverse of :meth:`format_common_iso` + +Example +------- +>>> Time.parse_common_iso(\"12:30:00\") +Time(12:30:00) +"; +pub(crate) const TIME_PY_TIME: &CStr = c"\ +py_time($self) +-- + +Convert to a standard library :class:`~datetime.time`"; +pub(crate) const TIME_REPLACE: &CStr = c"\ +replace($self, /, *, hour=None, minute=None, second=None, nanosecond=None) +-- + +Create a new instance with the given fields replaced + +Example +------- +>>> t = Time(12, 30, 0) +>>> d.replace(minute=3, nanosecond=4_000) +Time(12:03:00.000004) + +"; +pub(crate) const TIMEDELTA_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Format as the *popular interpretation* of the ISO 8601 duration format. +May not strictly adhere to (all versions of) the standard. +See :ref:`here ` for more information. + +Inverse of :meth:`parse_common_iso`. + +Example +------- +>>> TimeDelta(hours=1, minutes=30).format_common_iso() +'PT1H30M' +"; +pub(crate) const TIMEDELTA_FROM_PY_TIMEDELTA: &CStr = c"\ +from_py_timedelta(td, /) +-- + +Create from a :class:`~datetime.timedelta` + +Inverse of :meth:`py_timedelta` + +Example +------- +>>> TimeDelta.from_py_timedelta(timedelta(seconds=5400)) +TimeDelta(01:30:00) +"; +pub(crate) const TIMEDELTA_IN_DAYS_OF_24H: &CStr = c"\ +in_days_of_24h($self) +-- + +The total size in days (of exactly 24 hours each) + +Note +---- +Note that this may not be the same as days on the calendar, +since some days have 23 or 25 hours due to daylight saving time. +"; +pub(crate) const TIMEDELTA_IN_HOURS: &CStr = c"\ +in_hours($self) +-- + +The total size in hours + +Example +------- +>>> d = TimeDelta(hours=1, minutes=30) +>>> d.in_hours() +1.5 +"; +pub(crate) const TIMEDELTA_IN_HRS_MINS_SECS_NANOS: &CStr = c"\ +in_hrs_mins_secs_nanos($self) +-- + +Convert to a tuple of (hours, minutes, seconds, nanoseconds) + +Example +------- +>>> d = TimeDelta(hours=1, minutes=30, microseconds=5_000_090) +>>> d.in_hrs_mins_secs_nanos() +(1, 30, 5, 90_000) +"; +pub(crate) const TIMEDELTA_IN_MICROSECONDS: &CStr = c"\ +in_microseconds($self) +-- + +The total size in microseconds + +>>> d = TimeDelta(seconds=2, nanoseconds=50) +>>> d.in_microseconds() +2_000_000.05 +"; +pub(crate) const TIMEDELTA_IN_MILLISECONDS: &CStr = c"\ +in_milliseconds($self) +-- + +The total size in milliseconds + +>>> d = TimeDelta(seconds=2, microseconds=50) +>>> d.in_milliseconds() +2_000.05 +"; +pub(crate) const TIMEDELTA_IN_MINUTES: &CStr = c"\ +in_minutes($self) +-- + +The total size in minutes + +Example +------- +>>> d = TimeDelta(hours=1, minutes=30, seconds=30) +>>> d.in_minutes() +90.5 +"; +pub(crate) const TIMEDELTA_IN_NANOSECONDS: &CStr = c"\ +in_nanoseconds($self) +-- + +The total size in nanoseconds + +>>> d = TimeDelta(seconds=2, nanoseconds=50) +>>> d.in_nanoseconds() +2_000_000_050 +"; +pub(crate) const TIMEDELTA_IN_SECONDS: &CStr = c"\ +in_seconds($self) +-- + +The total size in seconds + +Example +------- +>>> d = TimeDelta(minutes=2, seconds=1, microseconds=500_000) +>>> d.in_seconds() +121.5 +"; +pub(crate) const TIMEDELTA_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Parse the *popular interpretation* of the ISO 8601 duration format. +Does not parse all possible ISO 8601 durations. +See :ref:`here ` for more information. + +Inverse of :meth:`format_common_iso` + +Example +------- +>>> TimeDelta.parse_common_iso(\"PT1H30M\") +TimeDelta(01:30:00) + +Note +---- +Any duration with a date part is considered invalid. +``PT0S`` is valid, but ``P0D`` is not. +"; +pub(crate) const TIMEDELTA_PY_TIMEDELTA: &CStr = c"\ +py_timedelta($self) +-- + +Convert to a :class:`~datetime.timedelta` + +Inverse of :meth:`from_py_timedelta` + +Note +---- +Nanoseconds are rounded to the nearest even microsecond. + +Example +------- +>>> d = TimeDelta(hours=1, minutes=30) +>>> d.py_timedelta() +timedelta(seconds=5400) +"; +pub(crate) const YEARMONTH_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Format as the common ISO 8601 year-month format. + +Inverse of :meth:`parse_common_iso`. + +Example +------- +>>> YearMonth(2021, 1).format_common_iso() +'2021-01' +"; +pub(crate) const YEARMONTH_ON_DAY: &CStr = c"\ +on_day($self, day, /) +-- + +Create a date from this year-month with a given day + +Example +------- +>>> YearMonth(2021, 1).on_day(2) +Date(2021-01-02) +"; +pub(crate) const YEARMONTH_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Create from the common ISO 8601 format ``YYYY-MM``. +Does not accept more \"exotic\" ISO 8601 formats. + +Inverse of :meth:`format_common_iso` + +Example +------- +>>> YearMonth.parse_common_iso(\"2021-01-02\") +YearMonth(2021-01-02) +"; +pub(crate) const YEARMONTH_REPLACE: &CStr = c"\ +replace($self, /, *, year=None, month=None) +-- + +Create a new instance with the given fields replaced + +Example +------- +>>> d = YearMonth(2021, 12) +>>> d.replace(month=3) +YearMonth(2021-03) +"; +pub(crate) const ZONEDDATETIME_ADD: &CStr = c"\ +add($self, delta=None, /, *, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, disambiguate=None) +-- + +Add a time amount to this datetime. + +Important +--------- +Shifting a ``ZonedDateTime`` with **calendar units** (e.g. months, weeks) +may result in an ambiguous time (e.g. during a DST transition). +Therefore, when adding calendar units, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const ZONEDDATETIME_FORMAT_COMMON_ISO: &CStr = c"\ +format_common_iso($self) +-- + +Convert to the popular ISO format ``YYYY-MM-DDTHH:MM:SS±HH:MM[TZ_ID]`` + +The inverse of the ``parse_common_iso()`` method. + +Example +------- +>>> ZonedDateTime(2020, 8, 15, hour=23, minute=12, tz=\"Europe/London\") +ZonedDateTime(2020-08-15 23:12:00+01:00[Europe/London]) + +Important +--------- +The timezone ID is a recent extension to the ISO 8601 format (RFC 9557). +Althought it is gaining popularity, it is not yet widely supported +by ISO 8601 parsers. +"; +pub(crate) const ZONEDDATETIME_FROM_PY_DATETIME: &CStr = c"\ +from_py_datetime(d, /) +-- + +Create an instance from a standard library ``datetime`` object +with a ``ZoneInfo`` tzinfo. + +The inverse of the ``py_datetime()`` method. + +Attention +--------- +If the datetime is ambiguous (e.g. during a DST transition), +the ``fold`` attribute is used to disambiguate the time. +"; +pub(crate) const ZONEDDATETIME_FROM_TIMESTAMP: &CStr = c"\ +from_timestamp(i, /, *, tz) +-- + +Create an instance from a UNIX timestamp (in seconds). + +The inverse of the ``timestamp()`` method. +"; +pub(crate) const ZONEDDATETIME_FROM_TIMESTAMP_MILLIS: &CStr = c"\ +from_timestamp_millis(i, /, *, tz) +-- + +Create an instance from a UNIX timestamp (in milliseconds). + +The inverse of the ``timestamp_millis()`` method. +"; +pub(crate) const ZONEDDATETIME_FROM_TIMESTAMP_NANOS: &CStr = c"\ +from_timestamp_nanos(i, /, *, tz) +-- + +Create an instance from a UNIX timestamp (in nanoseconds). + +The inverse of the ``timestamp_nanos()`` method. +"; +pub(crate) const ZONEDDATETIME_IS_AMBIGUOUS: &CStr = c"\ +is_ambiguous($self) +-- + +Whether the local time is ambiguous, e.g. due to a DST transition. + +Example +------- +>>> ZonedDateTime(2020, 8, 15, 23, tz=\"Europe/London\", disambiguate=\"later\").ambiguous() +False +>>> ZonedDateTime(2023, 10, 29, 2, 15, tz=\"Europe/Amsterdam\", disambiguate=\"later\").ambiguous() +True +"; +pub(crate) const ZONEDDATETIME_NOW: &CStr = c"\ +now(tz, /) +-- + +Create an instance from the current time in the given timezone."; +pub(crate) const ZONEDDATETIME_PARSE_COMMON_ISO: &CStr = c"\ +parse_common_iso(s, /) +-- + +Parse from the popular ISO format ``YYYY-MM-DDTHH:MM:SS±HH:MM[TZ_ID]`` + +The inverse of the ``format_common_iso()`` method. + +Example +------- +>>> ZonedDateTime.parse_common_iso(\"2020-08-15T23:12:00+01:00[Europe/London]\") +ZonedDateTime(2020-08-15 23:12:00+01:00[Europe/London]) + +Important +--------- +The timezone ID is a recent extension to the ISO 8601 format (RFC 9557). +Althought it is gaining popularity, it is not yet widely supported. +"; +pub(crate) const ZONEDDATETIME_REPLACE: &CStr = c"\ +replace($self, /, *, year=None, month=None, day=None, hour=None, minute=None, second=None, nanosecond=None, tz=None, disambiguate) +-- + +Construct a new instance with the given fields replaced. + +Important +--------- +Replacing fields of a ZonedDateTime may result in an ambiguous time +(e.g. during a DST transition). Therefore, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const ZONEDDATETIME_REPLACE_DATE: &CStr = c"\ +replace_date($self, date, /, disambiguate) +-- + +Construct a new instance with the date replaced. + +Important +--------- +Replacing the date of a ZonedDateTime may result in an ambiguous time +(e.g. during a DST transition). Therefore, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const ZONEDDATETIME_REPLACE_TIME: &CStr = c"\ +replace_time($self, time, /, disambiguate) +-- + +Construct a new instance with the time replaced. + +Important +--------- +Replacing the time of a ZonedDateTime may result in an ambiguous time +(e.g. during a DST transition). Therefore, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const ZONEDDATETIME_SUBTRACT: &CStr = c"\ +subtract($self, delta=None, /, *, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, disambiguate=None) +-- + +Subtract a time amount from this datetime. + +Important +--------- +Shifting a ``ZonedDateTime`` with **calendar units** (e.g. months, weeks) +may result in an ambiguous time (e.g. during a DST transition). +Therefore, when adding calendar units, you must explicitly +specify how to handle such a situation using the ``disambiguate`` argument. + +See `the documentation `_ +for more information. +"; +pub(crate) const BASICCONVERSIONS_PY_DATETIME: &CStr = c"\ +py_datetime($self) +-- + +Convert to a standard library :class:`~datetime.datetime` + +Note +---- +Nanoseconds are truncated to microseconds. +"; +pub(crate) const KNOWSINSTANT_DIFFERENCE: &CStr = c"\ +difference($self, other, /) +-- + +Calculate the difference between two instants in time. + +Equivalent to :meth:`__sub__`. + +See :ref:`the docs on arithmetic ` for more information. +"; +pub(crate) const KNOWSINSTANT_EXACT_EQ: &CStr = c"\ +exact_eq($self, other, /) +-- + +Compare objects by their values +(instead of whether they represent the same instant). +Different types are never equal. + +Note +---- +If ``a.exact_eq(b)`` is true, then +``a == b`` is also true, but the converse is not necessarily true. + +Examples +-------- + +>>> a = OffsetDateTime(2020, 8, 15, hour=12, offset=1) +>>> b = OffsetDateTime(2020, 8, 15, hour=13, offset=2) +>>> a == b +True # equivalent instants +>>> a.exact_eq(b) +False # different values (hour and offset) +>>> a.exact_eq(Instant.now()) +TypeError # different types +"; +pub(crate) const KNOWSINSTANT_TIMESTAMP: &CStr = c"\ +timestamp($self) +-- + +The UNIX timestamp for this datetime. Inverse of :meth:`from_timestamp`. + +Note +---- +In contrast to the standard library, this method always returns an integer, +not a float. This is because floating point timestamps are not precise +enough to represent all instants to nanosecond precision. +This decision is consistent with other modern date-time libraries. + +Example +------- +>>> Instant.from_utc(1970, 1, 1).timestamp() +0 +>>> ts = 1_123_000_000 +>>> Instant.from_timestamp(ts).timestamp() == ts +True +"; +pub(crate) const KNOWSINSTANT_TIMESTAMP_MILLIS: &CStr = c"\ +timestamp_millis($self) +-- + +Like :meth:`timestamp`, but with millisecond precision."; +pub(crate) const KNOWSINSTANT_TIMESTAMP_NANOS: &CStr = c"\ +timestamp_nanos($self) +-- + +Like :meth:`timestamp`, but with nanosecond precision."; +pub(crate) const KNOWSINSTANT_TO_FIXED_OFFSET: &CStr = c"\ +to_fixed_offset($self, offset=None, /) +-- + +Convert to an OffsetDateTime that represents the same moment in time. + +If not offset is given, the offset is taken from the original datetime. +"; +pub(crate) const KNOWSINSTANT_TO_SYSTEM_TZ: &CStr = c"\ +to_system_tz($self) +-- + +Convert to a SystemDateTime that represents the same moment in time."; +pub(crate) const KNOWSINSTANT_TO_TZ: &CStr = c"\ +to_tz($self, tz, /) +-- + +Convert to a ZonedDateTime that represents the same moment in time. + +Raises +------ +~zoneinfo.ZoneInfoNotFoundError + If the timezone ID is not found in the IANA database. +"; +pub(crate) const KNOWSINSTANTANDLOCAL_INSTANT: &CStr = c"\ +instant($self) +-- + +Get the underlying instant in time + +Example +------- + +>>> d = ZonedDateTime(2020, 8, 15, hour=23, tz=\"Europe/Amsterdam\") +>>> d.instant() +Instant(2020-08-15 21:00:00Z) +"; +pub(crate) const KNOWSINSTANTANDLOCAL_LOCAL: &CStr = c"\ +local($self) +-- + +Get the underlying local date and time + +As an inverse, :class:`LocalDateTime` has methods +:meth:`~LocalDateTime.assume_utc`, :meth:`~LocalDateTime.assume_fixed_offset` +, :meth:`~LocalDateTime.assume_tz`, and :meth:`~LocalDateTime.assume_system_tz` +which may require additional arguments. +"; +pub(crate) const KNOWSLOCAL_DATE: &CStr = c"\ +date($self) +-- + +The date part of the datetime + +Example +------- +>>> d = Instant.from_utc(2021, 1, 2, 3, 4, 5) +>>> d.date() +Date(2021-01-02) + +To perform the inverse, use :meth:`Date.at` and a method +like :meth:`~LocalDateTime.assume_utc` or +:meth:`~LocalDateTime.assume_tz`: + +>>> date.at(time).assume_tz(\"Europe/London\", disambiguate=\"compatible\") +"; +pub(crate) const KNOWSLOCAL_TIME: &CStr = c"\ +time($self) +-- + +The time-of-day part of the datetime + +Example +------- +>>> d = ZonedDateTime(2021, 1, 2, 3, 4, 5, tz=\"Europe/Paris\") +ZonedDateTime(2021-01-02T03:04:05+01:00[Europe/Paris]) +>>> d.time() +Time(03:04:05) + +To perform the inverse, use :meth:`Time.on` and a method +like :meth:`~LocalDateTime.assume_utc` or +:meth:`~LocalDateTime.assume_tz`: + +>>> time.on(date).assume_tz(\"Europe/Paris\", disambiguate=\"compatible\") +"; +pub(crate) const ADJUST_LOCAL_DATETIME_MSG: &str = "Adjusting a local datetime by time units (e.g. hours and minutess) ignores DST and other timezone changes. To perform DST-safe operations, convert to a ZonedDateTime first. Or, if you don't know the timezone and accept potentially incorrect results during DST transitions, pass `ignore_dst=True`. For more information, see whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic"; +pub(crate) const ADJUST_OFFSET_DATETIME_MSG: &str = "Adjusting a fixed offset datetime implicitly ignores DST and other timezone changes. To perform DST-safe operations, convert to a ZonedDateTime first. Or, if you don't know the timezone and accept potentially incorrect results during DST transitions, pass `ignore_dst=True`. For more information, see whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic"; +pub(crate) const DIFF_LOCAL_MSG: &str = "The difference between two local datetimes implicitly ignores DST transitions and other timezone changes. To perform DST-safe operations, convert to a ZonedDateTime first. Or, if you don't know the timezone and accept potentially incorrect results during DST transitions, pass `ignore_dst=True`. For more information, see whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic"; +pub(crate) const DIFF_OPERATOR_LOCAL_MSG: &str = "The difference between two local datetimes implicitly ignores DST transitions and other timezone changes. Use the `difference` method instead."; +pub(crate) const OFFSET_NOW_DST_MSG: &str = "Getting the current time with a fixed offset implicitly ignores DST and other timezone changes. Instead, use `Instant.now()` or `ZonedDateTime.now()` if you know the timezone. Or, if you want to ignore DST and accept potentially incorrect offsets, pass `ignore_dst=True` to this method. For more information, see whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic"; +pub(crate) const SHIFT_LOCAL_MSG: &str = "Adding or subtracting a (date)time delta to a local datetime implicitly ignores DST transitions and other timezone changes. Instead, use the `add` or `subtract` method."; +pub(crate) const SHIFT_OPERATOR_CALENDAR_ZONED_MSG: &str = "Addition/subtraction of calendar units on a Zoned/System-DateTime requires explicit disambiguation. Use the `add`/`subtract` methods instead. For example, instead of `dt + delta` use `dt.add(delta, disambiguate=...)`."; +pub(crate) const TIMESTAMP_DST_MSG: &str = "Converting from a timestamp with a fixed offset implicitly ignores DST and other timezone changes. To perform a DST-safe conversion, use ZonedDateTime.from_timestamp() instead. Or, if you don't know the timezone and accept potentially incorrect results during DST transitions, pass `ignore_dst=True`. For more information, see whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic"; diff --git a/src/instant.rs b/src/instant.rs index 2ec85602..c815a9fa 100644 --- a/src/instant.rs +++ b/src/instant.rs @@ -4,6 +4,7 @@ use pyo3_ffi::*; use crate::common::*; use crate::datetime_delta::handle_exact_unit; +use crate::docstrings as doc; use crate::time_delta::{MAX_HOURS, MAX_MICROSECONDS, MAX_MILLISECONDS, MAX_MINUTES, MAX_SECS}; use crate::{ date::Date, @@ -446,7 +447,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_nb_add, __add__, 2), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A UTC datetime type".as_ptr() as *mut c_void, + pfunc: doc::INSTANT.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_hash, @@ -456,10 +457,6 @@ static mut SLOTS: &[PyType_Slot] = &[ slot: Py_tp_methods, pfunc: unsafe { METHODS.as_ptr() as *mut c_void }, }, - PyType_Slot { - slot: Py_tp_getset, - pfunc: unsafe { GETSETTERS.as_ptr() as *mut c_void }, - }, PyType_Slot { slot: Py_tp_dealloc, pfunc: generic_dealloc as *mut c_void, @@ -818,16 +815,16 @@ unsafe fn parse_rfc2822(cls: *mut PyObject, s_obj: *mut PyObject) -> PyReturn { } static mut METHODS: &[PyMethodDef] = &[ - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!(exact_eq, "Equality check limited to the same type", METH_O), - method!(__reduce__, ""), - method!(timestamp, "Get the UNIX timestamp in seconds"), - method!(timestamp_millis, "Get the UNIX timestamp in milliseconds"), - method!(timestamp_nanos, "Get the UNIX timestamp in nanoseconds"), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(__reduce__, c""), + method!(exact_eq, doc::KNOWSINSTANT_EXACT_EQ, METH_O), + method!(timestamp, doc::KNOWSINSTANT_TIMESTAMP), + method!(timestamp_millis, doc::KNOWSINSTANT_TIMESTAMP_MILLIS), + method!(timestamp_nanos, doc::KNOWSINSTANT_TIMESTAMP_NANOS), method!( from_timestamp, - "Create an instance from a UNIX timestamp in seconds", + doc::INSTANT_FROM_TIMESTAMP, METH_O | METH_CLASS ), PyMethodDef { @@ -848,108 +845,50 @@ static mut METHODS: &[PyMethodDef] = &[ }, }, ml_flags: METH_CLASS | METH_VARARGS | METH_KEYWORDS, - ml_doc: c"from_utc()\n--\n\nCreate an instance from a UTC date and time".as_ptr(), + ml_doc: doc::INSTANT_FROM_UTC.as_ptr(), }, method!( from_timestamp_millis, - "Create an instance from a UNIX timestamp in milliseconds", + doc::INSTANT_FROM_TIMESTAMP_MILLIS, METH_O | METH_CLASS ), method!( from_timestamp_nanos, - "Create an instance from a UNIX timestamp in nanoseconds", + doc::INSTANT_FROM_TIMESTAMP_NANOS, METH_O | METH_CLASS ), - method!(py_datetime, "Get the equivalent datetime.datetime object"), + method!(py_datetime, doc::BASICCONVERSIONS_PY_DATETIME), method!( from_py_datetime, - "Create an instance from a datetime.datetime", + doc::INSTANT_FROM_PY_DATETIME, METH_O | METH_CLASS ), - method!( - now, - "Create an instance from the current time", - METH_CLASS | METH_NOARGS - ), - method!(format_rfc3339, "Format in the RFC3339 format"), + method!(now, doc::INSTANT_NOW, METH_CLASS | METH_NOARGS), + method!(format_rfc3339, doc::INSTANT_FORMAT_RFC3339), method!( parse_rfc3339, - "Create an instance from an RFC3339 string", + doc::INSTANT_PARSE_RFC3339, METH_CLASS | METH_O ), - method!(format_rfc2822, "Format in the RFC2822 format"), + method!(format_rfc2822, doc::INSTANT_FORMAT_RFC2822), method!( parse_rfc2822, - "Create an instance from an RFC2822 string", + doc::INSTANT_PARSE_RFC2822, METH_O | METH_CLASS ), - method!( - format_common_iso, - "Format in the common ISO8601 representation" - ), + method!(format_common_iso, doc::INSTANT_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Create an instance from the common ISO8601 format", + doc::INSTANT_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method_kwargs!( - add, - "add($self, *, hours=0, minutes=0, seconds=0, milliseconds=0, \ - microseconds=0, nanoseconds=0)\n--\n\n\ - Add various time units to the instance" - ), - method_kwargs!( - subtract, - "subtract($self, *, hours=0, minutes=0, seconds=0, milliseconds=0, \ - microseconds=0, nanoseconds=0)\n--\n\n\ - Subtract various time units from the instance" - ), - method!(to_tz, "Convert to an equivalent ZonedDateTime", METH_O), - method!( - to_system_tz, - "Convert to an equivalent datetime in the system timezone" - ), - method_vararg!( - to_fixed_offset, - "to_fixed_offset($self, offset=0, /)\n--\n\n\ - Convert to an equivalent OffsetDateTime" - ), - method!( - difference, - "Calculate the difference between two instances", - METH_O - ), + method_kwargs!(add, doc::INSTANT_ADD), + method_kwargs!(subtract, doc::INSTANT_SUBTRACT), + method!(to_tz, doc::KNOWSINSTANT_TO_TZ, METH_O), + method!(to_system_tz, doc::KNOWSINSTANT_TO_SYSTEM_TZ), + method_vararg!(to_fixed_offset, doc::KNOWSINSTANT_TO_FIXED_OFFSET), + method!(difference, doc::KNOWSINSTANT_DIFFERENCE, METH_O), PyMethodDef::zeroed(), ]; -unsafe fn get_hour(slf: *mut PyObject) -> PyReturn { - (Instant::extract(slf).secs % 86400 / 3600).to_py() -} - -unsafe fn get_minute(slf: *mut PyObject) -> PyReturn { - (Instant::extract(slf).secs % 3600 / 60).to_py() -} - -unsafe fn get_secs(slf: *mut PyObject) -> PyReturn { - (Instant::extract(slf).secs % 60).to_py() -} - -unsafe fn get_nanos(slf: *mut PyObject) -> PyReturn { - Instant::extract(slf).nanos.to_py() -} - -static mut GETSETTERS: &[PyGetSetDef] = &[ - getter!(get_hour named "hour", "The hour component"), - getter!(get_minute named "minute", "The minute component"), - getter!(get_secs named "second", "The second component"), - getter!(get_nanos named "nanosecond", "The nanosecond component"), - PyGetSetDef { - name: NULL(), - get: None, - set: None, - doc: NULL(), - closure: NULL(), - }, -]; - type_spec!(Instant, SLOTS); diff --git a/src/lib.rs b/src/lib.rs index b99d1eb8..1f403ab0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,8 @@ mod common; pub mod date; mod date_delta; mod datetime_delta; +#[rustfmt::skip] // this module is autogenerated. No need to format it. +mod docstrings; mod instant; pub mod local_datetime; mod monthday; @@ -24,6 +26,7 @@ use date::unpickle as _unpkl_date; use date_delta::unpickle as _unpkl_ddelta; use date_delta::{days, months, weeks, years}; use datetime_delta::unpickle as _unpkl_dtdelta; +use docstrings as doc; use instant::{unpickle as _unpkl_utc, UNIX_EPOCH_INSTANT}; use local_datetime::unpickle as _unpkl_local; use monthday::unpickle as _unpkl_md; @@ -51,72 +54,32 @@ static mut MODULE_DEF: PyModuleDef = PyModuleDef { }; static mut METHODS: &[PyMethodDef] = &[ - method!(_unpkl_date, "", METH_O), - method!(_unpkl_ym, "", METH_O), - method!(_unpkl_md, "", METH_O), - method!(_unpkl_time, "", METH_O), - method_vararg!(_unpkl_ddelta, ""), - method!(_unpkl_tdelta, "", METH_O), - method_vararg!(_unpkl_dtdelta, ""), - method!(_unpkl_local, "", METH_O), - method!(_unpkl_utc, "", METH_O), - method!(_unpkl_offset, "", METH_O), - method_vararg!(_unpkl_zoned, ""), - method!(_unpkl_system, "", METH_O), + method!(_unpkl_date, c"", METH_O), + method!(_unpkl_ym, c"", METH_O), + method!(_unpkl_md, c"", METH_O), + method!(_unpkl_time, c"", METH_O), + method_vararg!(_unpkl_ddelta, c""), + method!(_unpkl_tdelta, c"", METH_O), + method_vararg!(_unpkl_dtdelta, c""), + method!(_unpkl_local, c"", METH_O), + method!(_unpkl_utc, c"", METH_O), + method!(_unpkl_offset, c"", METH_O), + method_vararg!(_unpkl_zoned, c""), + method!(_unpkl_system, c"", METH_O), // FUTURE: set __module__ on these - method!( - years, - "Create a new `DateDelta` representing the given number of years.", - METH_O - ), - method!( - months, - "Create a new `DateDelta` representing the given number of months.", - METH_O - ), - method!( - weeks, - "Create a new `DateDelta` representing the given number of weeks.", - METH_O - ), - method!( - days, - "Create a new `DateDelta` representing the given number of days.", - METH_O - ), - method!( - hours, - "Create a new `TimeDelta` representing the given number of hours.", - METH_O - ), - method!( - minutes, - "Create a new `TimeDelta` representing the given number of minutes.", - METH_O - ), - method!( - seconds, - "Create a new `TimeDelta` representing the given number of seconds.", - METH_O - ), - method!( - milliseconds, - "Create a new `TimeDelta` representing the given number of milliseconds.", - METH_O - ), - method!( - microseconds, - "Create a new `TimeDelta` representing the given number of microseconds.", - METH_O - ), - method!( - nanoseconds, - "Create a new `TimeDelta` representing the given number of nanoseconds.", - METH_O - ), - method!(_patch_time_frozen, "", METH_O), - method!(_patch_time_keep_ticking, "", METH_O), - method!(_unpatch_time, ""), + method!(years, doc::YEARS, METH_O), + method!(months, doc::MONTHS, METH_O), + method!(weeks, doc::WEEKS, METH_O), + method!(days, doc::DAYS, METH_O), + method!(hours, doc::HOURS, METH_O), + method!(minutes, doc::MINUTES, METH_O), + method!(seconds, doc::SECONDS, METH_O), + method!(milliseconds, doc::MILLISECONDS, METH_O), + method!(microseconds, doc::MICROSECONDS, METH_O), + method!(nanoseconds, doc::NANOSECONDS, METH_O), + method!(_patch_time_frozen, c"", METH_O), + method!(_patch_time_keep_ticking, c"", METH_O), + method!(_unpatch_time, c""), PyMethodDef::zeroed(), ]; @@ -208,8 +171,13 @@ unsafe fn create_enum(name: &CStr, members: &[(&CStr, i32)]) -> PyReturn { .as_result() } -unsafe fn new_exc(module: *mut PyObject, name: &CStr, base: *mut PyObject) -> *mut PyObject { - let e = PyErr_NewException(name.as_ptr(), base, NULL()); +unsafe fn new_exc( + module: *mut PyObject, + name: &CStr, + doc: &CStr, + base: *mut PyObject, +) -> *mut PyObject { + let e = PyErr_NewExceptionWithDoc(name.as_ptr(), doc.as_ptr(), base, NULL()); if e.is_null() || PyModule_AddType(module, e.cast()) != 0 { return NULL(); } @@ -449,11 +417,20 @@ unsafe extern "C" fn module_exec(module: *mut PyObject) -> c_int { state.str_offset = PyUnicode_InternFromString(c"offset".as_ptr()); state.str_ignore_dst = PyUnicode_InternFromString(c"ignore_dst".as_ptr()); - state.exc_repeated = new_exc(module, c"whenever.RepeatedTime", NULL()); - state.exc_skipped = new_exc(module, c"whenever.SkippedTime", NULL()); - state.exc_invalid_offset = new_exc(module, c"whenever.InvalidOffset", PyExc_ValueError); - state.exc_implicitly_ignoring_dst = - new_exc(module, c"whenever.ImplicitlyIgnoringDST", PyExc_TypeError); + state.exc_repeated = new_exc(module, c"whenever.RepeatedTime", doc::REPEATEDTIME, NULL()); + state.exc_skipped = new_exc(module, c"whenever.SkippedTime", doc::SKIPPEDTIME, NULL()); + state.exc_invalid_offset = new_exc( + module, + c"whenever.InvalidOffset", + doc::INVALIDOFFSET, + PyExc_ValueError, + ); + state.exc_implicitly_ignoring_dst = new_exc( + module, + c"whenever.ImplicitlyIgnoringDST", + doc::IMPLICITLYIGNORINGDST, + PyExc_TypeError, + ); // Making time patcheable results in a performance hit. // Only enable it if the time_machine module is available. diff --git a/src/local_datetime.rs b/src/local_datetime.rs index 44167977..28bc80dd 100644 --- a/src/local_datetime.rs +++ b/src/local_datetime.rs @@ -3,6 +3,7 @@ use core::{mem, ptr::null_mut as NULL}; use pyo3_ffi::*; use crate::common::*; +use crate::docstrings as doc; use crate::offset_datetime::check_ignore_dst_kwarg; use crate::{ date::Date, @@ -228,8 +229,7 @@ unsafe fn __sub__(obj_a: *mut PyObject, obj_b: *mut PyObject) -> PyReturn { if Py_TYPE(obj_a) == Py_TYPE(obj_b) { Err(py_err!( State::for_obj(obj_a).exc_implicitly_ignoring_dst, - "The difference between local datetimes implicitly ignores DST transitions \ - and other timezone changes. Use the `difference` method instead." + doc::DIFF_OPERATOR_LOCAL_MSG ))? } else { _shift_operator(obj_a, obj_b, true) @@ -264,9 +264,7 @@ unsafe fn _shift_operator(obj_a: *mut PyObject, obj_b: *mut PyObject, negate: bo } else if type_b == state.datetime_delta_type || type_b == state.time_delta_type { Err(py_err!( state.exc_implicitly_ignoring_dst, - "Adding or subtracting a (date)time delta to a local datetime \ - implicitly ignores DST transitions and other timezone \ - changes. Instead, use the `add` or `subtract` method." + doc::SHIFT_LOCAL_MSG ))? } else { Err(type_err!( @@ -287,6 +285,10 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_tp_richcompare, __richcmp__), slotmethod!(Py_nb_add, __add__, 2), slotmethod!(Py_nb_subtract, __sub__, 2), + PyType_Slot { + slot: Py_tp_doc, + pfunc: doc::LOCALDATETIME.as_ptr() as *mut c_void, + }, PyType_Slot { slot: Py_tp_hash, pfunc: __hash__ as *mut c_void, @@ -478,11 +480,7 @@ unsafe fn _shift_method( if nanos != 0 && !ignore_dst { Err(py_err!( state.exc_implicitly_ignoring_dst, - "Adding time units to a LocalDateTime implicitly ignores \ - Daylight Saving Time. Instead, convert to a ZonedDateTime first \ - using assume_tz(). Or, if you're sure you want to ignore DST, \ - explicitly pass ignore_dst=True. For more information, see \ - whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic" + doc::ADJUST_LOCAL_DATETIME_MSG ))? } DateTime::extract(slf) @@ -499,15 +497,7 @@ unsafe fn difference( kwargs: &mut KwargIter, ) -> PyReturn { let state = State::for_type(cls); - check_ignore_dst_kwarg( - kwargs, - state, - "The difference between two local datetimes implicitly ignores DST transitions. \ - and other timezone changes. To perform DST-safe arithmetic, convert to a ZonedDateTime \ - first using assume_tz(). Or, if you're sure you want to ignore DST, explicitly pass \ - ignore_dst=True. For more information, see \ - whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic", - )?; + check_ignore_dst_kwarg(kwargs, state, doc::DIFF_LOCAL_MSG)?; let [arg] = *args else { Err(type_err!("difference() takes exactly 1 argument"))? }; @@ -824,89 +814,44 @@ unsafe fn replace_time(slf: *mut PyObject, arg: *mut PyObject) -> PyReturn { } static mut METHODS: &[PyMethodDef] = &[ - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(__reduce__, c""), method!( from_py_datetime, - "Create an instance from a datetime.datetime", + doc::LOCALDATETIME_FROM_PY_DATETIME, METH_CLASS | METH_O ), - method!(py_datetime, "Convert to a datetime.datetime"), + method!(py_datetime, doc::BASICCONVERSIONS_PY_DATETIME), method!( get_date named "date", - "Get the date component" + doc::KNOWSLOCAL_DATE ), method!( get_time named "time", - "Get the time component" - ), - method!( - format_common_iso, - "Get the common ISO 8601 string representation" + doc::KNOWSLOCAL_TIME ), + method!(format_common_iso, doc::LOCALDATETIME_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Create an instance from the common ISO 8601 string representation", + doc::LOCALDATETIME_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!(__reduce__, ""), - method_vararg!( - strptime, - "strptime($type, string, fmt, /)\n--\n\n\ - Parse a string into a LocalDateTime", - METH_CLASS - ), - method_kwargs!( - replace, - "replace($self, *, year=None, month=None, day=None, hour=None, \ - minute=None, second=None, nanosecond=None)\n--\n\n\ - Return a new instance with the specified fields replaced" - ), - method!(assume_utc, "Assume the datetime is in UTC"), + method_vararg!(strptime, doc::LOCALDATETIME_STRPTIME, METH_CLASS), + method_kwargs!(replace, doc::LOCALDATETIME_REPLACE), + method!(assume_utc, doc::LOCALDATETIME_ASSUME_UTC), method!( assume_fixed_offset, - "Assume the datetime has a fixed offset", - METH_O - ), - method_kwargs!( - assume_tz, - "assume_tz($self, tz, /, *, disambiguate)\n--\n\n\ - Assume the datetime is in a timezone" - ), - method_kwargs!( - assume_system_tz, - "assume_system_tz($self, *, disambiguate)\n--\n\n\ - Assume the datetime is in the system timezone" - ), - method!( - replace_date, - "Return a new instance with the date replaced", + doc::LOCALDATETIME_ASSUME_FIXED_OFFSET, METH_O ), - method!( - replace_time, - "Return a new instance with the time replaced", - METH_O - ), - method_kwargs!( - add, - "add($self, delta=None, /, *, years=0, months=0, days=0, \ - hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ - ignore_dst=False)\n--\n\n\ - Add various time and/or calendar units" - ), - method_kwargs!( - subtract, - "subtract($self, delta=None, /, *, years=0, months=0, days=0, \ - hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ - ignore_dst=False)\n--\n\n\ - Subtract various time and/or calendar units" - ), - method_kwargs!( - difference, - "difference($self, other, /, *, ignore_dst)\n--\n\n\ - Get the difference between two local datetimes" - ), + method_kwargs!(assume_tz, doc::LOCALDATETIME_ASSUME_TZ), + method_kwargs!(assume_system_tz, doc::LOCALDATETIME_ASSUME_SYSTEM_TZ), + method!(replace_date, doc::LOCALDATETIME_REPLACE_DATE, METH_O), + method!(replace_time, doc::LOCALDATETIME_REPLACE_TIME, METH_O), + method_kwargs!(add, doc::LOCALDATETIME_ADD), + method_kwargs!(subtract, doc::LOCALDATETIME_SUBTRACT), + method_kwargs!(difference, doc::LOCALDATETIME_DIFFERENCE), PyMethodDef::zeroed(), ]; diff --git a/src/monthday.rs b/src/monthday.rs index dc597a1d..45ea1b95 100644 --- a/src/monthday.rs +++ b/src/monthday.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Display, Formatter}; use crate::common::*; use crate::date::{Date, MAX_MONTH_DAYS_IN_LEAP_YEAR}; +use crate::docstrings as doc; use crate::State; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] @@ -129,7 +130,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_tp_richcompare, __richcmp__), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A month and day type, i.e. a date without a specific year".as_ptr() as *mut c_void, + pfunc: doc::MONTHDAY.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_methods, @@ -230,29 +231,18 @@ unsafe fn is_leap(slf: *mut PyObject, _: *mut PyObject) -> PyReturn { } static mut METHODS: &[PyMethodDef] = &[ - method!( - format_common_iso, - "Return the date in the common ISO 8601 format" - ), + method!(__reduce__, c""), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(format_common_iso, doc::MONTHDAY_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Create a date from the common ISO 8601 format", + doc::MONTHDAY_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!( - in_year, - "Create a date from this month and day in the specified year", - METH_O - ), - method!(is_leap, "Return whether this month and day is February 29"), - method!(__reduce__, ""), - method_kwargs!( - replace, - "replace($self, *, month=None, day=None)\n--\n\n\ - Return a new instance with the specified components replaced" - ), + method!(in_year, doc::MONTHDAY_IN_YEAR, METH_O), + method!(is_leap, doc::MONTHDAY_IS_LEAP), + method_kwargs!(replace, doc::MONTHDAY_REPLACE), PyMethodDef::zeroed(), ]; diff --git a/src/offset_datetime.rs b/src/offset_datetime.rs index e5f10efe..946cf4f8 100644 --- a/src/offset_datetime.rs +++ b/src/offset_datetime.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Display, Formatter}; use crate::common::*; use crate::datetime_delta::set_units_from_kwargs; +use crate::docstrings as doc; use crate::local_datetime::set_components_from_kwargs; use crate::{ date::Date, @@ -388,7 +389,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_nb_subtract, __sub__, 2), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A datetime type with a fixed UTC offset".as_ptr() as *mut c_void, + pfunc: doc::OFFSETDATETIME.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_hash, @@ -535,7 +536,7 @@ unsafe fn replace_date( } = OffsetDateTime::extract(slf); let state = State::for_type(cls); - check_ignore_dst_kwarg(kwargs, state, IGNORE_DST_MSG)?; + check_ignore_dst_kwarg(kwargs, state, doc::ADJUST_OFFSET_DATETIME_MSG)?; let &[arg] = args else { Err(type_err!("replace() takes exactly 1 positional argument"))? @@ -559,7 +560,7 @@ unsafe fn replace_time( date, offset_secs, .. } = OffsetDateTime::extract(slf); let state = State::for_type(cls); - check_ignore_dst_kwarg(kwargs, state, IGNORE_DST_MSG)?; + check_ignore_dst_kwarg(kwargs, state, doc::ADJUST_OFFSET_DATETIME_MSG)?; let &[arg] = args else { Err(type_err!("replace() takes exactly 1 positional argument"))? @@ -627,7 +628,10 @@ unsafe fn replace( })?; if !ignore_dst { - Err(py_err!(state.exc_implicitly_ignoring_dst, IGNORE_DST_MSG))? + Err(py_err!( + state.exc_implicitly_ignoring_dst, + doc::ADJUST_OFFSET_DATETIME_MSG + ))? } let date = Date::from_longs(year, month, day).ok_or_value_err("Invalid date")?; @@ -650,16 +654,7 @@ unsafe fn now( Err(type_err!("now() takes exactly 1 positional argument"))? }; - check_ignore_dst_kwarg( - kwargs, - state, - "Getting the current time with a fixed offset implicitly ignores DST \ - and other timezone changes. Instead, use `Instant.now()` or \ - `ZonedDateTime.now()` if you know the timezone. \ - Or, if you want to ignore DST and accept potentially incorrect offsets, \ - pass `ignore_dst=True` to this method. For more information, see \ - whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic", - )?; + check_ignore_dst_kwarg(kwargs, state, doc::OFFSET_NOW_DST_MSG)?; let offset_secs = extract_offset(offset, state.time_delta_type)?; // FUTURE: Conversion to Instant can be done a bit more efficiently @@ -795,11 +790,7 @@ unsafe fn _shift_method( if !ignore_dst { Err(py_err!( state.exc_implicitly_ignoring_dst, - "Adding time units to an OffsetDateTime implicitly ignores \ - Daylight Saving Time. Instead, convert to a ZonedDateTime first \ - using assume_tz(). Or, if you're sure you want to ignore DST, \ - explicitly pass ignore_dst=True. For more information, see \ - whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic" + doc::ADJUST_OFFSET_DATETIME_MSG ))? } let OffsetDateTime { @@ -893,7 +884,7 @@ unsafe fn check_from_timestamp_args_return_offset( })?; if !ignore_dst { - Err(py_err!(exc_implicitly_ignoring_dst, IGNORE_DST_MSG))? + Err(py_err!(exc_implicitly_ignoring_dst, doc::TIMESTAMP_DST_MSG))? } offset_secs.ok_or_type_err("Missing required keyword argument: 'offset'") @@ -1137,122 +1128,67 @@ unsafe fn parse_rfc2822(cls: *mut PyObject, s_obj: *mut PyObject) -> PyReturn { static mut METHODS: &[PyMethodDef] = &[ // FUTURE: get docstrings from Python implementation - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!(__reduce__, ""), - method_kwargs!( - now, - "now($type, offset, /, *, ignore_dst)\n--\n\n\ - Create a new instance representing the current time", - METH_CLASS - ), - method!(exact_eq, "exact_eq()\n--\n\nExact equality", METH_O), - method!(py_datetime, "Convert to a `datetime.datetime`"), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(__reduce__, c""), + method_kwargs!(now, doc::OFFSETDATETIME_NOW, METH_CLASS), + method!(exact_eq, doc::KNOWSINSTANT_EXACT_EQ, METH_O), + method!(py_datetime, doc::BASICCONVERSIONS_PY_DATETIME), method!( from_py_datetime, - "Create a new instance from a `datetime.datetime`", + doc::OFFSETDATETIME_FROM_PY_DATETIME, METH_O | METH_CLASS ), - method!(instant, "Get the underlying instant"), - method!(local, "Get the local date and time"), - method!(to_tz, "Convert to a `ZonedDateTime` with given tz", METH_O), - method_vararg!( - to_fixed_offset, - "to_fixed_offset($self, offset=None, /)\n--\n\n\ - Convert to a new instance with a different offset" - ), - method!(to_system_tz, "Convert to a datetime to the system timezone"), - method!(date, "The date component"), - method!(time, "The time component"), - method!(format_rfc3339, "Format according to RFC3339"), + method!(instant, doc::KNOWSINSTANTANDLOCAL_INSTANT), + method!(local, doc::KNOWSINSTANTANDLOCAL_LOCAL), + method!(to_tz, doc::KNOWSINSTANT_TO_TZ, METH_O), + method_vararg!(to_fixed_offset, doc::KNOWSINSTANT_TO_FIXED_OFFSET), + method!(to_system_tz, doc::KNOWSINSTANT_TO_SYSTEM_TZ), + method!(date, doc::KNOWSLOCAL_DATE), + method!(time, doc::KNOWSLOCAL_TIME), + method!(format_rfc3339, doc::OFFSETDATETIME_FORMAT_RFC3339), method!( parse_rfc3339, - "Create a new instance from an RFC3339 timestamp", + doc::OFFSETDATETIME_PARSE_RFC3339, METH_O | METH_CLASS ), - method!(format_rfc2822, "Format according to RFC2822"), + method!(format_rfc2822, doc::OFFSETDATETIME_FORMAT_RFC2822), method!( parse_rfc2822, - "Create a new instance from an RFC2822 timestamp", + doc::OFFSETDATETIME_PARSE_RFC2822, METH_O | METH_CLASS ), - method!( - format_common_iso, - "Format according to the common ISO8601 style" - ), + method!(format_common_iso, doc::OFFSETDATETIME_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Parse from the common ISO8601 format", + doc::OFFSETDATETIME_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!(timestamp, "Convert to a UNIX timestamp"), - method!( - timestamp_millis, - "Convert to a UNIX timestamp in milliseconds" - ), - method!( - timestamp_nanos, - "Convert to a UNIX timestamp in nanoseconds" - ), + method!(timestamp, doc::KNOWSINSTANT_TIMESTAMP), + method!(timestamp_millis, doc::KNOWSINSTANT_TIMESTAMP_MILLIS), + method!(timestamp_nanos, doc::KNOWSINSTANT_TIMESTAMP_NANOS), method_kwargs!( from_timestamp, - "from_timestamp($type, timestamp, /, *, offset, ignore_dst)\n--\n\n\ - Create a new instance from a UNIX timestamp", + doc::OFFSETDATETIME_FROM_TIMESTAMP, METH_CLASS ), method_kwargs!( from_timestamp_millis, - "from_timestamp_millis($type, timestamp, /, *, offset, ignore_dst)\n--\n\n\ - Create a new instance from a UNIX timestamp in milliseconds", + doc::OFFSETDATETIME_FROM_TIMESTAMP_MILLIS, METH_CLASS ), method_kwargs!( from_timestamp_nanos, - "from_timestamp_nanos($type, timestamp, /, *, offset, ignore_dst)\n--\n\n\ - Create a new instance from a UNIX timestamp", + doc::OFFSETDATETIME_FROM_TIMESTAMP_NANOS, METH_CLASS ), - method_kwargs!( - replace, - "replace($self, /, *, year=None, month=None, day=None, hour=None, \ - minute=None, second=None, nanosecond=None, offset=None, ignore_dst)\n--\n\n\ - Return a new instance with the specified fields replaced" - ), - method_kwargs!( - replace_date, - "replace_date($self, date, /, *, ignore_dst)\n--\n\n\ - Return a new instance with the date replaced" - ), - method_kwargs!( - replace_time, - "replace_time($self, time, /, *, ignore_dst)\n--\n\n\ - Return a new instance with the time replaced" - ), - method_vararg!( - strptime, - "strptime($type, date_string, format, /)\n--\n\n\ - Parse a string with strptime", - METH_CLASS - ), - method_kwargs!( - add, - "add($self, delta=None, /, *, years=0, months=0, weeks=0, days=0, \ - hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ - ignore_dst=False)\n--\n\n\ - Add time units" - ), - method_kwargs!( - subtract, - "subtract($self, delta=None, /, *, years=0, months=0, weeks=0, days=0, \ - hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ - ignore_dst=False)\n--\n\n\ - Subtract time units" - ), - method!( - difference, - "Calculate the difference between two instances", - METH_O - ), + method_kwargs!(replace, doc::OFFSETDATETIME_REPLACE), + method_kwargs!(replace_date, doc::OFFSETDATETIME_REPLACE_DATE), + method_kwargs!(replace_time, doc::OFFSETDATETIME_REPLACE_TIME), + method_vararg!(strptime, doc::OFFSETDATETIME_STRPTIME, METH_CLASS), + method_kwargs!(add, doc::OFFSETDATETIME_ADD), + method_kwargs!(subtract, doc::OFFSETDATETIME_SUBTRACT), + method!(difference, doc::KNOWSINSTANT_DIFFERENCE, METH_O), PyMethodDef::zeroed(), ]; @@ -1307,11 +1243,4 @@ static mut GETSETTERS: &[PyGetSetDef] = &[ }, ]; -static IGNORE_DST_MSG: &str = - "Adjusting a fixed-offset datetime implicitly ignores DST and other timezone changes. \ - To perform DST-safe operations, convert to a ZonedDateTime first using `to_tz()`. \ - Or, if you don't know the timezone and accept potentially incorrect results \ - during DST transitions, pass `ignore_dst=True`. For more information, see \ - whenever.rtfd.io/en/latest/overview.html#dst-safe-arithmetic"; - type_spec!(OffsetDateTime, SLOTS); diff --git a/src/system_datetime.rs b/src/system_datetime.rs index cd6b21eb..b61e7cdc 100644 --- a/src/system_datetime.rs +++ b/src/system_datetime.rs @@ -3,6 +3,7 @@ use core::{mem, ptr::null_mut as NULL}; use pyo3_ffi::*; use crate::common::*; +use crate::docstrings as doc; use crate::{ date::Date, date_delta::DateDelta, @@ -227,11 +228,7 @@ unsafe fn _shift(obj_a: *mut PyObject, obj_b: *mut PyObject, negate: bool) -> Py .to_system_tz(py_api)? .to_obj(type_a) } else if type_b == date_delta_type || type_b == datetime_delta_type { - Err(type_err!( - "Addition/subtraction of calendar units on a ZonedDateTime requires \ - explicit disambiguation. Use the `add`/`subtract` methods instead. \ - For example, instead of `dt + delta` use `dt.add(delta, disambiguate=...)`." - ))? + Err(type_err!(doc::SHIFT_OPERATOR_CALENDAR_ZONED_MSG))? } else { Ok(newref(Py_NotImplemented())) } @@ -290,7 +287,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_nb_subtract, __sub__, 2), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A datetime in the system timezone".as_ptr() as *mut c_void, + pfunc: doc::SYSTEMDATETIME.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_hash, @@ -811,100 +808,54 @@ unsafe fn difference(obj_a: *mut PyObject, obj_b: *mut PyObject) -> PyReturn { static mut METHODS: &[PyMethodDef] = &[ // FUTURE: get docstrings from Python implementation - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!(to_tz, "Convert to a `ZonedDateTime` with given tz", METH_O), - method!(exact_eq, "Exact equality", METH_O), - method!(py_datetime, "Convert to a `datetime.datetime`"), - method!(instant, "Get the underlying instant"), - method!(date, "The date component"), - method!(time, "The time component"), - method!( - format_common_iso, - "Format according to the common ISO8601 style" - ), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(__reduce__, c""), + method!(to_tz, doc::KNOWSINSTANT_TO_TZ, METH_O), + method!(to_system_tz, doc::KNOWSINSTANT_TO_SYSTEM_TZ), + method_vararg!(to_fixed_offset, doc::KNOWSINSTANT_TO_FIXED_OFFSET), + method!(exact_eq, doc::KNOWSINSTANT_EXACT_EQ, METH_O), + method!(py_datetime, doc::BASICCONVERSIONS_PY_DATETIME), + method!(instant, doc::KNOWSINSTANTANDLOCAL_INSTANT), + method!(local, doc::KNOWSINSTANTANDLOCAL_LOCAL), + method!(date, doc::KNOWSLOCAL_DATE), + method!(time, doc::KNOWSLOCAL_TIME), + method!(format_common_iso, doc::OFFSETDATETIME_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Create a new instance from the common ISO8601 style", + doc::SYSTEMDATETIME_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!(to_system_tz, "Convert to the system timezone"), - method!(__reduce__, ""), - method!( - now, - "Create a new instance representing the current time", - METH_CLASS | METH_NOARGS - ), + method!(now, doc::SYSTEMDATETIME_NOW, METH_CLASS | METH_NOARGS), method!( from_py_datetime, - "Create a new instance from a `datetime.datetime`", + doc::SYSTEMDATETIME_FROM_PY_DATETIME, METH_O | METH_CLASS ), - method!(local, "Get the local time"), - method!(timestamp, "Convert to a UNIX timestamp"), - method!( - timestamp_millis, - "Convert to a UNIX timestamp in milliseconds" - ), - method!( - timestamp_nanos, - "Convert to a UNIX timestamp in nanoseconds" - ), + method!(timestamp, doc::KNOWSINSTANT_TIMESTAMP), + method!(timestamp_millis, doc::KNOWSINSTANT_TIMESTAMP_MILLIS), + method!(timestamp_nanos, doc::KNOWSINSTANT_TIMESTAMP_NANOS), method!( from_timestamp, - "Create a new instance from a UNIX timestamp in seconds", + doc::SYSTEMDATETIME_FROM_TIMESTAMP, METH_O | METH_CLASS ), method!( from_timestamp_millis, - "Create a new instance from a UNIX timestamp in milliseconds", + doc::SYSTEMDATETIME_FROM_TIMESTAMP_MILLIS, METH_O | METH_CLASS ), method!( from_timestamp_nanos, - "Create a new instance from a UNIX timestamp in nanoseconds", + doc::SYSTEMDATETIME_FROM_TIMESTAMP_NANOS, METH_O | METH_CLASS ), - method_kwargs!( - replace, - "replace($self, *, year=None, month=None, day=None, hour=None, \ - minute=None, second=None, nanosecond=None, disambiguate=None)\n--\n\n\ - Return a new instance with the specified fields replaced" - ), - method_vararg!( - to_fixed_offset, - "to_fixed_offset($self, offset=None, /)\n--\n\n\ - Return an equivalent instance with the given offset" - ), - method_kwargs!( - replace_date, - "replace_date($self, date, /, *, disambiguate)\n--\n\n\ - Return a new instance with the date replaced" - ), - method_kwargs!( - replace_time, - "replace_time($self, time, /, *, disambiguate)\n--\n\n\ - Return a new instance with the time replaced" - ), - method_kwargs!( - add, - "add($self, delta=None, /, *, years=0, months=0, days=0, hours=0, \ - minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ - disambiguate)\n--\n\n\ - Return a new instance with the given time units added" - ), - method_kwargs!( - subtract, - "subtract($self, delta=None, /, *, years=0, months=0, days=0, hours=0, \ - minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ - disambiguate)\n--\n\n\ - Return a new instance with the given time units subtracted" - ), - method!( - difference, - "Return the difference between two instances", - METH_O - ), + method_kwargs!(replace, doc::SYSTEMDATETIME_REPLACE), + method_kwargs!(replace_date, doc::SYSTEMDATETIME_REPLACE_DATE), + method_kwargs!(replace_time, doc::SYSTEMDATETIME_REPLACE_TIME), + method_kwargs!(add, doc::SYSTEMDATETIME_ADD), + method_kwargs!(subtract, doc::SYSTEMDATETIME_SUBTRACT), + method!(difference, doc::KNOWSINSTANT_DIFFERENCE, METH_O), PyMethodDef::zeroed(), ]; diff --git a/src/time.rs b/src/time.rs index 9c6c35d7..f663033d 100644 --- a/src/time.rs +++ b/src/time.rs @@ -6,6 +6,7 @@ use std::ptr::null_mut as NULL; use crate::common::*; use crate::date::Date; +use crate::docstrings as doc; use crate::local_datetime::DateTime; use crate::State; @@ -275,7 +276,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_tp_richcompare, __richcmp__), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A type representing the time of day".as_ptr() as *mut c_void, + pfunc: doc::TIME.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_methods, @@ -442,30 +443,19 @@ unsafe fn replace( } static mut METHODS: &[PyMethodDef] = &[ - method!(py_time, "Convert to a Python datetime.time"), - method_kwargs!( - replace, - "replace($self, *, hour=None, minute=None, second=None, nanosecond=None)\n--\n\n\ - Replace one or more components of the time" - ), - method!( - format_common_iso, - "Return the time in the common ISO 8601 format" - ), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(__reduce__, c""), + method!(py_time, doc::TIME_PY_TIME), + method_kwargs!(replace, doc::TIME_REPLACE), + method!(format_common_iso, doc::TIME_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Create an instance from the common ISO 8601 format", - METH_O | METH_CLASS - ), - method!( - from_py_time, - "Create a time from a Python datetime.time", + doc::TIME_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!(__reduce__, ""), - method!(on, "Combine with a date to create a datetime", METH_O), + method!(from_py_time, doc::TIME_FROM_PY_TIME, METH_O | METH_CLASS), + method!(on, doc::TIME_ON, METH_O), PyMethodDef::zeroed(), ]; diff --git a/src/time_delta.rs b/src/time_delta.rs index 7b739f62..1e3b2f1f 100644 --- a/src/time_delta.rs +++ b/src/time_delta.rs @@ -10,6 +10,7 @@ use crate::common::*; use crate::date::MAX_YEAR; use crate::date_delta::{DateDelta, InitError}; use crate::datetime_delta::{handle_exact_unit, DateTimeDelta}; +use crate::docstrings as doc; use crate::State; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] @@ -489,7 +490,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_nb_absolute, __abs__, 1), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A delta type of precise time units".as_ptr() as *mut c_void, + pfunc: doc::TIMEDELTA.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_methods, @@ -794,35 +795,32 @@ unsafe fn parse_common_iso(cls: *mut PyObject, s_obj: *mut PyObject) -> PyReturn } static mut METHODS: &[PyMethodDef] = &[ - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!( - format_common_iso, - "Return the time delta in the common ISO8601 format" - ), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(__reduce__, c""), + method!(format_common_iso, doc::TIMEDELTA_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Parse from the common ISO8601 period format", + doc::TIMEDELTA_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!(in_nanoseconds, "Return the total number of nanoseconds"), - method!(in_microseconds, "Return the total number of microseconds"), - method!(in_milliseconds, "Return the total number of milliseconds"), - method!(in_seconds, "Return the total number of seconds"), - method!(in_minutes, "Return the total number of minutes"), - method!(in_hours, "Return the total number of hours"), - method!( - in_days_of_24h, - "Return the total number of days, assuming 24 hours per day" - ), + method!(in_nanoseconds, doc::TIMEDELTA_IN_NANOSECONDS), + method!(in_microseconds, doc::TIMEDELTA_IN_MICROSECONDS), + method!(in_milliseconds, doc::TIMEDELTA_IN_MILLISECONDS), + method!(in_seconds, doc::TIMEDELTA_IN_SECONDS), + method!(in_minutes, doc::TIMEDELTA_IN_MINUTES), + method!(in_hours, doc::TIMEDELTA_IN_HOURS), + method!(in_days_of_24h, doc::TIMEDELTA_IN_DAYS_OF_24H), method!( from_py_timedelta, - "Create a date from a Python datetime.timedelta", + doc::TIMEDELTA_FROM_PY_TIMEDELTA, METH_O | METH_CLASS ), - method!(py_timedelta, "Convert to a Python datetime.timedelta"), - method!(in_hrs_mins_secs_nanos, "Return the time delta as a tuple"), - method!(__reduce__, ""), + method!(py_timedelta, doc::TIMEDELTA_PY_TIMEDELTA), + method!( + in_hrs_mins_secs_nanos, + doc::TIMEDELTA_IN_HRS_MINS_SECS_NANOS + ), PyMethodDef::zeroed(), ]; diff --git a/src/yearmonth.rs b/src/yearmonth.rs index 3c6f1a73..327f53dd 100644 --- a/src/yearmonth.rs +++ b/src/yearmonth.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Display, Formatter}; use crate::common::*; use crate::date::{Date, MAX_YEAR, MIN_YEAR}; +use crate::docstrings as doc; use crate::State; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] @@ -130,7 +131,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_tp_richcompare, __richcmp__), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A year and month type, i.e. a date without a day".as_ptr() as *mut c_void, + pfunc: doc::YEARMONTH.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_methods, @@ -228,28 +229,17 @@ unsafe fn on_day(slf: *mut PyObject, day_obj: *mut PyObject) -> PyReturn { } static mut METHODS: &[PyMethodDef] = &[ - method!( - format_common_iso, - "Return the date in the common ISO 8601 format" - ), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(__reduce__, c""), + method!(format_common_iso, doc::YEARMONTH_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Create a date from the common ISO 8601 format", + doc::YEARMONTH_PARSE_COMMON_ISO, METH_O | METH_CLASS ), - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!( - on_day, - "Create a date from this year-month with a given day", - METH_O - ), - method!(__reduce__, ""), - method_kwargs!( - replace, - "replace($self, *, month=None, day=None)\n--\n\n\ - Return a new instance with the specified components replaced" - ), + method!(on_day, doc::YEARMONTH_ON_DAY, METH_O), + method_kwargs!(replace, doc::YEARMONTH_REPLACE), PyMethodDef::zeroed(), ]; diff --git a/src/zoned_datetime.rs b/src/zoned_datetime.rs index 1bbb399a..57410f21 100644 --- a/src/zoned_datetime.rs +++ b/src/zoned_datetime.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Display, Formatter}; use crate::common::*; use crate::datetime_delta::set_units_from_kwargs; +use crate::docstrings as doc; use crate::local_datetime::set_components_from_kwargs; use crate::{ date::Date, @@ -349,11 +350,7 @@ unsafe fn _shift(obj_a: *mut PyObject, obj_b: *mut PyObject, negate: bool) -> Py .to_tz(py_api, zdt.zoneinfo)? .to_obj(type_a) } else if type_b == date_delta_type || type_b == datetime_delta_type { - Err(type_err!( - "Addition/subtraction of calendar units on a ZonedDateTime requires \ - explicit disambiguation. Use the `add`/`subtract` methods instead. \ - For example, instead of `dt + delta` use `dt.add(delta, disambiguate=...)`." - ))? + Err(type_err!(doc::SHIFT_OPERATOR_CALENDAR_ZONED_MSG))? } else { Ok(newref(Py_NotImplemented())) } @@ -412,7 +409,7 @@ static mut SLOTS: &[PyType_Slot] = &[ slotmethod!(Py_nb_subtract, __sub__, 2), PyType_Slot { slot: Py_tp_doc, - pfunc: c"A datetime type with IANA tz ID".as_ptr() as *mut c_void, + pfunc: doc::ZONEDDATETIME.as_ptr() as *mut c_void, }, PyType_Slot { slot: Py_tp_hash, @@ -1250,104 +1247,55 @@ unsafe fn difference(obj_a: *mut PyObject, obj_b: *mut PyObject) -> PyReturn { } static mut METHODS: &[PyMethodDef] = &[ - method!(identity2 named "__copy__", ""), - method!(identity2 named "__deepcopy__", "", METH_O), - method!(to_tz, "Convert to a `ZonedDateTime` with given tz", METH_O), - method!(exact_eq, "Exact equality", METH_O), - method!(py_datetime, "Convert to a `datetime.datetime`"), - method!(instant, "Get the underlying instant"), - method!(to_system_tz, "Convert to a datetime in the system timezone"), - method!(date, "The date component"), - method!(time, "The time component"), - method!( - format_common_iso, - "Format in the common ISO format (RFC9557 profile)" - ), + method!(identity2 named "__copy__", c""), + method!(identity2 named "__deepcopy__", c"", METH_O), + method!(__reduce__, c""), + method!(to_tz, doc::KNOWSINSTANT_TO_TZ, METH_O), + method!(to_system_tz, doc::KNOWSINSTANT_TO_SYSTEM_TZ), + method_vararg!(to_fixed_offset, doc::KNOWSINSTANT_TO_FIXED_OFFSET), + method!(exact_eq, doc::KNOWSINSTANT_EXACT_EQ, METH_O), + method!(py_datetime, doc::BASICCONVERSIONS_PY_DATETIME), + method!(instant, doc::KNOWSINSTANTANDLOCAL_INSTANT), + method!(local, doc::KNOWSINSTANTANDLOCAL_LOCAL), + method!(date, doc::KNOWSLOCAL_DATE), + method!(time, doc::KNOWSLOCAL_TIME), + method!(format_common_iso, doc::ZONEDDATETIME_FORMAT_COMMON_ISO), method!( parse_common_iso, - "Parse the common ISO format (RFC9557 profile)", - METH_O | METH_CLASS - ), - method!(__reduce__, ""), - method!( - now, - "Create a new instance representing the current time", + doc::ZONEDDATETIME_PARSE_COMMON_ISO, METH_O | METH_CLASS ), + method!(now, doc::ZONEDDATETIME_NOW, METH_O | METH_CLASS), method!( from_py_datetime, - "Create a new instance from a `datetime.datetime`", + doc::ZONEDDATETIME_FROM_PY_DATETIME, METH_O | METH_CLASS ), - method!(local, "Get the local date and time"), - method!(timestamp, "Convert to a UNIX timestamp"), - method!( - timestamp_millis, - "Convert to a UNIX timestamp in milliseconds" - ), - method!( - timestamp_nanos, - "Convert to a UNIX timestamp in nanoseconds" - ), - method!(is_ambiguous, "Check if the datetime is ambiguous"), + method!(timestamp, doc::KNOWSINSTANT_TIMESTAMP), + method!(timestamp_millis, doc::KNOWSINSTANT_TIMESTAMP_MILLIS), + method!(timestamp_nanos, doc::KNOWSINSTANT_TIMESTAMP_NANOS), + method!(is_ambiguous, doc::ZONEDDATETIME_IS_AMBIGUOUS), method_kwargs!( from_timestamp, - "from_timestamp($type, ts, /, *, tz)\n--\n\n\ - Create a new instance from a UNIX timestamp", + doc::ZONEDDATETIME_FROM_TIMESTAMP, METH_CLASS ), method_kwargs!( from_timestamp_millis, - "from_timestamp_millis($type, ts, /, *, tz)\n--\n\n\ - Create a new instance from a UNIX timestamp in milliseconds", + doc::ZONEDDATETIME_FROM_TIMESTAMP_MILLIS, METH_CLASS ), method_kwargs!( from_timestamp_nanos, - "from_timestamp_nanos($type, ts, /, *, tz)\n--\n\n\ - Create a new instance from a UNIX timestamp in nanoseconds", + doc::ZONEDDATETIME_FROM_TIMESTAMP_NANOS, METH_CLASS ), - method_kwargs!( - replace, - "replace($self, /, *, year=None, month=None, day=None, hour=None, \ - minute=None, second=None, nanosecond=None, tz=None, disambiguate)\n--\n\n\ - Return a new instance with the specified fields replaced" - ), - method_kwargs!( - replace_date, - "replace_date($self, date, /, *, disambiguate)\n--\n\n\ - Return a new instance with the date replaced" - ), - method_kwargs!( - replace_time, - "replace_time($self, time, /, *, disambiguate)\n--\n\n\ - Return a new instance with the time replaced" - ), - method_vararg!( - to_fixed_offset, - "to_fixed_offset($self, offset=None, /)\n--\n\n\ - Convert to an equivalent offset datetime" - ), - method_kwargs!( - add, - "add($self, delta=None, /, *, years=0, months=0, days=0, hours=0, \ - minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ - disambiguate=None)\n--\n\n\ - Add various time/calendar units" - ), - method_kwargs!( - subtract, - "subtract($self, delta=None, /, *, years=0, months=0, days=0, hours=0, \ - minutes=0, seconds=0, milliseconds=0, microseconds=0, nanoseconds=0, \ - disambiguate=None)\n--\n\n\ - Subtract various time/calendar units" - ), - method!( - difference, - "Get the difference between two instances", - METH_O - ), + method_kwargs!(replace, doc::ZONEDDATETIME_REPLACE), + method_kwargs!(replace_date, doc::ZONEDDATETIME_REPLACE_DATE), + method_kwargs!(replace_time, doc::ZONEDDATETIME_REPLACE_TIME), + method_kwargs!(add, doc::ZONEDDATETIME_ADD), + method_kwargs!(subtract, doc::ZONEDDATETIME_SUBTRACT), + method!(difference, doc::KNOWSINSTANT_DIFFERENCE, METH_O), PyMethodDef::zeroed(), ]; diff --git a/tests/test_instant.py b/tests/test_instant.py index 31902eee..e0606df6 100644 --- a/tests/test_instant.py +++ b/tests/test_instant.py @@ -171,6 +171,9 @@ def test_zoned(self): assert hash(d) == hash(zoned_same) assert hash(d) != hash(zoned_different) + with pytest.raises(TypeError): + d.exact_eq(zoned_same) # type: ignore[arg-type] + @system_tz_ams() def test_system_tz(self): d: Instant | SystemDateTime = Instant.from_utc(2023, 10, 29, 1, 15) @@ -186,6 +189,9 @@ def test_system_tz(self): assert hash(d) == hash(sys_same) assert hash(d) != hash(sys_different) + with pytest.raises(TypeError): + d.exact_eq(sys_same) # type: ignore[arg-type] + def test_offset(self): d: Instant | OffsetDateTime = Instant.from_utc(2023, 4, 5, 4) offset_same = OffsetDateTime(2023, 4, 5, 6, offset=+2) @@ -198,6 +204,9 @@ def test_offset(self): assert hash(d) == hash(offset_same) assert hash(d) != hash(offset_different) + with pytest.raises(TypeError): + d.exact_eq(offset_same) # type: ignore[arg-type] + class TestTimestamp: diff --git a/tests/test_offset_datetime.py b/tests/test_offset_datetime.py index edcbfc0d..70826918 100644 --- a/tests/test_offset_datetime.py +++ b/tests/test_offset_datetime.py @@ -278,6 +278,9 @@ def test_exact_equality(): assert not d.exact_eq(different) assert not d.exact_eq(d.replace(nanosecond=1, ignore_dst=True)) + with pytest.raises(TypeError): + d.exact_eq(d.instant()) # type: ignore[arg-type] + class TestEquality: def test_same_exact(self): diff --git a/tests/test_system_datetime.py b/tests/test_system_datetime.py index 52afd558..f9eb6af3 100644 --- a/tests/test_system_datetime.py +++ b/tests/test_system_datetime.py @@ -555,6 +555,12 @@ def test_exact_equality(): assert not a.exact_eq(different) assert not different.exact_eq(a) + with pytest.raises(TypeError): + a.exact_eq(42) # type: ignore[arg-type] + + with pytest.raises(TypeError): + a.exact_eq(a.instant()) # type: ignore[arg-type] + class TestParseCommonIso: @pytest.mark.parametrize( diff --git a/tests/test_zoned_datetime.py b/tests/test_zoned_datetime.py index a3132d19..161fe39b 100644 --- a/tests/test_zoned_datetime.py +++ b/tests/test_zoned_datetime.py @@ -1431,9 +1431,12 @@ def test_same_unambiguous(self): def test_invalid(self): a = ZonedDateTime(2020, 8, 15, 12, 8, 30, tz="Europe/Amsterdam") - with pytest.raises((TypeError, AttributeError)): + with pytest.raises(TypeError): a.exact_eq(42) # type: ignore[arg-type] + with pytest.raises(TypeError): + a.exact_eq(a.instant()) # type: ignore[arg-type] + class TestReplace: def test_basics(self):