infdate 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl

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.
infdate/__init__.py CHANGED
@@ -6,10 +6,15 @@ capable of representing positive and negative infinity
6
6
  """
7
7
 
8
8
  from datetime import date, datetime
9
- from math import inf
9
+ from math import inf, trunc
10
10
  from typing import final, overload, Any, Final, TypeVar
11
11
 
12
12
 
13
+ # -----------------------------------------------------------------------------
14
+ # Public module constants:
15
+ # display definitions, format strings, upper and loer ordinal boundaries
16
+ # -----------------------------------------------------------------------------
17
+
13
18
  INFINITE_DATE_DISPLAY: Final[str] = "<inf>"
14
19
  NEGATIVE_INFINITE_DATE_DISPLAY: Final[str] = "<-inf>"
15
20
 
@@ -23,22 +28,34 @@ ISO_DATETIME_FORMAT_UTC: Final[str] = f"{ISO_DATE_FORMAT}T%H:%M:%S.%fZ"
23
28
  MIN_ORDINAL: Final[int] = date.min.toordinal()
24
29
  MAX_ORDINAL: Final[int] = date.max.toordinal()
25
30
 
31
+ RESOLUTION: Final[int] = 1
32
+
33
+
34
+ # -----------------------------------------------------------------------------
35
+ # Internal module constants:
36
+ # TypeVar for GenericDate
37
+ # -----------------------------------------------------------------------------
26
38
 
27
39
  _GD = TypeVar("_GD", bound="GenericDate")
28
40
 
29
41
 
42
+ # -----------------------------------------------------------------------------
43
+ # Classes
44
+ # -----------------------------------------------------------------------------
45
+
46
+
30
47
  class GenericDate:
31
48
  """Base Date object derived from an ordinal"""
32
49
 
33
- # pylint: disable=invalid-name
34
- resolution: Final = 1
35
- # pylint: enable=invalid-name
36
-
37
- def __init__(self, ordinal: float, /) -> None:
50
+ def __init__(self, ordinal: int | float, /) -> None:
38
51
  """Create a date-like object"""
39
- self.__ordinal = ordinal
52
+ if ordinal in (-inf, inf):
53
+ self.__ordinal = ordinal
54
+ else:
55
+ self.__ordinal = trunc(ordinal)
56
+ #
40
57
 
41
- def toordinal(self: _GD) -> float:
58
+ def toordinal(self: _GD) -> int | float:
42
59
  """to ordinal (almost like date.toordinal())"""
43
60
  return self.__ordinal
44
61
 
@@ -94,24 +111,30 @@ class GenericDate:
94
111
  return self
95
112
  #
96
113
  # Return a RealDate instance if possible
97
- return fromordinal(self.__ordinal + delta)
114
+ return fromordinal(self.__ordinal + trunc(delta))
98
115
 
99
116
  def __add__(self: _GD, delta: int | float, /) -> _GD:
100
117
  """gd_instance1 + number capability"""
101
118
  return self._add_days(delta)
102
119
 
120
+ __radd__ = __add__
121
+
103
122
  @overload
104
123
  def __sub__(self: _GD, other: int | float, /) -> _GD: ...
105
124
  @overload
106
- def __sub__(self: _GD, other: _GD, /) -> int | float: ...
125
+ def __sub__(self: _GD, other: _GD | date, /) -> int | float: ...
107
126
  @final
108
- def __sub__(self: _GD, other: _GD | int | float, /) -> _GD | int | float:
127
+ def __sub__(self: _GD, other: _GD | date | int | float, /) -> _GD | int | float:
109
128
  """subtract other, respecting possibly nondeterministic values"""
110
129
  if isinstance(other, (int, float)):
111
130
  return self._add_days(-other)
112
131
  #
113
132
  return self.__ordinal - other.toordinal()
114
133
 
134
+ def __rsub__(self: _GD, other: _GD | date, /) -> int | float:
135
+ """subtract from other, respecting possibly nondeterministic values"""
136
+ return other.toordinal() - self.__ordinal
137
+
115
138
  def __repr__(self: _GD, /) -> str:
116
139
  """String representation of the object"""
117
140
  return f"{self.__class__.__name__}({repr(self.__ordinal)})"
@@ -171,7 +194,7 @@ class RealDate(GenericDate):
171
194
  self.year = year
172
195
  self.month = month
173
196
  self.day = day
174
- super().__init__(float(self._wrapped_date_object.toordinal()))
197
+ super().__init__(self._wrapped_date_object.toordinal())
175
198
  self.timetuple = self._wrapped_date_object.timetuple
176
199
  self.weekday = self._wrapped_date_object.weekday
177
200
  self.isoweekday = self._wrapped_date_object.isoweekday
@@ -204,12 +227,18 @@ class RealDate(GenericDate):
204
227
  )
205
228
 
206
229
 
207
- # Absolute minimum and maximum dates
230
+ # -----------------------------------------------------------------------------
231
+ # Public module constants continued:
232
+ # absolute minimum and maximum dates
233
+ # -----------------------------------------------------------------------------
234
+
208
235
  MIN: Final[GenericDate] = InfinityDate(past_bound=True)
209
236
  MAX: Final[GenericDate] = InfinityDate(past_bound=False)
210
237
 
211
238
 
212
- # Factory functions
239
+ # -----------------------------------------------------------------------------
240
+ # Module-level factory functions
241
+ # -----------------------------------------------------------------------------
213
242
 
214
243
 
215
244
  def from_datetime_object(source: date | datetime, /) -> GenericDate:
@@ -241,7 +270,19 @@ def from_native_type(
241
270
  raise ValueError(f"Don’t know how to convert {source!r} into a date")
242
271
 
243
272
 
244
- def fromordinal(ordinal: float | int) -> GenericDate:
273
+ def fromtimestamp(timestamp: float) -> GenericDate:
274
+ """Create an InfinityDate or RealDate instance from the provided timestamp"""
275
+ if timestamp == -inf:
276
+ return MIN
277
+ #
278
+ if timestamp == inf:
279
+ return MAX
280
+ #
281
+ stdlib_date_object = date.fromtimestamp(timestamp)
282
+ return from_datetime_object(stdlib_date_object)
283
+
284
+
285
+ def fromordinal(ordinal: int | float) -> GenericDate:
245
286
  """Create an InfinityDate or RealDate instance from the provided ordinal"""
246
287
  if ordinal == -inf:
247
288
  return MIN
@@ -249,11 +290,7 @@ def fromordinal(ordinal: float | int) -> GenericDate:
249
290
  if ordinal == inf:
250
291
  return MAX
251
292
  #
252
- try:
253
- new_ordinal = int(ordinal)
254
- except ValueError as error:
255
- raise ValueError(f"Cannot convert {ordinal!r} to integer") from error
256
- #
293
+ new_ordinal = trunc(ordinal)
257
294
  if not MIN_ORDINAL <= new_ordinal <= MAX_ORDINAL:
258
295
  raise OverflowError("RealDate value out of range")
259
296
  #
@@ -261,6 +298,20 @@ def fromordinal(ordinal: float | int) -> GenericDate:
261
298
  return from_datetime_object(stdlib_date_object)
262
299
 
263
300
 
301
+ # -----------------------------------------------------------------------------
302
+ # Public module constants continued:
303
+ # minimum and maximum real dates
304
+ # -----------------------------------------------------------------------------
305
+
306
+ REAL_MIN: Final[GenericDate] = fromordinal(MIN_ORDINAL)
307
+ REAL_MAX: Final[GenericDate] = fromordinal(MAX_ORDINAL)
308
+
309
+
310
+ # -----------------------------------------------------------------------------
311
+ # Module-level factory functions continued
312
+ # -----------------------------------------------------------------------------
313
+
314
+
264
315
  def fromisoformat(source: str, /) -> GenericDate:
265
316
  """Create an InfinityDate or RealDate instance from an iso format representation"""
266
317
  lower_source_stripped = source.strip().lower()
@@ -281,8 +332,3 @@ def fromisocalendar(year: int, week: int, weekday: int) -> GenericDate:
281
332
  def today() -> GenericDate:
282
333
  """Today as RealDate object"""
283
334
  return from_datetime_object(date.today())
284
-
285
-
286
- # Minimum and maximum real dates
287
- # setattr(RealDate, "min", fromordinal(MIN_ORDINAL))
288
- # setattr(RealDate, "max", fromordinal(MAX_ORDINAL))
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: infdate
3
+ Version: 0.2.2
4
+ Summary: Date object wrapper supporting infinity
5
+ Project-URL: Homepage, https://gitlab.com/blackstream-x/infdate
6
+ Project-URL: CI, https://gitlab.com/blackstream-x/infdate/-/pipelines
7
+ Project-URL: Bug Tracker, https://gitlab.com/blackstream-x/infdate/-/issues
8
+ Project-URL: Repository, https://gitlab.com/blackstream-x/infdate.git
9
+ Author-email: Rainer Schwarzbach <rainer@blackstream.de>
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+
26
+ # infdate
27
+
28
+ _Python module for date calculations implementing a concept of infinity_
29
+
30
+ ## Module description
31
+
32
+ ### Classes overview
33
+
34
+ └── GenericDate
35
+ ├── InfinityDate
36
+ └── RealDate
37
+
38
+
39
+ The base class **GenericDate** should not be instantiated
40
+ but can be used as a type annotation. In fact, it should be preferred
41
+ over the other classes in typing annotations.
42
+
43
+ **InfinityDate** can represent either positive or negative infinity.
44
+ The module-level constants **MIN** and **MAX** contain the two possible
45
+ **InfinityDate** instance variations.
46
+
47
+ **RealDate** instances represent real dates like the standard library’s
48
+ **[datetime.date]** class, with mostly equal or similar semantics.
49
+ The module -level constants **REAL_MIN** and **REAL_MAX** are the eqivalents
50
+ of **datetime.date.min** and **datetime.date.max** as **RealDate** instances.
51
+
52
+ For any valid **RealDate** instance, the following is **True**:
53
+
54
+ ``` python
55
+ infdate.MIN < infdate.REAL_MIN <= real_date_instance <= infdate.REAL_MAX < infdate.MAX
56
+ ```
57
+
58
+ ### Module-level constants
59
+
60
+ * **MIN** = **InfinityDate(**_past_bound_=`True`**)** → the beginning of time
61
+ * **MAX** = **InfinityDate(**_past_bound_=`False`**)** → the end of time
62
+ * **MIN_ORDINAL** = `1` → same as **datetime.date.min.toordinal()**
63
+ * **MAX_ORDINAL** = `3652059` → same as **datetime.date.max.toordinal()**
64
+ * **REAL_MIN** = **fromordinal(MIN_ORDINAL)**
65
+ → represents _0001-01-01_, the same date as **datetime.date.min**
66
+ * **REAL_MAX** = **fromordinal(MAX_ORDINAL)**
67
+ → represents _9999-12-31_, the same date as **datetime.date.max**
68
+ * **RESOLUTION** = `1` → represents the lowest possible date difference: _one day_
69
+
70
+
71
+ ### Module-level factory functions
72
+
73
+ The following factory methods from the **datetime.date** class
74
+ are provided as module-level functions:
75
+
76
+ * **fromtimestamp()** (also accepting **-math.inf** or **math.inf**)
77
+ * **fromordinal()** (also accepting **-math.inf** or **math.inf**)
78
+ * **fromisoformat()**
79
+ * **fromisocalendar()**
80
+ * **today()**
81
+
82
+ Two additional factory functions are provided:
83
+
84
+ * **from_datetime_object()** to create a **RealDate** instance from a
85
+ **datetime.date** or **datetime.datetime** instance
86
+
87
+ * **from_native_type()** to create an **InfinityDate** or **RealDate**
88
+ instance from a string, from **None**, **-math.inf** or **math.inf**.
89
+
90
+ This can come handy when dealing with API representations of dates,
91
+ eg. in GitLab’s [Personal Access Tokens API].
92
+
93
+
94
+ ### Differences between infdate classes and datetime.date
95
+
96
+ Some notable difference from the **datetime.date** class, mainly due to the design decision to express date differences in pure numbers (ie. **float** because **math.inf** also is a float):
97
+
98
+ * infdate classes have no **max**, **min** or **resolution** attributes,
99
+ but there are [module-level constants] serving the same purpose.
100
+
101
+ * The **.toordinal()** method returns **int**, **math.inf**, or **-math.inf**.
102
+
103
+ * Subtracting a date from an **InfinityDate** or **RealDate** always returns
104
+ an **int**, **math.inf**, or **-math.inf** instead of a **datetime.timedelta** instance.
105
+
106
+ * Likewise, you cannot add or subtract **datetime.timedelta** instances
107
+ from an **InfinityDate** or **RealDate**, only **float** or **int**
108
+ (support for adding and subtracting datetime.timedelta instances might be added in the future, [see the feature request]).
109
+
110
+
111
+ ## Example usage
112
+
113
+ ``` pycon
114
+ >>> import infdate
115
+ >>> today = infdate.today()
116
+ >>> today
117
+ RealDate(2025, 6, 27)
118
+ >>> print(f"US date notation: {today:%m/%d/%y}")
119
+ US date notation: 06/27/25
120
+ >>> today.ctime()
121
+ 'Fri Jun 27 00:00:00 2025'
122
+ >>> today.isocalendar()
123
+ datetime.IsoCalendarDate(year=2025, week=26, weekday=5)
124
+ >>>
125
+ >>> yesterday = today - 1
126
+ >>> yesterday.ctime()
127
+ 'Thu Jun 26 00:00:00 2025'
128
+ >>>
129
+ >>> today - yesterday
130
+ 1
131
+ >>> infdate.MIN
132
+ InfinityDate(past_bound=True)
133
+ >>> infdate.MAX
134
+ InfinityDate(past_bound=False)
135
+ >>> infdate.MAX - today
136
+ inf
137
+ >>> infdate.MAX - infdate.MIN
138
+ inf
139
+ ```
140
+
141
+ **InfinityDate** and **RealDate** instances can be compared with each other, and also with **datetime.date** instances.
142
+
143
+ Subtracting **InfinityDate** or **RealDate** and **datetime.date** instances from each other also works:
144
+
145
+ ``` pycon
146
+ >>> from datetime import date
147
+ >>> stdlib_today = date.today()
148
+ >>> today == stdlib_today
149
+ True
150
+ >>> yesterday < stdlib_today
151
+ True
152
+ >>> yesterday - stdlib_today
153
+ -1
154
+ >>> stdlib_today - yesterday
155
+ 1
156
+ >>> stdlib_today - infdate.MIN
157
+ inf
158
+ ```
159
+
160
+
161
+ * * *
162
+ [datetime.date]: https://docs.python.org/3/library/datetime.html#date-objects
163
+ [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
164
+ [module-level constants]: #module-level-constants
165
+ [see the feature request]: https://gitlab.com/blackstream-x/infdate/-/issues/6
@@ -0,0 +1,6 @@
1
+ infdate/__init__.py,sha256=5hjONWaY3mtCaXUQz2YN3qm6R84p6eV5bjTwW8JcvFU,11360
2
+ infdate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ infdate-0.2.2.dist-info/METADATA,sha256=LBnq5cSaqv4eIom0ZC0NTMV0jNiAziuRyLqEY-Rhvls,5904
4
+ infdate-0.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ infdate-0.2.2.dist-info/licenses/LICENSE,sha256=867pxriiObx28vCU1JsRtu3H9kUKyl54e0-xl1IIv3Y,913
6
+ infdate-0.2.2.dist-info/RECORD,,
@@ -1,141 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: infdate
3
- Version: 0.2.0
4
- Summary: Date object wrapper supporting infinity
5
- Project-URL: Homepage, https://gitlab.com/blackstream-x/infdate
6
- Project-URL: CI, https://gitlab.com/blackstream-x/infdate/-/pipelines
7
- Project-URL: Bug Tracker, https://gitlab.com/blackstream-x/infdate/-/issues
8
- Project-URL: Repository, https://gitlab.com/blackstream-x/infdate.git
9
- Author-email: Rainer Schwarzbach <rainer@blackstream.de>
10
- License-File: LICENSE
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Operating System :: OS Independent
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3 :: Only
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: 3.13
20
- Classifier: Programming Language :: Python :: 3.14
21
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
- Requires-Python: >=3.11
23
- Description-Content-Type: text/markdown
24
-
25
- # infdate
26
-
27
- _Python module for date calculations implementing a concept of infinity_
28
-
29
- ## Module description
30
-
31
- Class hierarchy:
32
-
33
- └── GenericDate
34
- ├── InfinityDate
35
- └── RealDate
36
-
37
-
38
-
39
- **InfinityDate** can represent either positive or negative infinity.
40
- The module-level constants **MIN** and **MAX** contain the two possible
41
- **InfinityDate** instance variations.
42
-
43
- **RealDate** instances represent real dates like the standard library’s
44
- **datetime.date** class, with mostly equal or similöar semantics.
45
-
46
- For any valid **RealDate** instance, the following is **True**:
47
-
48
- ``` python
49
- infdate.MIN < infdate.RealDate(1, 1, 1) <= real_date_instance <= infdate.RealDate(9999, 12, 31) < infdate.MAX
50
- ```
51
-
52
- The following factory methods from the **datetime.date** class
53
- are provided as module-level functions:
54
-
55
- * **fromordinal()** (also accepting **-math.inf** or **math.inf**)
56
- * **fromisoformat()**
57
- * **fromisocalendar()**
58
- * **today()**
59
-
60
- **fromtimestamp()** is still missing by mistake.
61
-
62
- Two additional factory functions are provided in the module:
63
-
64
- * **from_datetime_object()** to create a **RealDate** instance from a
65
- **datetime.date** or **datetime.datetime** instance
66
-
67
- * **from_native_type()** to create an **InfinityDate** or **RealDate**
68
- instance from a string, from **None**, **-math.inf** or **math.inf**.
69
-
70
- This can come handy when dealing with API representations of dates,
71
- eg. in GitLab’s [Personal Access Tokens API].
72
-
73
- Some notable difference from the **datetime.date** class:
74
-
75
- * The **.toordinal()** method returns **float** instead of **int**
76
-
77
- * The **resolution** attribute is **1.0** instead of **datetime.timedelta(days=1)**
78
- but also represents exactly one day.
79
-
80
- * Subtracting a date from an **InfinityDate** or **RealDate** always returns a float
81
- (because **math.inf** is a float), not a **datetime.timedelta** instance.
82
-
83
- * Likewise, you cannot add or subtract **datetime.timedelta** instances
84
- from n **InfinityDate** or **RealDate**, only **loat** or **int**.
85
-
86
-
87
- ## Example usage
88
-
89
- ``` pycon
90
- >>> import infdate
91
- >>> today = infdate.today()
92
- >>> today
93
- RealDate(2025, 6, 25)
94
- >>> print(today)
95
- 2025-06-25
96
- >>> print(f"US date notation {today:%m/%d/%y}")
97
- US date notation 06/25/25
98
- >>> today.ctime()
99
- 'Wed Jun 25 00:00:00 2025'
100
- >>> today.isocalendar()
101
- datetime.IsoCalendarDate(year=2025, week=26, weekday=3)
102
- >>>
103
- >>> yesterday = today - 1
104
- >>> yesterday.ctime()
105
- 'Tue Jun 24 00:00:00 2025'
106
- >>>
107
- >>> today - yesterday
108
- 1.0
109
- >>> infdate.MIN
110
- InfinityDate(past_bound=True)
111
- >>> infdate.MAX
112
- InfinityDate(past_bound=False)
113
- >>> infdate.MAX - today
114
- inf
115
- >>> infdate.MAX - infdate.MIN
116
- inf
117
- ```
118
-
119
- You can compare **InfinityDate**, **RealDate** and **datetime.date** instances,
120
- and subtract them from each other (although currently, `__rsub__` is not implemented yet,
121
- so subtracting an **InfinityDate** or **RealDate** from a **datetime.date**
122
- still gives a **TypeError**):
123
-
124
- >>> from datetime import date
125
- >>> stdlib_today = date.today()
126
- >>> today == stdlib_today
127
- True
128
- >>> yesterday < stdlib_today
129
- True
130
- >>> yesterday - stdlib_today
131
- -1.0
132
- >>> stdlib_today - yesterday
133
- Traceback (most recent call last):
134
- File "<python-input-22>", line 1, in <module>
135
- stdlib_today - yesterday
136
- ~~~~~~~~~~~~~^~~~~~~~~~~
137
- TypeError: unsupported operand type(s) for -: 'datetime.date' and 'RealDate'
138
-
139
-
140
- * * *
141
- [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
@@ -1,6 +0,0 @@
1
- infdate/__init__.py,sha256=wBa4EXdkOXSSsX3QpBwx8jIpz3s6UETUoJnTlnqRNhc,9422
2
- infdate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- infdate-0.2.0.dist-info/METADATA,sha256=_g58qFMeECVrjNn0Z_t3Bggox3GHYXqY9NGVwA75ld4,4573
4
- infdate-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
- infdate-0.2.0.dist-info/licenses/LICENSE,sha256=867pxriiObx28vCU1JsRtu3H9kUKyl54e0-xl1IIv3Y,913
6
- infdate-0.2.0.dist-info/RECORD,,