infdate 0.2.1__py3-none-any.whl → 0.2.3__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
@@ -1,40 +1,37 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  """
4
- infdate: a wrapper around standard library’s datetime.date objects,
5
- capable of representing positive and negative infinity
4
+ infdate: a wrapper around the standard library’s datetime.date objects,
5
+ capable of representing past and future infinity
6
6
  """
7
7
 
8
+ import warnings
9
+
8
10
  from datetime import date, datetime
9
- from math import inf
11
+ from math import inf, trunc
10
12
  from typing import final, overload, Any, Final, TypeVar
11
13
 
12
14
 
13
15
  # -----------------------------------------------------------------------------
14
- # Public module constants:
15
- # display definitions, format strings, upper and loer ordinal boundaries
16
+ # Internal module constants:
17
+ # TypeVar for GenericDate
16
18
  # -----------------------------------------------------------------------------
17
19
 
18
- INFINITE_DATE_DISPLAY: Final[str] = "<inf>"
19
- NEGATIVE_INFINITE_DATE_DISPLAY: Final[str] = "<-inf>"
20
-
21
- INFINITY_SYMBOL: Final[str] = "∝"
22
- UP_TO_SYMBOL: Final[str] = "⤒"
23
- FROM_ON_SYMBOL: Final[str] = "↥"
24
-
25
- ISO_DATE_FORMAT: Final[str] = "%Y-%m-%d"
26
- ISO_DATETIME_FORMAT_UTC: Final[str] = f"{ISO_DATE_FORMAT}T%H:%M:%S.%fZ"
20
+ _GD = TypeVar("_GD", bound="GenericDate")
27
21
 
28
- MIN_ORDINAL: Final[int] = date.min.toordinal()
29
- MAX_ORDINAL: Final[int] = date.max.toordinal()
22
+ _INFINITY_FORMS = (-inf, inf)
30
23
 
31
24
 
32
25
  # -----------------------------------------------------------------------------
33
- # Internal module constants:
34
- # TypeVar for GenericDate
26
+ # Public module constants:
27
+ # format strings
35
28
  # -----------------------------------------------------------------------------
36
29
 
37
- _GD = TypeVar("_GD", bound="GenericDate")
30
+ INFINITE_PAST_DATE_DISPLAY: Final[str] = "<-inf>"
31
+ INFINITE_FUTURE_DATE_DISPLAY: Final[str] = "<inf>"
32
+
33
+ ISO_DATE_FORMAT: Final[str] = "%Y-%m-%d"
34
+ ISO_DATETIME_FORMAT_UTC: Final[str] = f"{ISO_DATE_FORMAT}T%H:%M:%S.%fZ"
38
35
 
39
36
 
40
37
  # -----------------------------------------------------------------------------
@@ -45,15 +42,15 @@ _GD = TypeVar("_GD", bound="GenericDate")
45
42
  class GenericDate:
46
43
  """Base Date object derived from an ordinal"""
47
44
 
48
- # pylint: disable=invalid-name
49
- resolution: Final = 1
50
- # pylint: enable=invalid-name
51
-
52
- def __init__(self, ordinal: float, /) -> None:
45
+ def __init__(self, ordinal: int | float, /) -> None:
53
46
  """Create a date-like object"""
54
- self.__ordinal = ordinal
47
+ if ordinal in _INFINITY_FORMS:
48
+ self.__ordinal = ordinal
49
+ else:
50
+ self.__ordinal = trunc(ordinal)
51
+ #
55
52
 
56
- def toordinal(self: _GD) -> float:
53
+ def toordinal(self: _GD) -> int | float:
57
54
  """to ordinal (almost like date.toordinal())"""
58
55
  return self.__ordinal
59
56
 
@@ -90,12 +87,14 @@ class GenericDate:
90
87
  return hash(f"date with ordinal {self.__ordinal}")
91
88
 
92
89
  def _add_days(self: _GD, delta: int | float, /):
93
- """Add other, respecting maybe-nondeterministic values"""
90
+ """Add other, respecting maybe-nondeterministic values
91
+ (ie. -inf or inf)
92
+ """
94
93
  # Check for infinity in either self or delta,
95
94
  # and return a matching InfinityDate if found.
96
95
  # Re-use existing objects if possible.
97
96
  for observed_item in (delta, self.__ordinal):
98
- for infinity_form in (inf, -inf):
97
+ for infinity_form in _INFINITY_FORMS:
99
98
  if observed_item == infinity_form:
100
99
  if observed_item == self.__ordinal:
101
100
  return self
@@ -109,24 +108,30 @@ class GenericDate:
109
108
  return self
110
109
  #
111
110
  # Return a RealDate instance if possible
112
- return fromordinal(self.__ordinal + delta)
111
+ return fromordinal(self.__ordinal + trunc(delta))
113
112
 
114
113
  def __add__(self: _GD, delta: int | float, /) -> _GD:
115
114
  """gd_instance1 + number capability"""
116
115
  return self._add_days(delta)
117
116
 
117
+ __radd__ = __add__
118
+
118
119
  @overload
119
120
  def __sub__(self: _GD, other: int | float, /) -> _GD: ...
120
121
  @overload
121
- def __sub__(self: _GD, other: _GD, /) -> int | float: ...
122
+ def __sub__(self: _GD, other: _GD | date, /) -> int | float: ...
122
123
  @final
123
- def __sub__(self: _GD, other: _GD | int | float, /) -> _GD | int | float:
124
+ def __sub__(self: _GD, other: _GD | date | int | float, /) -> _GD | int | float:
124
125
  """subtract other, respecting possibly nondeterministic values"""
125
126
  if isinstance(other, (int, float)):
126
127
  return self._add_days(-other)
127
128
  #
128
129
  return self.__ordinal - other.toordinal()
129
130
 
131
+ def __rsub__(self: _GD, other: _GD | date, /) -> int | float:
132
+ """subtract from other, respecting possibly nondeterministic values"""
133
+ return other.toordinal() - self.__ordinal
134
+
130
135
  def __repr__(self: _GD, /) -> str:
131
136
  """String representation of the object"""
132
137
  return f"{self.__class__.__name__}({repr(self.__ordinal)})"
@@ -147,6 +152,29 @@ class GenericDate:
147
152
  """Return a copy with year, month, and/or date replaced"""
148
153
  raise NotImplementedError
149
154
 
155
+ @overload
156
+ def pretty(
157
+ self: _GD,
158
+ /,
159
+ *,
160
+ inf_common_prefix: str,
161
+ inf_past_suffix: str,
162
+ inf_future_suffix: str,
163
+ ) -> str: ...
164
+ @overload
165
+ def pretty(self: _GD, /, *, fmt: str) -> str: ...
166
+ def pretty(
167
+ self: _GD,
168
+ /,
169
+ *,
170
+ inf_common_prefix: str = "",
171
+ inf_past_suffix: str = "",
172
+ inf_future_suffix: str = "",
173
+ fmt: str = ISO_DATE_FORMAT,
174
+ ) -> str:
175
+ """Return the date, pretty printed"""
176
+ raise NotImplementedError
177
+
150
178
 
151
179
  class InfinityDate(GenericDate):
152
180
  """Infinity Date object"""
@@ -154,6 +182,7 @@ class InfinityDate(GenericDate):
154
182
  def __init__(self, /, *, past_bound: bool = False) -> None:
155
183
  """Store -inf or inf"""
156
184
  ordinal = -inf if past_bound else inf
185
+ self.__is_past = past_bound
157
186
  super().__init__(ordinal)
158
187
 
159
188
  def __repr__(self, /) -> str:
@@ -162,10 +191,10 @@ class InfinityDate(GenericDate):
162
191
 
163
192
  def strftime(self, fmt: str, /) -> str:
164
193
  """String representation of the date"""
165
- if self.toordinal() == inf:
166
- return INFINITE_DATE_DISPLAY
194
+ if self.__is_past:
195
+ return INFINITE_PAST_DATE_DISPLAY
167
196
  #
168
- return NEGATIVE_INFINITE_DATE_DISPLAY
197
+ return INFINITE_FUTURE_DATE_DISPLAY
169
198
 
170
199
  __format__ = strftime
171
200
 
@@ -175,6 +204,25 @@ class InfinityDate(GenericDate):
175
204
  f"{self.__class__.__name__} instances do not support .replace()"
176
205
  )
177
206
 
207
+ @final
208
+ def pretty(
209
+ self,
210
+ /,
211
+ *,
212
+ inf_common_prefix: str = "",
213
+ inf_past_suffix: str = "",
214
+ inf_future_suffix: str = "",
215
+ # pylint:disable = unused-argument ; required by inheritance
216
+ fmt: str = ISO_DATE_FORMAT,
217
+ ) -> str:
218
+ """Return the date, pretty printed"""
219
+ if self.__is_past:
220
+ pretty_result = f"{inf_common_prefix}{inf_past_suffix}"
221
+ else:
222
+ pretty_result = f"{inf_common_prefix}{inf_future_suffix}"
223
+ #
224
+ return pretty_result or self.isoformat()
225
+
178
226
 
179
227
  # pylint: disable=too-many-instance-attributes
180
228
  class RealDate(GenericDate):
@@ -186,7 +234,7 @@ class RealDate(GenericDate):
186
234
  self.year = year
187
235
  self.month = month
188
236
  self.day = day
189
- super().__init__(float(self._wrapped_date_object.toordinal()))
237
+ super().__init__(self._wrapped_date_object.toordinal())
190
238
  self.timetuple = self._wrapped_date_object.timetuple
191
239
  self.weekday = self._wrapped_date_object.weekday
192
240
  self.isoweekday = self._wrapped_date_object.isoweekday
@@ -218,29 +266,78 @@ class RealDate(GenericDate):
218
266
  )
219
267
  )
220
268
 
269
+ @final
270
+ def pretty(
271
+ self,
272
+ /,
273
+ *,
274
+ inf_common_prefix: str = "",
275
+ inf_past_suffix: str = "",
276
+ inf_future_suffix: str = "",
277
+ fmt: str = ISO_DATE_FORMAT,
278
+ ) -> str:
279
+ """Return the date, pretty printed"""
280
+ return self.strftime(fmt)
281
+
282
+
283
+ # -----------------------------------------------------------------------------
284
+ # Private module functions
285
+ # -----------------------------------------------------------------------------
286
+
287
+
288
+ def _from_datetime_object(source: date | datetime, /) -> GenericDate:
289
+ """Create a new RealDate instance from a
290
+ date or datetime object (module-private function)
291
+ """
292
+ return RealDate(source.year, source.month, source.day)
293
+
221
294
 
222
295
  # -----------------------------------------------------------------------------
223
296
  # Public module constants continued:
224
- # absolute minimum and maximum dates
297
+ # minimum and maximum dates and ordinals, resolution (one day)
225
298
  # -----------------------------------------------------------------------------
226
299
 
227
- MIN: Final[GenericDate] = InfinityDate(past_bound=True)
228
- MAX: Final[GenericDate] = InfinityDate(past_bound=False)
300
+
301
+ INFINITE_PAST: Final[GenericDate] = InfinityDate(past_bound=True)
302
+ INFINITE_FUTURE: Final[GenericDate] = InfinityDate(past_bound=False)
303
+
304
+ MIN: Final[GenericDate] = INFINITE_PAST
305
+ MAX: Final[GenericDate] = INFINITE_FUTURE
306
+
307
+ REAL_MIN: Final[GenericDate] = _from_datetime_object(date.min)
308
+ REAL_MAX: Final[GenericDate] = _from_datetime_object(date.max)
309
+
310
+ MIN_ORDINAL: Final[int] = date.min.toordinal()
311
+ MAX_ORDINAL: Final[int] = date.max.toordinal()
312
+
313
+ RESOLUTION: Final[int] = 1
229
314
 
230
315
 
231
316
  # -----------------------------------------------------------------------------
232
- # Module-level factory functions
317
+ # Public module-level factory functions
233
318
  # -----------------------------------------------------------------------------
234
319
 
235
320
 
236
- def from_datetime_object(source: date | datetime, /) -> GenericDate:
321
+ def fromdatetime(source: date | datetime, /) -> GenericDate:
237
322
  """Create a new RealDate instance from a
238
323
  date or datetime object
239
324
  """
240
- return RealDate(source.year, source.month, source.day)
325
+ return _from_datetime_object(source)
241
326
 
242
327
 
243
- def from_native_type(
328
+ def from_datetime_object(source: date | datetime, /) -> GenericDate:
329
+ """Create a new RealDate instance from a
330
+ date or datetime object (deprecated function name)
331
+ """
332
+ warnings.warn(
333
+ "outdated function name, please use fromdatetime() instead",
334
+ DeprecationWarning,
335
+ stacklevel=2,
336
+ )
337
+ return fromdatetime(source)
338
+
339
+
340
+ def fromnative(
244
341
  source: Any,
245
342
  /,
246
343
  *,
@@ -251,7 +348,7 @@ def from_native_type(
251
348
  assuming infinity in the latter case
252
349
  """
253
350
  if isinstance(source, str):
254
- return from_datetime_object(datetime.strptime(source, fmt))
351
+ return _from_datetime_object(datetime.strptime(source, fmt))
255
352
  #
256
353
  if source == -inf or source is None and past_bound:
257
354
  return MIN
@@ -262,69 +359,63 @@ def from_native_type(
262
359
  raise ValueError(f"Don’t know how to convert {source!r} into a date")
263
360
 
264
361
 
362
+ def from_native_type(
363
+ source: Any,
364
+ /,
365
+ *,
366
+ fmt: str = ISO_DATETIME_FORMAT_UTC,
367
+ past_bound: bool = False,
368
+ ) -> GenericDate:
369
+ """Create an InfinityDate or RealDate instance from string or another type,
370
+ assuming infinity in the latter case (deprecated function name)
371
+ """
372
+ warnings.warn(
373
+ "outdated function name, please use fromnative() instead",
374
+ DeprecationWarning,
375
+ stacklevel=2,
376
+ )
377
+ return fromnative(source, fmt=fmt, past_bound=past_bound)
378
+
379
+
265
380
  def fromtimestamp(timestamp: float) -> GenericDate:
266
381
  """Create an InfinityDate or RealDate instance from the provided timestamp"""
267
- if timestamp == -inf:
268
- return MIN
269
- #
270
- if timestamp == inf:
271
- return MAX
382
+ if timestamp in _INFINITY_FORMS:
383
+ return INFINITE_PAST if timestamp == -inf else INFINITE_FUTURE
272
384
  #
273
385
  stdlib_date_object = date.fromtimestamp(timestamp)
274
- return from_datetime_object(stdlib_date_object)
386
+ return _from_datetime_object(stdlib_date_object)
275
387
 
276
388
 
277
- def fromordinal(ordinal: float | int) -> GenericDate:
389
+ def fromordinal(ordinal: int | float) -> GenericDate:
278
390
  """Create an InfinityDate or RealDate instance from the provided ordinal"""
279
- if ordinal == -inf:
280
- return MIN
281
- #
282
- if ordinal == inf:
283
- return MAX
284
- #
285
- try:
286
- new_ordinal = int(ordinal)
287
- except ValueError as error:
288
- raise ValueError(f"Cannot convert {ordinal!r} to integer") from error
391
+ if ordinal in _INFINITY_FORMS:
392
+ return INFINITE_PAST if ordinal == -inf else INFINITE_FUTURE
289
393
  #
394
+ new_ordinal = trunc(ordinal)
290
395
  if not MIN_ORDINAL <= new_ordinal <= MAX_ORDINAL:
291
396
  raise OverflowError("RealDate value out of range")
292
397
  #
293
398
  stdlib_date_object = date.fromordinal(new_ordinal)
294
- return from_datetime_object(stdlib_date_object)
295
-
296
-
297
- # -----------------------------------------------------------------------------
298
- # Public module constants continued:
299
- # minimum and maximum real dates
300
- # -----------------------------------------------------------------------------
301
-
302
- REAL_MIN: Final[GenericDate] = fromordinal(MIN_ORDINAL)
303
- REAL_MAX: Final[GenericDate] = fromordinal(MAX_ORDINAL)
304
-
305
-
306
- # -----------------------------------------------------------------------------
307
- # Module-level factory functions continued
308
- # -----------------------------------------------------------------------------
399
+ return _from_datetime_object(stdlib_date_object)
309
400
 
310
401
 
311
402
  def fromisoformat(source: str, /) -> GenericDate:
312
403
  """Create an InfinityDate or RealDate instance from an iso format representation"""
313
404
  lower_source_stripped = source.strip().lower()
314
- if lower_source_stripped == INFINITE_DATE_DISPLAY:
315
- return MAX
405
+ if lower_source_stripped == INFINITE_FUTURE_DATE_DISPLAY:
406
+ return INFINITE_FUTURE
316
407
  #
317
- if lower_source_stripped == NEGATIVE_INFINITE_DATE_DISPLAY:
318
- return MIN
408
+ if lower_source_stripped == INFINITE_PAST_DATE_DISPLAY:
409
+ return INFINITE_PAST
319
410
  #
320
- return from_datetime_object(date.fromisoformat(source))
411
+ return _from_datetime_object(date.fromisoformat(source))
321
412
 
322
413
 
323
414
  def fromisocalendar(year: int, week: int, weekday: int) -> GenericDate:
324
415
  """Create a RealDate instance from an iso calendar date"""
325
- return from_datetime_object(date.fromisocalendar(year, week, weekday))
416
+ return _from_datetime_object(date.fromisocalendar(year, week, weekday))
326
417
 
327
418
 
328
419
  def today() -> GenericDate:
329
420
  """Today as RealDate object"""
330
- return from_datetime_object(date.today())
421
+ return _from_datetime_object(date.today())
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: infdate
3
+ Version: 0.2.3
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 for that purpose.
42
+
43
+ **InfinityDate** can represent either past or future infinity.
44
+ The module-level constants **INFINITE_PAST** and **INFINITE_FUTURE**
45
+ contain the two possible **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
+ * **INFINITE_PAST** = **InfinityDate(**_past_bound_=`True`**)** → infinity before any date
61
+ * **INFINITE_FUTURE** = **InfinityDate(**_past_bound_=`False`**)** → infinity after any date
62
+ * **MIN** = **INFINITE_PAST**
63
+ * **MAX** = **INFINITE_FUTURE**
64
+ * **REAL_MIN** = **RealDate(**_`1`, `1`, `1`_**)** → the same date as **datetime.date.min**
65
+ * **REAL_MAX** = **RealDate(**_`9999`, `12`, `31`_**)** → the same date as **datetime.date.max**
66
+ * **MIN_ORDINAL** = `1` → the same value as **datetime.date.min.toordinal()**
67
+ * **MAX_ORDINAL** = `3652059` → the same value as **datetime.date.max.toordinal()**
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
+ * **fromdatetime()** to create a **RealDate** instance from a
85
+ **datetime.date** or **datetime.datetime** instance
86
+ (deprecated old name: _from_datetime_object()_).
87
+
88
+ * **fromnative()** to create an **InfinityDate** or **RealDate**
89
+ instance from a string, from **None**, **-math.inf** or **math.inf**
90
+ (deprecated old name: _from_native_type()_).
91
+
92
+ This can come handy when dealing with API representations of dates,
93
+ eg. in GitLab’s [Personal Access Tokens API].
94
+
95
+
96
+ ### Differences between the infdate module classes and datetime.date
97
+
98
+ 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):
99
+
100
+ * infdate module classes have no **max**, **min** or **resolution** attributes,
101
+ but there are [module-level constants] serving the same purpose.
102
+
103
+ * The **.toordinal()** method returns **int**, **math.inf**, or **-math.inf**.
104
+
105
+ * Subtracting a date from an **InfinityDate** or **RealDate** always returns
106
+ an **int**, **math.inf**, or **-math.inf** instead of a **datetime.timedelta** instance.
107
+
108
+ * Likewise, you cannot add or subtract **datetime.timedelta** instances
109
+ from an **InfinityDate** or **RealDate**, only **float** or **int**
110
+ (support for adding and subtracting datetime.timedelta instances might be added in the future, [see the feature request]).
111
+
112
+ * infdate module classes have a **.pretty()** method that can be used to format **RealDate** instances with a format string like **.strftime()** (provided with the _fmt_ argument that defaults to the ISO format `%Y-%m-%d`),
113
+ or to apply a custom format to **InfinityDate** classes using the _inf_common_prefix_, _inf_past_suffix_ and _inf_future_suffix_ arguments.
114
+
115
+
116
+ ## Example usage
117
+
118
+ ``` pycon
119
+ >>> import infdate
120
+ >>> today = infdate.today()
121
+ >>> today
122
+ RealDate(2025, 6, 30)
123
+ >>> print(f"US date notation: {today:%m/%d/%y}")
124
+ US date notation: 06/30/25
125
+ >>> today.ctime()
126
+ 'Mon Jun 30 00:00:00 2025'
127
+ >>> today.isocalendar()
128
+ datetime.IsoCalendarDate(year=2025, week=27, weekday=1)
129
+ >>> yesterday = today - 1
130
+ >>> yesterday.ctime()
131
+ 'Sun Jun 29 00:00:00 2025'
132
+ >>> today - yesterday
133
+ 1
134
+ >>> infdate.INFINITE_PAST
135
+ InfinityDate(past_bound=True)
136
+ >>> infdate.INFINITE_FUTURE
137
+ InfinityDate(past_bound=False)
138
+ >>> infdate.INFINITE_FUTURE - today
139
+ inf
140
+ >>> infdate.INFINITE_FUTURE - infdate.INFINITE_PAST
141
+ inf
142
+ ```
143
+
144
+ **InfinityDate** and **RealDate** instances can be compared with each other, and also with **datetime.date** instances.
145
+
146
+ Subtracting **InfinityDate** or **RealDate** and **datetime.date** instances from each other also works:
147
+
148
+ ``` pycon
149
+ >>> from datetime import date
150
+ >>> stdlib_today = date.today()
151
+ >>> stdlib_today
152
+ datetime.date(2025, 6, 30)
153
+ >>> today == stdlib_today
154
+ True
155
+ >>> yesterday < stdlib_today
156
+ True
157
+ >>> yesterday - stdlib_today
158
+ -1
159
+ >>> stdlib_today - yesterday
160
+ 1
161
+ >>> stdlib_today - infdate.INFINITE_PAST
162
+ inf
163
+ ```
164
+
165
+
166
+ * * *
167
+ [datetime.date]: https://docs.python.org/3/library/datetime.html#date-objects
168
+ [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
169
+ [module-level constants]: #module-level-constants
170
+ [see the feature request]: https://gitlab.com/blackstream-x/infdate/-/issues/6
@@ -0,0 +1,6 @@
1
+ infdate/__init__.py,sha256=9YUvBcztVWjzQYvu6N0ISSrG_OoH6BjusxX3TGNNv5s,13774
2
+ infdate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ infdate-0.2.3.dist-info/METADATA,sha256=oX3erUYcGq-4dmRxqqMxyOG_RCHvTivIiFL3WcxaN94,6569
4
+ infdate-0.2.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ infdate-0.2.3.dist-info/licenses/LICENSE,sha256=867pxriiObx28vCU1JsRtu3H9kUKyl54e0-xl1IIv3Y,913
6
+ infdate-0.2.3.dist-info/RECORD,,
@@ -1,147 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: infdate
3
- Version: 0.2.1
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
- ### Classes overview
32
-
33
- └── GenericDate
34
- ├── InfinityDate
35
- └── RealDate
36
-
37
-
38
- The base class **GenericDate** should not be instantiated but can be used as a type annotation.
39
-
40
- **InfinityDate** can represent either positive or negative infinity.
41
- The module-level constants **MIN** and **MAX** contain the two possible
42
- **InfinityDate** instance variations.
43
-
44
- **RealDate** instances represent real dates like the standard library’s
45
- **datetime.date** class, with mostly equal or similar semantics. The module -level constants **REAL_MIN** and **REAL_MAX** are the eqivalents of **datetime.date.min** and **datetime.date.max** as **RealDate** instances.
46
-
47
- For any valid **RealDate** instance, the following is **True**:
48
-
49
- ``` python
50
- infdate.MIN < infdate.REAL_MIN <= real_date_instance <= infdate.REAL_MAX < infdate.MAX
51
- ```
52
-
53
- ### Module-level factory functions
54
-
55
-
56
- The following factory methods from the **datetime.date** class
57
- are provided as module-level functions:
58
-
59
- * **fromtimestamp()** (also accepting **-math.inf** or **math.inf**)
60
- * **fromordinal()** (also accepting **-math.inf** or **math.inf**)
61
- * **fromisoformat()**
62
- * **fromisocalendar()**
63
- * **today()**
64
-
65
- Two additional factory functions are provided:
66
-
67
- * **from_datetime_object()** to create a **RealDate** instance from a
68
- **datetime.date** or **datetime.datetime** instance
69
-
70
- * **from_native_type()** to create an **InfinityDate** or **RealDate**
71
- instance from a string, from **None**, **-math.inf** or **math.inf**.
72
-
73
- This can come handy when dealing with API representations of dates,
74
- eg. in GitLab’s [Personal Access Tokens API].
75
-
76
-
77
- ### Differences between infdate classes and datetime.date
78
-
79
- 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):
80
-
81
- * The **.toordinal()** method returns **float** instead of **int**
82
-
83
- * The **resolution** attribute is **1.0** instead of **datetime.timedelta(days=1)**
84
- but also represents exactly one day.
85
-
86
- * Subtracting a date from an **InfinityDate** or **RealDate** always returns a float instead of a **datetime.timedelta** instance.
87
-
88
- * Likewise, you cannot add or subtract **datetime.timedelta** instances
89
- from an **InfinityDate** or **RealDate**, only **float** or **int** (support for adding and subtracting datetime.timedelta instances might be added in the future).
90
-
91
-
92
- ## Example usage
93
-
94
- ``` pycon
95
- >>> import infdate
96
- >>> today = infdate.today()
97
- >>> today
98
- RealDate(2025, 6, 25)
99
- >>> print(today)
100
- 2025-06-25
101
- >>> print(f"US date notation {today:%m/%d/%y}")
102
- US date notation 06/25/25
103
- >>> today.ctime()
104
- 'Wed Jun 25 00:00:00 2025'
105
- >>> today.isocalendar()
106
- datetime.IsoCalendarDate(year=2025, week=26, weekday=3)
107
- >>>
108
- >>> yesterday = today - 1
109
- >>> yesterday.ctime()
110
- 'Tue Jun 24 00:00:00 2025'
111
- >>>
112
- >>> today - yesterday
113
- 1.0
114
- >>> infdate.MIN
115
- InfinityDate(past_bound=True)
116
- >>> infdate.MAX
117
- InfinityDate(past_bound=False)
118
- >>> infdate.MAX - today
119
- inf
120
- >>> infdate.MAX - infdate.MIN
121
- inf
122
- ```
123
-
124
- **InfinityDate** and **RealDate** instances can be compared with each other, and also with **datetime.date** instances.
125
-
126
- Subtracting instances from each other also works, but currently, `__rsub__` is not implemented yet, so although the other direction works, subtracting an **InfinityDate** or **RealDate** _from_ a **datetime.date**
127
- currently still raises a **TypeError**):
128
-
129
- >>> from datetime import date
130
- >>> stdlib_today = date.today()
131
- >>> today == stdlib_today
132
- True
133
- >>> yesterday < stdlib_today
134
- True
135
- >>> yesterday - stdlib_today
136
- -1.0
137
- >>> stdlib_today - yesterday
138
- Traceback (most recent call last):
139
- File "<python-input-22>", line 1, in <module>
140
- stdlib_today - yesterday
141
- ~~~~~~~~~~~~~^~~~~~~~~~~
142
- TypeError: unsupported operand type(s) for -: 'datetime.date' and 'RealDate'
143
-
144
-
145
-
146
- * * *
147
- [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
@@ -1,6 +0,0 @@
1
- infdate/__init__.py,sha256=n3WfJnHma_FGelLrkOjBt4gj49P-_rlsqc0FTLmUxCI,11195
2
- infdate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- infdate-0.2.1.dist-info/METADATA,sha256=V85LBfMkyB66rZJI4ceghR0KL-x0b2RQoOo-0_Sr77s,5205
4
- infdate-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
- infdate-0.2.1.dist-info/licenses/LICENSE,sha256=867pxriiObx28vCU1JsRtu3H9kUKyl54e0-xl1IIv3Y,913
6
- infdate-0.2.1.dist-info/RECORD,,