whenever 0.10.0b4__tar.gz → 0.10.1.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of whenever might be problematic. Click here for more details.
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/Cargo.toml +0 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/PKG-INFO +2 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/README.md +1 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/_custom_pybuild/backend.py +2 -3
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pyproject.toml +58 -23
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/__init__.py +1 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/__init__.pyi +3 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_common.py +1 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_format.py +4 -6
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_parse.py +0 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_pywhenever.py +37 -33
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_shared.py +1 -2
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_utils.py +3 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever.egg-info/PKG-INFO +2 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/setup.py +4 -5
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/date.rs +2 -2
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_date.py +9 -22
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_date_delta.py +2 -8
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_datetime_delta.py +0 -6
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_format_parse.py +0 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_instant.py +4 -13
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_iso_week_date.py +0 -11
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_itemized_date_delta.py +5 -9
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_itemized_delta.py +21 -10
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_math.py +0 -2
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_misc.py +5 -6
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_month_day.py +0 -3
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_offset_datetime.py +4 -20
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_plain_datetime.py +2 -17
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_time.py +0 -4
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_time_delta.py +1 -11
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_tz_posix.py +0 -5
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_tz_tzif.py +0 -1
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_year_month.py +0 -6
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/test_zoned_datetime.py +14 -27
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/.cargo/config.toml +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/Cargo.lock +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/LICENSE +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/MANIFEST.in +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/build.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_core.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_math.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_typing.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_typing_312.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_tz/__init__.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_tz/ambiguity.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_tz/common.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_tz/posix.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_tz/store.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_tz/system.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/_tz/tzif.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever/py.typed +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever.egg-info/SOURCES.txt +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever.egg-info/dependency_links.txt +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever.egg-info/requires.txt +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/pysrc/whenever.egg-info/top_level.txt +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/setup.cfg +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/date_delta.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/datetime_delta.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/instant.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/itemized_date_delta.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/itemized_delta.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/mod.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/offset_datetime.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/plain_datetime.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/time.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/time_delta.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/classes/zoned_datetime.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/common/ambiguity.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/common/fmt.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/common/math.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/common/mod.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/common/parse.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/common/pattern.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/common/rfc2822.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/common/round.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/common/scalar.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/docstrings.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/lib.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/args.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/base.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/datetime.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/dict.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/exc.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/methods.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/misc.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/mod.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/module.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/num.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/refs.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/string.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/tuple.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/py/types.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/pymodule/def.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/pymodule/mod.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/pymodule/patch.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/pymodule/tzconf.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/pymodule/utils.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/tz/mod.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/tz/posix.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/tz/store.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/tz/sync.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/src/tz/tzif.rs +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/__init__.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/benchmark/__init__.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/common.py +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/tzif/Amsterdam.tzif +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/tzif/Asia/Amman +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/tzif/Asia/NOT_A_TZIF +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/tzif/GMT-13.tzif +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/tzif/Honolulu.tzif +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/tzif/Iceland +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/tzif/Paris_v1.tzif +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/tzif/Sydney_widerange.tzif +0 -0
- {whenever-0.10.0b4 → whenever-0.10.1.dev0}/tests/tzif/UTC.tzif +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: whenever
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.1.dev0
|
|
4
4
|
Summary: Modern datetime library for Python
|
|
5
5
|
Author-email: Arie Bovenberg <a.c.bovenberg@gmail.com>
|
|
6
6
|
Maintainer-email: Arie Bovenberg <a.c.bovenberg@gmail.com>
|
|
@@ -157,6 +157,7 @@ while many serious and long-standing issues remain unaddressed.
|
|
|
157
157
|
- 💎 Thoroughly tested and documented
|
|
158
158
|
- 📆 Support for date arithmetic
|
|
159
159
|
- ⏱️ Nanosecond precision
|
|
160
|
+
- 🗄️ [SQLAlchemy support](https://pypi.org/project/whenever-sqlalchemy/)
|
|
160
161
|
- 🪃 Pydantic support (beta)
|
|
161
162
|
- 🦀 Rust!—but with a [pure-Python option](https://whenever.readthedocs.io/en/latest/faq.html#how-can-i-use-the-pure-python-version)
|
|
162
163
|
- 🧵 Free-threading support (beta)
|
|
@@ -121,6 +121,7 @@ while many serious and long-standing issues remain unaddressed.
|
|
|
121
121
|
- 💎 Thoroughly tested and documented
|
|
122
122
|
- 📆 Support for date arithmetic
|
|
123
123
|
- ⏱️ Nanosecond precision
|
|
124
|
+
- 🗄️ [SQLAlchemy support](https://pypi.org/project/whenever-sqlalchemy/)
|
|
124
125
|
- 🪃 Pydantic support (beta)
|
|
125
126
|
- 🦀 Rust!—but with a [pure-Python option](https://whenever.readthedocs.io/en/latest/faq.html#how-can-i-use-the-pure-python-version)
|
|
126
127
|
- 🧵 Free-threading support (beta)
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# See pyproject.toml for why this file exists.
|
|
2
|
-
from setuptools import build_meta as _orig
|
|
3
|
-
from setuptools.build_meta import *
|
|
4
|
-
import platform
|
|
5
2
|
import os
|
|
3
|
+
import platform
|
|
6
4
|
|
|
5
|
+
from setuptools.build_meta import *
|
|
7
6
|
|
|
8
7
|
if os.getenv("WHENEVER_NO_BUILD_RUST_EXT") or (
|
|
9
8
|
platform.python_implementation() in ("PyPy", "GraalVM")
|
|
@@ -7,7 +7,7 @@ maintainers = [
|
|
|
7
7
|
{name = "Arie Bovenberg", email = "a.c.bovenberg@gmail.com"},
|
|
8
8
|
]
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
version = "0.10.
|
|
10
|
+
version = "0.10.1.dev0"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
description = "Modern datetime library for Python"
|
|
13
13
|
requires-python = ">=3.10"
|
|
@@ -39,6 +39,42 @@ keywords = [
|
|
|
39
39
|
"zoneinfo", "tzdata", "tzdb"
|
|
40
40
|
]
|
|
41
41
|
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
test = [
|
|
44
|
+
"pytest-benchmark[histogram]>=5.2.3,<6",
|
|
45
|
+
"pytest-cov>=7.1.0,<8",
|
|
46
|
+
"pytest-order>=1.4.0,<2",
|
|
47
|
+
"pytest>=9.0.3,<10",
|
|
48
|
+
"hypothesis>=6.152.7,<7",
|
|
49
|
+
]
|
|
50
|
+
typing = [
|
|
51
|
+
"mypy>=2.1.0,<2.2",
|
|
52
|
+
]
|
|
53
|
+
integrations = [
|
|
54
|
+
"time_machine>=3.2,<4; implementation_name == 'cpython'",
|
|
55
|
+
"tzdata>=2020.1",
|
|
56
|
+
"pydantic>=2.10,<3",
|
|
57
|
+
]
|
|
58
|
+
dev-extra = [
|
|
59
|
+
"ruff>=0.15,<0.16",
|
|
60
|
+
"slotscheck>=0.20,<0.21",
|
|
61
|
+
"build",
|
|
62
|
+
"packaging>=26.2",
|
|
63
|
+
"pyperf>=2.10,<3",
|
|
64
|
+
"setuptools-rust",
|
|
65
|
+
"twine>=6.2.0",
|
|
66
|
+
]
|
|
67
|
+
docs = [
|
|
68
|
+
"furo>=2025.12.19,<2026",
|
|
69
|
+
"myst-parser>=5,<6",
|
|
70
|
+
"sphinx-copybutton~=0.5",
|
|
71
|
+
"sphinx-design>=0.7,<0.8",
|
|
72
|
+
"sphinx>=9,<10",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
[tool.uv.dependency-groups]
|
|
76
|
+
docs = {requires-python = ">=3.13"}
|
|
77
|
+
|
|
42
78
|
|
|
43
79
|
[project.urls]
|
|
44
80
|
Documentation = "https://whenever.readthedocs.io"
|
|
@@ -46,26 +82,24 @@ Repository = "https://github.com/ariebovenberg/whenever"
|
|
|
46
82
|
Issues = "https://github.com/ariebovenberg/whenever/issues"
|
|
47
83
|
Changelog = "https://github.com/ariebovenberg/whenever/blob/main/CHANGELOG.md"
|
|
48
84
|
|
|
49
|
-
[tool.
|
|
85
|
+
[tool.ruff]
|
|
50
86
|
line-length = 79
|
|
51
|
-
|
|
52
|
-
exclude =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
87
|
+
target-version = "py310"
|
|
88
|
+
exclude = ["pysrc/whenever/_typing_312.py"]
|
|
89
|
+
|
|
90
|
+
[tool.ruff.lint]
|
|
91
|
+
select = ["E4", "E7", "E9", "F", "I"]
|
|
92
|
+
|
|
93
|
+
[tool.ruff.lint.per-file-ignores]
|
|
94
|
+
"_custom_pybuild/backend.py" = ["F403"]
|
|
95
|
+
"docs/conf.py" = ["E402"]
|
|
96
|
+
"pysrc/whenever/__init__.py" = ["F401", "F403", "F405"]
|
|
97
|
+
"pysrc/whenever/_core.py" = ["F401", "F403", "F405"]
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint.isort]
|
|
100
|
+
combine-as-imports = true
|
|
64
101
|
|
|
65
|
-
[tool.
|
|
66
|
-
profile = 'black'
|
|
67
|
-
line_length = 79
|
|
68
|
-
combine_as_imports = true
|
|
102
|
+
[tool.ruff.format]
|
|
69
103
|
|
|
70
104
|
[tool.mypy]
|
|
71
105
|
warn_unused_ignores = true
|
|
@@ -96,8 +130,9 @@ find = { where = ["pysrc"] }
|
|
|
96
130
|
# We essentially use three build systems:
|
|
97
131
|
# - We use maturin to build the binary distributions for different
|
|
98
132
|
# platforms in CI, since it is very convenient for this purpose.
|
|
99
|
-
# - We
|
|
100
|
-
#
|
|
133
|
+
# - We use setuptools for source distributions and editable installs.
|
|
134
|
+
# - The custom backend below runs `build_rust --inplace` during editable
|
|
135
|
+
# installs so `uv sync` can keep the Rust extension in the source tree.
|
|
101
136
|
# - To actually build the Rust extension with setuptools, we need
|
|
102
137
|
# setuptools_rust.
|
|
103
138
|
[tool.maturin]
|
|
@@ -110,8 +145,8 @@ requires = [
|
|
|
110
145
|
"setuptools",
|
|
111
146
|
"wheel",
|
|
112
147
|
]
|
|
113
|
-
# We need a custom backend to make
|
|
114
|
-
#
|
|
148
|
+
# We need a custom backend to make editable installs build the Rust
|
|
149
|
+
# extension in place.
|
|
115
150
|
# Follows the approach from setuptools.pypa.io/en/latest/build_meta.html#dynamic-build-dependencies-and-other-build-meta-tweaks
|
|
116
151
|
build-backend = "backend"
|
|
117
152
|
backend-path = ["_custom_pybuild"]
|
|
@@ -31,7 +31,7 @@ if not _EXTENSION_LOADED: # pragma: no cover
|
|
|
31
31
|
|
|
32
32
|
# Yes, we could get the version with importlib.metadata,
|
|
33
33
|
# but we try to keep our import time as low as possible.
|
|
34
|
-
__version__ = "0.10.
|
|
34
|
+
__version__ = "0.10.0"
|
|
35
35
|
|
|
36
36
|
# We expose these at module-level for convenience
|
|
37
37
|
MONDAY = Weekday.MONDAY
|
|
@@ -76,7 +76,7 @@ def _parse_digits(s: str, pos: int, count: int) -> tuple[int, int]:
|
|
|
76
76
|
chunk = s[pos:end]
|
|
77
77
|
if not chunk.isdigit():
|
|
78
78
|
raise ValueError(
|
|
79
|
-
f"Expected {count} digits at position {pos},
|
|
79
|
+
f"Expected {count} digits at position {pos}, got {chunk!r}"
|
|
80
80
|
)
|
|
81
81
|
return int(chunk), end
|
|
82
82
|
|
|
@@ -934,9 +934,7 @@ def _validate_cross_fields(elements: Iterable[_Element]) -> None:
|
|
|
934
934
|
# Characters allowed as unquoted literals in patterns.
|
|
935
935
|
# Letters must be quoted. Reserved chars (< > [ ] { } #) raise errors.
|
|
936
936
|
# '.' and ':' are handled separately as potential compound-token prefixes.
|
|
937
|
-
_LITERAL_CHARS = frozenset(
|
|
938
|
-
" \t\n" "0123456789" "-/,;_" "()+@!~*&%$^|\\=?`" '"'
|
|
939
|
-
)
|
|
937
|
+
_LITERAL_CHARS = frozenset(' \t\n0123456789-/,;_()+@!~*&%$^|\\=?`"')
|
|
940
938
|
_PENDING_CHARS = frozenset(".:")
|
|
941
939
|
|
|
942
940
|
_RESERVED_CHARS = frozenset("<>[]{}#")
|
|
@@ -1090,7 +1088,7 @@ def validate_fields(
|
|
|
1090
1088
|
for el in elements:
|
|
1091
1089
|
if isinstance(el, _Field) and el.category not in allowed_categories:
|
|
1092
1090
|
raise ValueError(
|
|
1093
|
-
f"{type_name} does not support pattern
|
|
1091
|
+
f"{type_name} does not support pattern field {el!r}"
|
|
1094
1092
|
)
|
|
1095
1093
|
|
|
1096
1094
|
|
|
@@ -1170,7 +1168,7 @@ def parse_fields(
|
|
|
1170
1168
|
|
|
1171
1169
|
if pos != len(s):
|
|
1172
1170
|
raise ValueError(
|
|
1173
|
-
f"Unexpected trailing text at position {pos}:
|
|
1171
|
+
f"Unexpected trailing text at position {pos}: {s[pos:]!r}"
|
|
1174
1172
|
)
|
|
1175
1173
|
|
|
1176
1174
|
state.resolve()
|
|
@@ -239,7 +239,6 @@ def monthday_from_iso(s: str) -> _date:
|
|
|
239
239
|
# The ISO parsing functions were improved in Python 3.11,
|
|
240
240
|
# so we use them if available.
|
|
241
241
|
if sys.version_info >= (3, 11):
|
|
242
|
-
|
|
243
242
|
_date_from_iso_basic = _date.fromisoformat
|
|
244
243
|
|
|
245
244
|
def _time_from_iso_nofrac(s: str) -> _time:
|
|
@@ -454,7 +454,7 @@ class Date(_Base):
|
|
|
454
454
|
return Date._from_py_unchecked(self._py_date.replace(day=1))
|
|
455
455
|
else:
|
|
456
456
|
raise ValueError(
|
|
457
|
-
f"Invalid unit: {unit!r}.
|
|
457
|
+
f"Invalid unit: {unit!r}. Valid units: 'year', 'month'"
|
|
458
458
|
)
|
|
459
459
|
|
|
460
460
|
def end_of(self, unit: Literal["year", "month"], /) -> Date:
|
|
@@ -479,7 +479,7 @@ class Date(_Base):
|
|
|
479
479
|
)
|
|
480
480
|
else:
|
|
481
481
|
raise ValueError(
|
|
482
|
-
f"Invalid unit: {unit!r}.
|
|
482
|
+
f"Invalid unit: {unit!r}. Valid units: 'year', 'month'"
|
|
483
483
|
)
|
|
484
484
|
|
|
485
485
|
def nth_weekday_of_month(self, n: int, weekday: Weekday, /) -> Date:
|
|
@@ -518,8 +518,8 @@ class Date(_Base):
|
|
|
518
518
|
raise ValueError("n must not be 0")
|
|
519
519
|
if not isinstance(weekday, Weekday):
|
|
520
520
|
raise TypeError("weekday must be a Weekday enum member")
|
|
521
|
-
if not (-
|
|
522
|
-
raise ValueError("n
|
|
521
|
+
if not (-521_722 <= n <= 521_722):
|
|
522
|
+
raise ValueError("n out of range")
|
|
523
523
|
target_dow = weekday.value
|
|
524
524
|
self_dow = self._py_date.isoweekday()
|
|
525
525
|
|
|
@@ -936,10 +936,9 @@ class Date(_Base):
|
|
|
936
936
|
sign,
|
|
937
937
|
)
|
|
938
938
|
|
|
939
|
-
# mypy false positive: 'keywords must be strings' (but they're string literals!)
|
|
940
939
|
return ItemizedDateDelta._from_signed(
|
|
941
940
|
sign if any(results.values()) else 0, **results
|
|
942
|
-
)
|
|
941
|
+
)
|
|
943
942
|
|
|
944
943
|
@overload
|
|
945
944
|
def until(
|
|
@@ -2094,8 +2093,7 @@ class TimeDelta(_Base):
|
|
|
2094
2093
|
sign: Sign = 1 if self._total_ns >= 0 else -1
|
|
2095
2094
|
if not any(result.values()):
|
|
2096
2095
|
sign = 0 # due to rounding, the result may be zero even if self is not zero
|
|
2097
|
-
|
|
2098
|
-
return ItemizedDelta._from_signed(sign, **result) # type: ignore[misc]
|
|
2096
|
+
return ItemizedDelta._from_signed(sign, **result)
|
|
2099
2097
|
|
|
2100
2098
|
def _in_exact_units(
|
|
2101
2099
|
self,
|
|
@@ -3415,8 +3413,7 @@ class ItemizedDelta(_Base, Mapping[DeltaUnitStr, int]):
|
|
|
3415
3413
|
>>> d.format_iso()
|
|
3416
3414
|
'P1W11DT4H1.000012S'
|
|
3417
3415
|
"""
|
|
3418
|
-
|
|
3419
|
-
y, m, w, d, h, s = "ymwdhs" if lowercase_units else "YMWDHS" # type: ignore[misc]
|
|
3416
|
+
y, m, w, d, h, s = "ymwdhs" if lowercase_units else "YMWDHS"
|
|
3420
3417
|
|
|
3421
3418
|
sgn = self.sign()
|
|
3422
3419
|
parts = ["-" * (sgn < 0), "P"]
|
|
@@ -3940,6 +3937,10 @@ class ItemizedDelta(_Base, Mapping[DeltaUnitStr, int]):
|
|
|
3940
3937
|
stacklevel=2,
|
|
3941
3938
|
)
|
|
3942
3939
|
relative_to = relative_to.to_plain().assume_tz("UTC")
|
|
3940
|
+
elif not isinstance(relative_to, ZonedDateTime):
|
|
3941
|
+
raise TypeError(
|
|
3942
|
+
"relative_to must be a ZonedDateTime, PlainDateTime, or OffsetDateTime"
|
|
3943
|
+
)
|
|
3943
3944
|
return relative_to.add(self).since(
|
|
3944
3945
|
relative_to,
|
|
3945
3946
|
in_units=units,
|
|
@@ -3989,6 +3990,10 @@ class ItemizedDelta(_Base, Mapping[DeltaUnitStr, int]):
|
|
|
3989
3990
|
stacklevel=2,
|
|
3990
3991
|
)
|
|
3991
3992
|
relative_to = relative_to.to_plain().assume_tz("UTC")
|
|
3993
|
+
elif not isinstance(relative_to, ZonedDateTime):
|
|
3994
|
+
raise TypeError(
|
|
3995
|
+
"relative_to must be a ZonedDateTime, PlainDateTime, or OffsetDateTime"
|
|
3996
|
+
)
|
|
3992
3997
|
return (relative_to.add(self) - relative_to).total(
|
|
3993
3998
|
unit, relative_to=relative_to
|
|
3994
3999
|
)
|
|
@@ -4293,8 +4298,7 @@ class ItemizedDateDelta(_Base, Mapping[DateDeltaUnitStr, int]):
|
|
|
4293
4298
|
which is not part of the ISO 8601 standard, but is a common extension.
|
|
4294
4299
|
See :ref:`here <iso8601-durations>` for more information.
|
|
4295
4300
|
"""
|
|
4296
|
-
|
|
4297
|
-
y, m, w, d = "ymwd" if lowercase_units else "YMWD" # type: ignore[misc]
|
|
4301
|
+
y, m, w, d = "ymwd" if lowercase_units else "YMWD"
|
|
4298
4302
|
|
|
4299
4303
|
parts = ["-" * (self.sign() < 0), "P"]
|
|
4300
4304
|
if self._years is not None:
|
|
@@ -4801,7 +4805,9 @@ def _check_bound(i: int | None, max_value: int) -> int | None:
|
|
|
4801
4805
|
|
|
4802
4806
|
|
|
4803
4807
|
def _check_component(
|
|
4804
|
-
value: int,
|
|
4808
|
+
value: int,
|
|
4809
|
+
sign: Sign,
|
|
4810
|
+
max_value: int, # may also be UNSET
|
|
4805
4811
|
) -> tuple[int | None, Sign]:
|
|
4806
4812
|
if value is UNSET:
|
|
4807
4813
|
return None, sign
|
|
@@ -5483,7 +5489,6 @@ class _LocalTime(_BasicConversions):
|
|
|
5483
5489
|
# - OffsetDateTime
|
|
5484
5490
|
# (This base class class itself is not for public use.)
|
|
5485
5491
|
class _ExactTime(_BasicConversions):
|
|
5486
|
-
|
|
5487
5492
|
__slots__ = ()
|
|
5488
5493
|
|
|
5489
5494
|
def timestamp(self) -> int:
|
|
@@ -5719,7 +5724,6 @@ class _ExactTime(_BasicConversions):
|
|
|
5719
5724
|
# - OffsetDateTime
|
|
5720
5725
|
# (The class itself it not for public use.)
|
|
5721
5726
|
class _ExactAndLocalTime(_LocalTime, _ExactTime):
|
|
5722
|
-
|
|
5723
5727
|
__slots__ = ()
|
|
5724
5728
|
|
|
5725
5729
|
@property
|
|
@@ -6016,7 +6020,7 @@ class Instant(_ExactTime):
|
|
|
6016
6020
|
state = parse_fields(elements, s)
|
|
6017
6021
|
if state.offset_secs is None:
|
|
6018
6022
|
raise ValueError(
|
|
6019
|
-
"Instant.parse() pattern must include an offset
|
|
6023
|
+
"Instant.parse() pattern must include an offset field (x/X)"
|
|
6020
6024
|
)
|
|
6021
6025
|
if state.year is None or state.month is None or state.day is None:
|
|
6022
6026
|
raise ValueError(
|
|
@@ -6248,7 +6252,7 @@ class Instant(_ExactTime):
|
|
|
6248
6252
|
return hash((self._py_dt, self._nanos))
|
|
6249
6253
|
|
|
6250
6254
|
def __repr__(self) -> str:
|
|
6251
|
-
return f
|
|
6255
|
+
return f'Instant("{str(self).replace("T", " ")}")'
|
|
6252
6256
|
|
|
6253
6257
|
# a custom pickle implementation with a smaller payload
|
|
6254
6258
|
def __reduce__(self) -> tuple[object, ...]:
|
|
@@ -7207,10 +7211,12 @@ class OffsetDateTime(_ExactAndLocalTime):
|
|
|
7207
7211
|
return result
|
|
7208
7212
|
elif offset_mismatch == "raise":
|
|
7209
7213
|
offset_expected = _format_offset(
|
|
7210
|
-
self._py_dt.utcoffset(),
|
|
7214
|
+
self._py_dt.utcoffset(), # type: ignore[arg-type]
|
|
7215
|
+
basic=False,
|
|
7211
7216
|
)
|
|
7212
7217
|
offset_actual = _format_offset(
|
|
7213
|
-
result._py_dt.utcoffset(),
|
|
7218
|
+
result._py_dt.utcoffset(), # type: ignore[arg-type]
|
|
7219
|
+
basic=False,
|
|
7214
7220
|
)
|
|
7215
7221
|
raise InvalidOffsetError(
|
|
7216
7222
|
f"Offset mismatch: timezone {tz!r} has offset {offset_actual}, "
|
|
@@ -7265,7 +7271,7 @@ class OffsetDateTime(_ExactAndLocalTime):
|
|
|
7265
7271
|
return _offset_since(
|
|
7266
7272
|
self,
|
|
7267
7273
|
b,
|
|
7268
|
-
total
|
|
7274
|
+
None if total is UNSET else total,
|
|
7269
7275
|
None if in_units is UNSET else in_units,
|
|
7270
7276
|
round_mode,
|
|
7271
7277
|
round_increment,
|
|
@@ -7305,14 +7311,14 @@ class OffsetDateTime(_ExactAndLocalTime):
|
|
|
7305
7311
|
return _offset_since(
|
|
7306
7312
|
b,
|
|
7307
7313
|
self,
|
|
7308
|
-
total
|
|
7314
|
+
None if total is UNSET else total,
|
|
7309
7315
|
None if in_units is UNSET else in_units,
|
|
7310
7316
|
round_mode,
|
|
7311
7317
|
round_increment,
|
|
7312
7318
|
)
|
|
7313
7319
|
|
|
7314
7320
|
def __repr__(self) -> str:
|
|
7315
|
-
return f
|
|
7321
|
+
return f'OffsetDateTime("{str(self).replace("T", " ")}")'
|
|
7316
7322
|
|
|
7317
7323
|
# a custom pickle implementation with a smaller payload
|
|
7318
7324
|
def __reduce__(self) -> tuple[object, ...]:
|
|
@@ -8106,7 +8112,7 @@ class ZonedDateTime(_ExactAndLocalTime):
|
|
|
8106
8112
|
return _zoned_since(
|
|
8107
8113
|
self,
|
|
8108
8114
|
b,
|
|
8109
|
-
total
|
|
8115
|
+
None if total is UNSET else total,
|
|
8110
8116
|
None if in_units is UNSET else in_units,
|
|
8111
8117
|
round_mode,
|
|
8112
8118
|
round_increment,
|
|
@@ -8146,7 +8152,7 @@ class ZonedDateTime(_ExactAndLocalTime):
|
|
|
8146
8152
|
return _zoned_since(
|
|
8147
8153
|
b,
|
|
8148
8154
|
self,
|
|
8149
|
-
total
|
|
8155
|
+
None if total is UNSET else total,
|
|
8150
8156
|
None if in_units is UNSET else in_units,
|
|
8151
8157
|
round_mode,
|
|
8152
8158
|
round_increment,
|
|
@@ -8457,7 +8463,7 @@ class ZonedDateTime(_ExactAndLocalTime):
|
|
|
8457
8463
|
f'ZonedDateTime("{_format_date(self._py_dt, False)} '
|
|
8458
8464
|
f"{_format_time(self._py_dt, self._nanos, 'auto', False)}"
|
|
8459
8465
|
f"{_format_offset(self._py_dt.utcoffset(), False)}" # type: ignore[arg-type]
|
|
8460
|
-
f
|
|
8466
|
+
f'[{self._tz.key or "<system timezone without ID>"}]")'
|
|
8461
8467
|
)
|
|
8462
8468
|
|
|
8463
8469
|
# a custom pickle implementation with a smaller payload
|
|
@@ -8977,7 +8983,7 @@ class PlainDateTime(_LocalTime):
|
|
|
8977
8983
|
return _plain_since(
|
|
8978
8984
|
self,
|
|
8979
8985
|
b,
|
|
8980
|
-
total
|
|
8986
|
+
None if total is UNSET else total,
|
|
8981
8987
|
None if in_units is UNSET else in_units,
|
|
8982
8988
|
round_mode,
|
|
8983
8989
|
round_increment,
|
|
@@ -9021,7 +9027,7 @@ class PlainDateTime(_LocalTime):
|
|
|
9021
9027
|
return _plain_since(
|
|
9022
9028
|
b,
|
|
9023
9029
|
self,
|
|
9024
|
-
total
|
|
9030
|
+
None if total is UNSET else total,
|
|
9025
9031
|
None if in_units is UNSET else in_units,
|
|
9026
9032
|
round_mode,
|
|
9027
9033
|
round_increment,
|
|
@@ -9335,7 +9341,7 @@ class PlainDateTime(_LocalTime):
|
|
|
9335
9341
|
return self.date()._add_days(next_day).at(rounded_time)
|
|
9336
9342
|
|
|
9337
9343
|
def __repr__(self) -> str:
|
|
9338
|
-
return f
|
|
9344
|
+
return f'PlainDateTime("{str(self).replace("T", " ")}")'
|
|
9339
9345
|
|
|
9340
9346
|
# a custom pickle implementation with a smaller payload
|
|
9341
9347
|
def __reduce__(self) -> tuple[object, ...]:
|
|
@@ -9925,8 +9931,7 @@ def _plain_since(
|
|
|
9925
9931
|
sign,
|
|
9926
9932
|
)
|
|
9927
9933
|
|
|
9928
|
-
|
|
9929
|
-
return ItemizedDelta._from_signed( # type: ignore[misc]
|
|
9934
|
+
return ItemizedDelta._from_signed(
|
|
9930
9935
|
sign if any(result.values()) else 0, **result
|
|
9931
9936
|
)
|
|
9932
9937
|
|
|
@@ -9995,7 +10000,7 @@ def _offset_since(
|
|
|
9995
10000
|
round_increment=effective_increment,
|
|
9996
10001
|
round_mode=effective_round_mode,
|
|
9997
10002
|
)
|
|
9998
|
-
return ItemizedDelta._from_signed(
|
|
10003
|
+
return ItemizedDelta._from_signed(
|
|
9999
10004
|
sign if any(result.values()) else 0, **result
|
|
10000
10005
|
)
|
|
10001
10006
|
|
|
@@ -10088,8 +10093,7 @@ def _zoned_since(
|
|
|
10088
10093
|
sign,
|
|
10089
10094
|
)
|
|
10090
10095
|
|
|
10091
|
-
|
|
10092
|
-
return ItemizedDelta._from_signed( # type: ignore[misc]
|
|
10096
|
+
return ItemizedDelta._from_signed(
|
|
10093
10097
|
sign if any(result.values()) else 0, **result
|
|
10094
10098
|
)
|
|
10095
10099
|
|
|
@@ -37,17 +37,16 @@ def _nth_weekday_of_month(year: int, month: int, n: int, weekday: int) -> int:
|
|
|
37
37
|
``weekday`` is ISO weekday (1=Mon, 7=Sun).
|
|
38
38
|
Returns the day of month, or raises ValueError if it doesn't exist.
|
|
39
39
|
"""
|
|
40
|
+
dim = days_in_month(year, month)
|
|
40
41
|
if n > 0:
|
|
41
42
|
first_dow = _date(year, month, 1).isoweekday()
|
|
42
43
|
offset = (weekday - first_dow) % 7
|
|
43
44
|
day = 1 + offset + (n - 1) * 7
|
|
44
45
|
else:
|
|
45
|
-
dim = days_in_month(year, month)
|
|
46
46
|
last_dow = _date(year, month, dim).isoweekday()
|
|
47
47
|
offset = (last_dow - weekday) % 7
|
|
48
48
|
day = dim - offset + (n + 1) * 7
|
|
49
49
|
|
|
50
|
-
dim = days_in_month(year, month)
|
|
51
50
|
if day < 1 or day > dim:
|
|
52
51
|
raise ValueError(f"Weekday #{n} doesn't exist in {year}-{month:02d}")
|
|
53
52
|
return day
|
|
@@ -228,7 +228,9 @@ def available_timezones() -> set[str]:
|
|
|
228
228
|
for base in TZPATH:
|
|
229
229
|
zones.update(_find_all_tznames(base))
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
# special files that shouldn't be included
|
|
232
|
+
zones.discard("posixrules")
|
|
233
|
+
zones.discard("localtime")
|
|
232
234
|
return zones
|
|
233
235
|
|
|
234
236
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: whenever
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.1.dev0
|
|
4
4
|
Summary: Modern datetime library for Python
|
|
5
5
|
Author-email: Arie Bovenberg <a.c.bovenberg@gmail.com>
|
|
6
6
|
Maintainer-email: Arie Bovenberg <a.c.bovenberg@gmail.com>
|
|
@@ -157,6 +157,7 @@ while many serious and long-standing issues remain unaddressed.
|
|
|
157
157
|
- 💎 Thoroughly tested and documented
|
|
158
158
|
- 📆 Support for date arithmetic
|
|
159
159
|
- ⏱️ Nanosecond precision
|
|
160
|
+
- 🗄️ [SQLAlchemy support](https://pypi.org/project/whenever-sqlalchemy/)
|
|
160
161
|
- 🪃 Pydantic support (beta)
|
|
161
162
|
- 🦀 Rust!—but with a [pure-Python option](https://whenever.readthedocs.io/en/latest/faq.html#how-can-i-use-the-pure-python-version)
|
|
162
163
|
- 🧵 Free-threading support (beta)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
|
-
|
|
3
2
|
import platform
|
|
3
|
+
|
|
4
4
|
from setuptools import setup
|
|
5
5
|
|
|
6
6
|
_SKIP_BUILD_SUGGESTION = """
|
|
@@ -16,10 +16,9 @@ building the Rust extension and use the (slower) Python version instead.
|
|
|
16
16
|
|
|
17
17
|
extra_setup_kwargs = {}
|
|
18
18
|
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
):
|
|
19
|
+
if os.getenv(
|
|
20
|
+
"WHENEVER_NO_BUILD_RUST_EXT"
|
|
21
|
+
) or platform.python_implementation() in ("PyPy", "GraalVM"):
|
|
23
22
|
print("Skipping Whenever Rust extension build")
|
|
24
23
|
else:
|
|
25
24
|
from setuptools_rust import Binding, RustExtension, build_rust
|
|
@@ -591,8 +591,8 @@ fn nth_weekday(cls: HeapType<Date>, slf: Date, args: &[PyObj]) -> PyReturn {
|
|
|
591
591
|
.to_i64()?;
|
|
592
592
|
if raw == 0 {
|
|
593
593
|
raise_value_err("n must not be 0")?
|
|
594
|
-
} else if !(-
|
|
595
|
-
raise_value_err("n
|
|
594
|
+
} else if !(-521_722..=521_722).contains(&raw) {
|
|
595
|
+
raise_value_err("n out of range")?
|
|
596
596
|
}
|
|
597
597
|
// SAFETY: we just checked that it's well within range
|
|
598
598
|
raw as i32
|