whenever 0.10.1.dev0__tar.gz → 0.10.2__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.
Files changed (123) hide show
  1. {whenever-0.10.1.dev0 → whenever-0.10.2}/Cargo.lock +4 -14
  2. {whenever-0.10.1.dev0 → whenever-0.10.2}/Cargo.toml +4 -5
  3. {whenever-0.10.1.dev0 → whenever-0.10.2}/PKG-INFO +1 -1
  4. {whenever-0.10.1.dev0 → whenever-0.10.2}/pyproject.toml +10 -2
  5. whenever-0.10.2/pysrc/whenever/__init__.py +214 -0
  6. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/__init__.pyi +78 -7
  7. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_common.py +1 -1
  8. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_core.py +8 -28
  9. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_format.py +3 -0
  10. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_parse.py +10 -2
  11. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_pywhenever.py +285 -110
  12. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_shared.py +29 -0
  13. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_tz/__init__.py +2 -0
  14. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_tz/ambiguity.py +14 -14
  15. whenever-0.10.2/pysrc/whenever/_tz/common.py +75 -0
  16. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_tz/posix.py +11 -5
  17. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_tz/store.py +5 -0
  18. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_tz/tzif.py +25 -13
  19. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_utils.py +5 -25
  20. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever.egg-info/PKG-INFO +1 -1
  21. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever.egg-info/SOURCES.txt +2 -1
  22. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/date.rs +181 -181
  23. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/date_delta.rs +39 -65
  24. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/datetime_delta.rs +49 -47
  25. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/instant.rs +57 -90
  26. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/itemized_date_delta.rs +81 -133
  27. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/itemized_delta.rs +107 -186
  28. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/offset_datetime.rs +142 -220
  29. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/plain_datetime.rs +213 -403
  30. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/time.rs +152 -89
  31. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/time_delta.rs +103 -158
  32. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/zoned_datetime.rs +593 -867
  33. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/common/ambiguity.rs +15 -22
  34. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/common/fmt.rs +26 -63
  35. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/common/math.rs +28 -60
  36. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/common/mod.rs +1 -0
  37. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/common/round.rs +44 -125
  38. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/common/scalar.rs +0 -20
  39. whenever-0.10.2/src/common/sync.rs +300 -0
  40. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/docstrings.rs +29 -40
  41. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/base.rs +73 -34
  42. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/datetime.rs +48 -8
  43. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/exc.rs +78 -18
  44. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/methods.rs +20 -11
  45. whenever-0.10.2/src/py/misc.rs +194 -0
  46. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/num.rs +63 -26
  47. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/refs.rs +22 -7
  48. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/string.rs +10 -15
  49. whenever-0.10.2/src/py/tuple.rs +97 -0
  50. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/types.rs +101 -37
  51. whenever-0.10.2/src/pymodule/def.rs +720 -0
  52. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/pymodule/patch.rs +9 -14
  53. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/pymodule/tzconf.rs +6 -3
  54. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/pymodule/utils.rs +11 -12
  55. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/tz/mod.rs +0 -1
  56. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/tz/posix.rs +150 -47
  57. whenever-0.10.2/src/tz/store.rs +378 -0
  58. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/tz/tzif.rs +256 -64
  59. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_date.py +119 -0
  60. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_format_parse.py +38 -0
  61. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_misc.py +94 -0
  62. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_offset_datetime.py +126 -6
  63. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_plain_datetime.py +104 -6
  64. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_tz_posix.py +54 -37
  65. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_tz_tzif.py +157 -61
  66. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_zoned_datetime.py +304 -38
  67. whenever-0.10.2/tests/tzif/Lord_Howe.tzif +0 -0
  68. whenever-0.10.1.dev0/pysrc/whenever/__init__.py +0 -60
  69. whenever-0.10.1.dev0/pysrc/whenever/_tz/common.py +0 -53
  70. whenever-0.10.1.dev0/src/py/misc.rs +0 -166
  71. whenever-0.10.1.dev0/src/py/tuple.rs +0 -141
  72. whenever-0.10.1.dev0/src/pymodule/def.rs +0 -840
  73. whenever-0.10.1.dev0/src/tz/store.rs +0 -639
  74. whenever-0.10.1.dev0/src/tz/sync.rs +0 -291
  75. {whenever-0.10.1.dev0 → whenever-0.10.2}/.cargo/config.toml +0 -0
  76. {whenever-0.10.1.dev0 → whenever-0.10.2}/LICENSE +0 -0
  77. {whenever-0.10.1.dev0 → whenever-0.10.2}/MANIFEST.in +0 -0
  78. {whenever-0.10.1.dev0 → whenever-0.10.2}/README.md +0 -0
  79. {whenever-0.10.1.dev0 → whenever-0.10.2}/_custom_pybuild/backend.py +0 -0
  80. {whenever-0.10.1.dev0 → whenever-0.10.2}/build.rs +0 -0
  81. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_math.py +0 -0
  82. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_typing.py +0 -0
  83. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_typing_312.py +0 -0
  84. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/_tz/system.py +0 -0
  85. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever/py.typed +0 -0
  86. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever.egg-info/dependency_links.txt +0 -0
  87. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever.egg-info/requires.txt +0 -0
  88. {whenever-0.10.1.dev0 → whenever-0.10.2}/pysrc/whenever.egg-info/top_level.txt +0 -0
  89. {whenever-0.10.1.dev0 → whenever-0.10.2}/setup.cfg +0 -0
  90. {whenever-0.10.1.dev0 → whenever-0.10.2}/setup.py +0 -0
  91. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/classes/mod.rs +0 -0
  92. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/common/parse.rs +0 -0
  93. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/common/pattern.rs +0 -0
  94. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/common/rfc2822.rs +0 -0
  95. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/lib.rs +0 -0
  96. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/args.rs +0 -0
  97. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/dict.rs +0 -0
  98. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/mod.rs +0 -0
  99. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/py/module.rs +0 -0
  100. {whenever-0.10.1.dev0 → whenever-0.10.2}/src/pymodule/mod.rs +0 -0
  101. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/__init__.py +0 -0
  102. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/benchmark/__init__.py +0 -0
  103. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/common.py +0 -0
  104. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_date_delta.py +0 -0
  105. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_datetime_delta.py +0 -0
  106. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_instant.py +0 -0
  107. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_iso_week_date.py +0 -0
  108. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_itemized_date_delta.py +0 -0
  109. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_itemized_delta.py +0 -0
  110. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_math.py +0 -0
  111. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_month_day.py +0 -0
  112. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_time.py +0 -0
  113. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_time_delta.py +0 -0
  114. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/test_year_month.py +0 -0
  115. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/tzif/Amsterdam.tzif +0 -0
  116. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/tzif/Asia/Amman +0 -0
  117. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/tzif/Asia/NOT_A_TZIF +0 -0
  118. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/tzif/GMT-13.tzif +0 -0
  119. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/tzif/Honolulu.tzif +0 -0
  120. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/tzif/Iceland +0 -0
  121. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/tzif/Paris_v1.tzif +0 -0
  122. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/tzif/Sydney_widerange.tzif +0 -0
  123. {whenever-0.10.1.dev0 → whenever-0.10.2}/tests/tzif/UTC.tzif +0 -0
@@ -339,33 +339,23 @@ dependencies = [
339
339
 
340
340
  [[package]]
341
341
  name = "pyo3-build-config"
342
- version = "0.28.2"
342
+ version = "0.29.0"
343
343
  source = "registry+https://github.com/rust-lang/crates.io-index"
344
- checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7"
344
+ checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
345
345
  dependencies = [
346
- "python3-dll-a",
347
346
  "target-lexicon",
348
347
  ]
349
348
 
350
349
  [[package]]
351
350
  name = "pyo3-ffi"
352
- version = "0.28.2"
351
+ version = "0.29.0"
353
352
  source = "registry+https://github.com/rust-lang/crates.io-index"
354
- checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc"
353
+ checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
355
354
  dependencies = [
356
355
  "libc",
357
356
  "pyo3-build-config",
358
357
  ]
359
358
 
360
- [[package]]
361
- name = "python3-dll-a"
362
- version = "0.2.14"
363
- source = "registry+https://github.com/rust-lang/crates.io-index"
364
- checksum = "d381ef313ae70b4da5f95f8a4de773c6aa5cd28f73adec4b4a31df70b66780d8"
365
- dependencies = [
366
- "cc",
367
- ]
368
-
369
359
  [[package]]
370
360
  name = "quote"
371
361
  version = "1.0.45"
@@ -24,19 +24,18 @@ crate-type = ["cdylib", "rlib"]
24
24
  lto = "fat"
25
25
  codegen-units = 1
26
26
  strip = true
27
- # FUTURE: consider `opt-level = "s"`.
28
- # It reduces binary size by about 10%, at about 10% slower performance
27
+ opt-level = 3
29
28
 
30
29
  [dependencies]
31
30
  ahash = "^0.8.12"
32
31
 
33
32
  [dependencies.pyo3-ffi]
34
- version = "^0.28.2"
33
+ version = "^0.29.0"
35
34
  default-features = false
36
35
  features = ["extension-module", "generate-import-lib"]
37
36
 
38
37
  [build-dependencies]
39
- pyo3-build-config = { version = "^0.28.2" }
38
+ pyo3-build-config = { version = "^0.29.0" }
40
39
 
41
40
  [dev-dependencies]
42
41
  criterion = "^0.8.1"
@@ -48,4 +47,4 @@ harness = false
48
47
  path = "benchmarks/rust/main.rs"
49
48
 
50
49
  [lints.rust]
51
- unexpected_cfgs = { level = "warn", check-cfg = [ "cfg(Py_3_9)", "cfg(Py_3_10)", "cfg(Py_3_11)", "cfg(Py_3_12)", "cfg(Py_3_13)", "cfg(Py_GIL_DISABLED)"] }
50
+ unexpected_cfgs = { level = "warn", check-cfg = [ "cfg(Py_3_9)", "cfg(Py_3_10)", "cfg(Py_3_11)", "cfg(Py_3_12)", "cfg(Py_3_13)", "cfg(Py_GIL_DISABLED)", "cfg(PyPy)"] }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: whenever
3
- Version: 0.10.1.dev0
3
+ Version: 0.10.2
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>
@@ -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.1.dev0"
10
+ version = "0.10.2"
11
11
  license = "MIT"
12
12
  description = "Modern datetime library for Python"
13
13
  requires-python = ">=3.10"
@@ -60,10 +60,17 @@ dev-extra = [
60
60
  "slotscheck>=0.20,<0.21",
61
61
  "build",
62
62
  "packaging>=26.2",
63
- "pyperf>=2.10,<3",
64
63
  "setuptools-rust",
65
64
  "twine>=6.2.0",
66
65
  ]
66
+ benchmark = [
67
+ "arrow>=1.4,<2",
68
+ "matplotlib>=3.10,<4",
69
+ "pendulum>=3.2,<4",
70
+ "pyperf>=2.10,<3",
71
+ "python-dateutil>=2.9,<3",
72
+ "setuptools-rust",
73
+ ]
67
74
  docs = [
68
75
  "furo>=2025.12.19,<2026",
69
76
  "myst-parser>=5,<6",
@@ -73,6 +80,7 @@ docs = [
73
80
  ]
74
81
 
75
82
  [tool.uv.dependency-groups]
83
+ benchmark = {requires-python = ">=3.14"}
76
84
  docs = {requires-python = ">=3.13"}
77
85
 
78
86
 
@@ -0,0 +1,214 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ # Almost everything is lazily imported, to speed up initial import time.
5
+ def __getattr__(name: str) -> object:
6
+ # When any name from a group is first accessed, the whole module is loaded
7
+ # and all names from it are pre-populated, so subsequent accesses skip __getattr__.
8
+ if src := _LAZY_NAMES.get(name):
9
+ mod = __import__(src, fromlist=("",))
10
+ g = globals()
11
+ for n in _LAZY_MODULES[src]:
12
+ g[n] = getattr(mod, n)
13
+ return g[name]
14
+ # TZPATH is a live view, not a cached value.
15
+ elif name == "TZPATH":
16
+ from ._core import _get_tzpath
17
+
18
+ return _get_tzpath()
19
+ elif name == "AnyDelta":
20
+ from ._core import (
21
+ DateDelta,
22
+ DateTimeDelta,
23
+ ItemizedDateDelta,
24
+ ItemizedDelta,
25
+ TimeDelta,
26
+ )
27
+
28
+ globals()["AnyDelta"] = val = (
29
+ DateDelta
30
+ | TimeDelta
31
+ | DateTimeDelta
32
+ | ItemizedDelta
33
+ | ItemizedDateDelta
34
+ )
35
+ return val
36
+ elif name == "__version__":
37
+ globals()["__version__"] = version = __import__(
38
+ "importlib.metadata"
39
+ ).metadata.version(__name__)
40
+ return version
41
+
42
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
43
+
44
+
45
+ # Ensures not-yet-imported names are still included in dir() output
46
+ def __dir__() -> list[str]:
47
+ return sorted(
48
+ (
49
+ globals().keys()
50
+ | _LAZY_NAMES.keys()
51
+ | {"TZPATH", "AnyDelta", "__version__"}
52
+ )
53
+ - {"_LAZY_MODULES", "_LAZY_NAMES"}
54
+ )
55
+
56
+
57
+ # This could be derived from the imports below, but it's easier for static
58
+ # analysis and IDEs if it's statically defined.
59
+ __all__ = (
60
+ # Date and time
61
+ "Date",
62
+ "YearMonth",
63
+ "MonthDay",
64
+ "IsoWeekDate",
65
+ "Time",
66
+ "Instant",
67
+ "OffsetDateTime",
68
+ "ZonedDateTime",
69
+ "PlainDateTime",
70
+ # Deltas and time units
71
+ "DateDelta",
72
+ "TimeDelta",
73
+ "DateTimeDelta",
74
+ "ItemizedDelta",
75
+ "ItemizedDateDelta",
76
+ "years",
77
+ "months",
78
+ "weeks",
79
+ "days",
80
+ "hours",
81
+ "minutes",
82
+ "seconds",
83
+ "milliseconds",
84
+ "microseconds",
85
+ "nanoseconds",
86
+ # Exceptions/warnings
87
+ "DaysAssumed24HoursWarning",
88
+ "StaleOffsetWarning",
89
+ "NaiveArithmeticWarning",
90
+ "PotentialDstBugWarning",
91
+ "WheneverDeprecationWarning",
92
+ "SkippedTime",
93
+ "RepeatedTime",
94
+ "InvalidOffsetError",
95
+ "ImplicitlyIgnoringDST",
96
+ "TimeZoneNotFoundError",
97
+ # Enums/constants
98
+ "Weekday",
99
+ "MONDAY",
100
+ "TUESDAY",
101
+ "WEDNESDAY",
102
+ "THURSDAY",
103
+ "FRIDAY",
104
+ "SATURDAY",
105
+ "SUNDAY",
106
+ # Other
107
+ "reset_system_tz",
108
+ "patch_current_time",
109
+ "reset_tzpath",
110
+ "clear_tzcache",
111
+ "available_timezones",
112
+ "AnyDelta",
113
+ )
114
+
115
+
116
+ _LAZY_MODULES = {
117
+ f"{__package__}._core": (
118
+ # Classes
119
+ "Date",
120
+ "Time",
121
+ "Instant",
122
+ "OffsetDateTime",
123
+ "ZonedDateTime",
124
+ "PlainDateTime",
125
+ "DateDelta",
126
+ "TimeDelta",
127
+ "DateTimeDelta",
128
+ "ItemizedDelta",
129
+ "ItemizedDateDelta",
130
+ # Unit constructors
131
+ "years",
132
+ "months",
133
+ "weeks",
134
+ "days",
135
+ "hours",
136
+ "minutes",
137
+ "seconds",
138
+ "milliseconds",
139
+ "microseconds",
140
+ "nanoseconds",
141
+ # Exceptions/warnings
142
+ "DaysAssumed24HoursWarning",
143
+ "StaleOffsetWarning",
144
+ "NaiveArithmeticWarning",
145
+ "PotentialDstBugWarning",
146
+ "WheneverDeprecationWarning",
147
+ "SkippedTime",
148
+ "RepeatedTime",
149
+ "InvalidOffsetError",
150
+ "ImplicitlyIgnoringDST",
151
+ "TimeZoneNotFoundError",
152
+ # Other
153
+ "reset_system_tz",
154
+ "_EXTENSION_LOADED",
155
+ # Unpickle functions
156
+ "_unpkl_date",
157
+ "_unpkl_ddelta",
158
+ "_unpkl_dtdelta",
159
+ "_unpkl_iddelta",
160
+ "_unpkl_idelta",
161
+ "_unpkl_inst",
162
+ "_unpkl_local",
163
+ "_unpkl_offset",
164
+ "_unpkl_tdelta",
165
+ "_unpkl_time",
166
+ "_unpkl_utc",
167
+ "_unpkl_zoned",
168
+ ),
169
+ f"{__package__}._utils": (
170
+ "patch_current_time",
171
+ "reset_tzpath",
172
+ "clear_tzcache",
173
+ "available_timezones",
174
+ ),
175
+ f"{__package__}._typing": (
176
+ "RoundModeStr",
177
+ "DeltaUnitStr",
178
+ "DateDeltaUnitStr",
179
+ "ExactDeltaUnitStr",
180
+ "DisambiguateStr",
181
+ "OffsetMismatchStr",
182
+ ),
183
+ f"{__package__}._shared": (
184
+ "YearMonth",
185
+ "MonthDay",
186
+ "Weekday",
187
+ "IsoWeekDate",
188
+ "_unpkl_iwd",
189
+ "_unpkl_md",
190
+ "_unpkl_ym",
191
+ "MONDAY",
192
+ "TUESDAY",
193
+ "WEDNESDAY",
194
+ "THURSDAY",
195
+ "FRIDAY",
196
+ "SATURDAY",
197
+ "SUNDAY",
198
+ ),
199
+ }
200
+ _LAZY_NAMES = {n: mod for mod, names in _LAZY_MODULES.items() for n in names}
201
+
202
+
203
+ # Without this, IDEs won't show proper information for our types.
204
+ # Note we don't actually import the variable from `typing`,
205
+ # as this has a runtime cost.
206
+ TYPE_CHECKING = False
207
+
208
+ if TYPE_CHECKING:
209
+ from ._core import *
210
+ from ._shared import *
211
+ from ._typing import *
212
+ from ._utils import *
213
+
214
+ del TYPE_CHECKING
@@ -74,9 +74,22 @@ __all__ = [
74
74
  "InvalidOffsetError",
75
75
  "ImplicitlyIgnoringDST",
76
76
  "TimeZoneNotFoundError",
77
- # Other stuff
77
+ # Enums/constants
78
78
  "Weekday",
79
+ "MONDAY",
80
+ "TUESDAY",
81
+ "WEDNESDAY",
82
+ "THURSDAY",
83
+ "FRIDAY",
84
+ "SATURDAY",
85
+ "SUNDAY",
86
+ # Other
79
87
  "reset_system_tz",
88
+ "patch_current_time",
89
+ "reset_tzpath",
90
+ "clear_tzcache",
91
+ "available_timezones",
92
+ "AnyDelta",
80
93
  ]
81
94
 
82
95
  _EXTENSION_LOADED: bool
@@ -175,8 +188,26 @@ class Date(_DateOrTimeMixin):
175
188
  def prev_day(self) -> Date: ...
176
189
  def nth_weekday_of_month(self, n: int, weekday: Weekday, /) -> Date: ...
177
190
  def nth_weekday(self, n: int, weekday: Weekday, /) -> Date: ...
178
- def start_of(self, unit: Literal["year", "month"], /) -> Date: ...
179
- def end_of(self, unit: Literal["year", "month"], /) -> Date: ...
191
+ def start_of(
192
+ self,
193
+ unit: Literal[
194
+ "year",
195
+ "month",
196
+ "week_mon",
197
+ "week_sun",
198
+ ],
199
+ /,
200
+ ) -> Date: ...
201
+ def end_of(
202
+ self,
203
+ unit: Literal[
204
+ "year",
205
+ "month",
206
+ "week_mon",
207
+ "week_sun",
208
+ ],
209
+ /,
210
+ ) -> Date: ...
180
211
  def at(self, t: Time, /) -> PlainDateTime: ...
181
212
  def to_stdlib(self) -> _date: ...
182
213
  @deprecated("Use to_stdlib() instead")
@@ -1187,12 +1218,30 @@ class _LocalTime(ABC):
1187
1218
  def in_leap_year(self) -> bool: ...
1188
1219
  def start_of(
1189
1220
  self,
1190
- unit: Literal["year", "month", "day", "hour", "minute", "second"],
1221
+ unit: Literal[
1222
+ "year",
1223
+ "month",
1224
+ "week_mon",
1225
+ "week_sun",
1226
+ "day",
1227
+ "hour",
1228
+ "minute",
1229
+ "second",
1230
+ ],
1191
1231
  /,
1192
1232
  ) -> Self: ...
1193
1233
  def end_of(
1194
1234
  self,
1195
- unit: Literal["year", "month", "day", "hour", "minute", "second"],
1235
+ unit: Literal[
1236
+ "year",
1237
+ "month",
1238
+ "week_mon",
1239
+ "week_sun",
1240
+ "day",
1241
+ "hour",
1242
+ "minute",
1243
+ "second",
1244
+ ],
1196
1245
  /,
1197
1246
  ) -> Self: ...
1198
1247
  @overload
@@ -1617,14 +1666,32 @@ class OffsetDateTime(_PyDateTimeMixin, _ExactAndLocalTime):
1617
1666
  ) -> Self: ...
1618
1667
  def start_of(
1619
1668
  self,
1620
- unit: Literal["year", "month", "day", "hour", "minute", "second"],
1669
+ unit: Literal[
1670
+ "year",
1671
+ "month",
1672
+ "week_mon",
1673
+ "week_sun",
1674
+ "day",
1675
+ "hour",
1676
+ "minute",
1677
+ "second",
1678
+ ],
1621
1679
  /,
1622
1680
  *,
1623
1681
  stale_offset_ok: bool = ...,
1624
1682
  ) -> Self: ...
1625
1683
  def end_of(
1626
1684
  self,
1627
- unit: Literal["year", "month", "day", "hour", "minute", "second"],
1685
+ unit: Literal[
1686
+ "year",
1687
+ "month",
1688
+ "week_mon",
1689
+ "week_sun",
1690
+ "day",
1691
+ "hour",
1692
+ "minute",
1693
+ "second",
1694
+ ],
1628
1695
  /,
1629
1696
  *,
1630
1697
  stale_offset_ok: bool = ...,
@@ -2241,3 +2308,7 @@ class DaysAssumed24HoursWarning(PotentialDstBugWarning): ...
2241
2308
  class StaleOffsetWarning(PotentialDstBugWarning): ...
2242
2309
  class NaiveArithmeticWarning(PotentialDstBugWarning): ...
2243
2310
  class WheneverDeprecationWarning(UserWarning): ...
2311
+
2312
+ AnyDelta: TypeAlias = (
2313
+ DateDelta | TimeDelta | DateTimeDelta | ItemizedDelta | ItemizedDateDelta
2314
+ )
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable
4
4
 
5
- # Unused imports are necessary for sphinc autodoc due to
5
+ # Unused imports are necessary for sphinx autodoc due to
6
6
  # scoping issues introduced by add_alternate_constructors().
7
7
  from datetime import ( # noqa: F401
8
8
  date as _date,
@@ -6,6 +6,7 @@ try: # pragma: no cover
6
6
  from ._whenever import (
7
7
  _clear_tz_cache as _clear_tz_cache,
8
8
  _clear_tz_cache_by_keys as _clear_tz_cache_by_keys,
9
+ _get_tzpath as _get_tzpath,
9
10
  _patch_time_frozen as _patch_time_frozen,
10
11
  _patch_time_keep_ticking as _patch_time_keep_ticking,
11
12
  _set_tzpath as _set_tzpath,
@@ -13,6 +14,8 @@ try: # pragma: no cover
13
14
  _unpkl_date,
14
15
  _unpkl_ddelta,
15
16
  _unpkl_dtdelta,
17
+ _unpkl_iddelta,
18
+ _unpkl_idelta,
16
19
  _unpkl_inst,
17
20
  _unpkl_local,
18
21
  _unpkl_offset,
@@ -22,24 +25,6 @@ try: # pragma: no cover
22
25
  _unpkl_zoned,
23
26
  )
24
27
 
25
- # Backfill symbols not yet in the Rust extension (temporary)
26
- try:
27
- from ._whenever import _unpkl_idelta as _unpkl_idelta
28
- except ImportError:
29
- from ._pywhenever import _unpkl_idelta as _unpkl_idelta
30
- try:
31
- from ._whenever import _unpkl_iddelta as _unpkl_iddelta
32
- except ImportError:
33
- from ._pywhenever import _unpkl_iddelta as _unpkl_iddelta
34
- try:
35
- from ._whenever import ItemizedDelta as ItemizedDelta
36
- except ImportError:
37
- from ._pywhenever import ItemizedDelta as ItemizedDelta
38
- try:
39
- from ._whenever import ItemizedDateDelta as ItemizedDateDelta
40
- except ImportError:
41
- from ._pywhenever import ItemizedDateDelta as ItemizedDateDelta
42
-
43
28
  _EXTENSION_LOADED = True
44
29
 
45
30
  except ModuleNotFoundError as e:
@@ -50,6 +35,7 @@ except ModuleNotFoundError as e:
50
35
  from ._pywhenever import (
51
36
  _clear_tz_cache,
52
37
  _clear_tz_cache_by_keys,
38
+ _get_tzpath,
53
39
  _patch_time_frozen,
54
40
  _patch_time_keep_ticking,
55
41
  _set_tzpath,
@@ -70,14 +56,8 @@ except ModuleNotFoundError as e:
70
56
 
71
57
  _EXTENSION_LOADED = False
72
58
 
73
- # YearMonth/MonthDay/IsoWeekDate unpickle functions always come from _shared
74
- from ._shared import _unpkl_iwd, _unpkl_md, _unpkl_ym
75
- from ._typing import *
59
+ # In pure Python mode, populate TZPATH eagerly (the Rust extension defers
60
+ # this to first timezone lookup for faster import time).
61
+ from ._utils import reset_tzpath
76
62
 
77
- MONDAY = Weekday.MONDAY
78
- TUESDAY = Weekday.TUESDAY
79
- WEDNESDAY = Weekday.WEDNESDAY
80
- THURSDAY = Weekday.THURSDAY
81
- FRIDAY = Weekday.FRIDAY
82
- SATURDAY = Weekday.SATURDAY
83
- SUNDAY = Weekday.SUNDAY
63
+ reset_tzpath()
@@ -617,6 +617,9 @@ class _FracTrim(_Field):
617
617
  return [_DotFrac(self.width)]
618
618
  return [_Literal(ch), self]
619
619
 
620
+ def __repr__(self) -> str:
621
+ return "F" * self.width
622
+
620
623
 
621
624
  class _DotFrac(_Field):
622
625
  """Decimal point + trimmed fractional seconds (``.FFF``).
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import sys
2
4
  from datetime import (
3
5
  date as _date,
@@ -6,7 +8,7 @@ from datetime import (
6
8
  timedelta as _timedelta,
7
9
  timezone as _timezone,
8
10
  )
9
- from typing import Literal, NoReturn, cast
11
+ from typing import TYPE_CHECKING, Literal, NoReturn, cast
10
12
 
11
13
  from ._common import (
12
14
  DUMMY_LEAP_YEAR,
@@ -15,7 +17,9 @@ from ._common import (
15
17
  check_utc_bounds,
16
18
  mk_fixed_tzinfo,
17
19
  )
18
- from ._tz import SafeTzId, TimeZone, get_tz, resolve_ambiguity, validate_tzid
20
+
21
+ if TYPE_CHECKING:
22
+ from ._tz import SafeTzId, TimeZone
19
23
 
20
24
 
21
25
  class InvalidOffsetError(ValueError):
@@ -122,6 +126,8 @@ def offset_dt_from_iso(s: str) -> tuple[_datetime, Nanos]:
122
126
 
123
127
 
124
128
  def zdt_from_iso(s: str) -> tuple[_datetime, Nanos, TimeZone]:
129
+ from ._tz import get_tz, resolve_ambiguity
130
+
125
131
  if len(s) < 11 or "W" in s[:11] or not s.isascii():
126
132
  _parse_err(s)
127
133
 
@@ -180,6 +186,8 @@ def _time_offset_tz_from_iso(
180
186
  ) -> tuple[_time, Nanos, _timezone | Literal["Z"] | None, SafeTzId | None]:
181
187
  # ditch the bracketted timezone (if present)
182
188
  if s.endswith("]"):
189
+ from ._tz import validate_tzid
190
+
183
191
  # NOTE: sorry for the unicode escape sequences. Literal brackets
184
192
  # break my LSP's indentation detection. \x5b is open bracket '['
185
193
  s, tz_raw = s[:-1].rsplit("\x5b", 1)