infdate 0.2.2__py3-none-any.whl → 0.2.4__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,42 +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
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"
27
-
28
- MIN_ORDINAL: Final[int] = date.min.toordinal()
29
- MAX_ORDINAL: Final[int] = date.max.toordinal()
20
+ _GD = TypeVar("_GD", bound="GenericDate")
30
21
 
31
- RESOLUTION: Final[int] = 1
22
+ _INFINITY_FORMS = (-inf, inf)
32
23
 
33
24
 
34
25
  # -----------------------------------------------------------------------------
35
- # Internal module constants:
36
- # TypeVar for GenericDate
26
+ # Public module constants:
27
+ # format strings
37
28
  # -----------------------------------------------------------------------------
38
29
 
39
- _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"
40
35
 
41
36
 
42
37
  # -----------------------------------------------------------------------------
@@ -49,7 +44,7 @@ class GenericDate:
49
44
 
50
45
  def __init__(self, ordinal: int | float, /) -> None:
51
46
  """Create a date-like object"""
52
- if ordinal in (-inf, inf):
47
+ if ordinal in _INFINITY_FORMS:
53
48
  self.__ordinal = ordinal
54
49
  else:
55
50
  self.__ordinal = trunc(ordinal)
@@ -92,12 +87,14 @@ class GenericDate:
92
87
  return hash(f"date with ordinal {self.__ordinal}")
93
88
 
94
89
  def _add_days(self: _GD, delta: int | float, /):
95
- """Add other, respecting maybe-nondeterministic values"""
90
+ """Add other, respecting maybe-nondeterministic values
91
+ (ie. -inf or inf)
92
+ """
96
93
  # Check for infinity in either self or delta,
97
94
  # and return a matching InfinityDate if found.
98
95
  # Re-use existing objects if possible.
99
96
  for observed_item in (delta, self.__ordinal):
100
- for infinity_form in (inf, -inf):
97
+ for infinity_form in _INFINITY_FORMS:
101
98
  if observed_item == infinity_form:
102
99
  if observed_item == self.__ordinal:
103
100
  return self
@@ -155,6 +152,18 @@ class GenericDate:
155
152
  """Return a copy with year, month, and/or date replaced"""
156
153
  raise NotImplementedError
157
154
 
155
+ def pretty(
156
+ self: _GD,
157
+ /,
158
+ *,
159
+ inf_common_prefix: str = "",
160
+ inf_past_suffix: str = "",
161
+ inf_future_suffix: str = "",
162
+ fmt: str = ISO_DATE_FORMAT,
163
+ ) -> str:
164
+ """Return the date, pretty printed"""
165
+ raise NotImplementedError
166
+
158
167
 
159
168
  class InfinityDate(GenericDate):
160
169
  """Infinity Date object"""
@@ -162,6 +171,7 @@ class InfinityDate(GenericDate):
162
171
  def __init__(self, /, *, past_bound: bool = False) -> None:
163
172
  """Store -inf or inf"""
164
173
  ordinal = -inf if past_bound else inf
174
+ self.__is_past = past_bound
165
175
  super().__init__(ordinal)
166
176
 
167
177
  def __repr__(self, /) -> str:
@@ -170,10 +180,10 @@ class InfinityDate(GenericDate):
170
180
 
171
181
  def strftime(self, fmt: str, /) -> str:
172
182
  """String representation of the date"""
173
- if self.toordinal() == inf:
174
- return INFINITE_DATE_DISPLAY
183
+ if self.__is_past:
184
+ return INFINITE_PAST_DATE_DISPLAY
175
185
  #
176
- return NEGATIVE_INFINITE_DATE_DISPLAY
186
+ return INFINITE_FUTURE_DATE_DISPLAY
177
187
 
178
188
  __format__ = strftime
179
189
 
@@ -183,6 +193,25 @@ class InfinityDate(GenericDate):
183
193
  f"{self.__class__.__name__} instances do not support .replace()"
184
194
  )
185
195
 
196
+ @final
197
+ def pretty(
198
+ self,
199
+ /,
200
+ *,
201
+ inf_common_prefix: str = "",
202
+ inf_past_suffix: str = "",
203
+ inf_future_suffix: str = "",
204
+ # pylint:disable = unused-argument ; required by inheritance
205
+ fmt: str = ISO_DATE_FORMAT,
206
+ ) -> str:
207
+ """Return the date, pretty printed"""
208
+ if self.__is_past:
209
+ pretty_result = f"{inf_common_prefix}{inf_past_suffix}"
210
+ else:
211
+ pretty_result = f"{inf_common_prefix}{inf_future_suffix}"
212
+ #
213
+ return pretty_result or self.isoformat()
214
+
186
215
 
187
216
  # pylint: disable=too-many-instance-attributes
188
217
  class RealDate(GenericDate):
@@ -226,29 +255,78 @@ class RealDate(GenericDate):
226
255
  )
227
256
  )
228
257
 
258
+ @final
259
+ def pretty(
260
+ self,
261
+ /,
262
+ *,
263
+ inf_common_prefix: str = "",
264
+ inf_past_suffix: str = "",
265
+ inf_future_suffix: str = "",
266
+ fmt: str = ISO_DATE_FORMAT,
267
+ ) -> str:
268
+ """Return the date, pretty printed"""
269
+ return self.strftime(fmt)
270
+
271
+
272
+ # -----------------------------------------------------------------------------
273
+ # Private module functions
274
+ # -----------------------------------------------------------------------------
275
+
276
+
277
+ def _from_datetime_object(source: date | datetime, /) -> GenericDate:
278
+ """Create a new RealDate instance from a
279
+ date or datetime object (module-private function)
280
+ """
281
+ return RealDate(source.year, source.month, source.day)
282
+
229
283
 
230
284
  # -----------------------------------------------------------------------------
231
285
  # Public module constants continued:
232
- # absolute minimum and maximum dates
286
+ # minimum and maximum dates and ordinals, resolution (one day)
233
287
  # -----------------------------------------------------------------------------
234
288
 
235
- MIN: Final[GenericDate] = InfinityDate(past_bound=True)
236
- MAX: Final[GenericDate] = InfinityDate(past_bound=False)
289
+
290
+ INFINITE_PAST: Final[GenericDate] = InfinityDate(past_bound=True)
291
+ INFINITE_FUTURE: Final[GenericDate] = InfinityDate(past_bound=False)
292
+
293
+ MIN: Final[GenericDate] = INFINITE_PAST
294
+ MAX: Final[GenericDate] = INFINITE_FUTURE
295
+
296
+ REAL_MIN: Final[GenericDate] = _from_datetime_object(date.min)
297
+ REAL_MAX: Final[GenericDate] = _from_datetime_object(date.max)
298
+
299
+ MIN_ORDINAL: Final[int] = date.min.toordinal()
300
+ MAX_ORDINAL: Final[int] = date.max.toordinal()
301
+
302
+ RESOLUTION: Final[int] = 1
237
303
 
238
304
 
239
305
  # -----------------------------------------------------------------------------
240
- # Module-level factory functions
306
+ # Public module-level factory functions
241
307
  # -----------------------------------------------------------------------------
242
308
 
243
309
 
244
- def from_datetime_object(source: date | datetime, /) -> GenericDate:
310
+ def fromdatetime(source: date | datetime, /) -> GenericDate:
245
311
  """Create a new RealDate instance from a
246
312
  date or datetime object
247
313
  """
248
- return RealDate(source.year, source.month, source.day)
314
+ return _from_datetime_object(source)
249
315
 
250
316
 
251
- def from_native_type(
317
+ def from_datetime_object(source: date | datetime, /) -> GenericDate:
318
+ """Create a new RealDate instance from a
319
+ date or datetime object (deprecated function name)
320
+ """
321
+ warnings.warn(
322
+ "outdated function name, please use fromdatetime() instead",
323
+ DeprecationWarning,
324
+ stacklevel=2,
325
+ )
326
+ return fromdatetime(source)
327
+
328
+
329
+ def fromnative(
252
330
  source: Any,
253
331
  /,
254
332
  *,
@@ -259,7 +337,7 @@ def from_native_type(
259
337
  assuming infinity in the latter case
260
338
  """
261
339
  if isinstance(source, str):
262
- return from_datetime_object(datetime.strptime(source, fmt))
340
+ return _from_datetime_object(datetime.strptime(source, fmt))
263
341
  #
264
342
  if source == -inf or source is None and past_bound:
265
343
  return MIN
@@ -270,65 +348,63 @@ def from_native_type(
270
348
  raise ValueError(f"Don’t know how to convert {source!r} into a date")
271
349
 
272
350
 
351
+ def from_native_type(
352
+ source: Any,
353
+ /,
354
+ *,
355
+ fmt: str = ISO_DATETIME_FORMAT_UTC,
356
+ past_bound: bool = False,
357
+ ) -> GenericDate:
358
+ """Create an InfinityDate or RealDate instance from string or another type,
359
+ assuming infinity in the latter case (deprecated function name)
360
+ """
361
+ warnings.warn(
362
+ "outdated function name, please use fromnative() instead",
363
+ DeprecationWarning,
364
+ stacklevel=2,
365
+ )
366
+ return fromnative(source, fmt=fmt, past_bound=past_bound)
367
+
368
+
273
369
  def fromtimestamp(timestamp: float) -> GenericDate:
274
370
  """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
371
+ if timestamp in _INFINITY_FORMS:
372
+ return INFINITE_PAST if timestamp == -inf else INFINITE_FUTURE
280
373
  #
281
374
  stdlib_date_object = date.fromtimestamp(timestamp)
282
- return from_datetime_object(stdlib_date_object)
375
+ return _from_datetime_object(stdlib_date_object)
283
376
 
284
377
 
285
378
  def fromordinal(ordinal: int | float) -> GenericDate:
286
379
  """Create an InfinityDate or RealDate instance from the provided ordinal"""
287
- if ordinal == -inf:
288
- return MIN
289
- #
290
- if ordinal == inf:
291
- return MAX
380
+ if ordinal in _INFINITY_FORMS:
381
+ return INFINITE_PAST if ordinal == -inf else INFINITE_FUTURE
292
382
  #
293
383
  new_ordinal = trunc(ordinal)
294
384
  if not MIN_ORDINAL <= new_ordinal <= MAX_ORDINAL:
295
385
  raise OverflowError("RealDate value out of range")
296
386
  #
297
387
  stdlib_date_object = date.fromordinal(new_ordinal)
298
- return from_datetime_object(stdlib_date_object)
299
-
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
- # -----------------------------------------------------------------------------
388
+ return _from_datetime_object(stdlib_date_object)
313
389
 
314
390
 
315
391
  def fromisoformat(source: str, /) -> GenericDate:
316
392
  """Create an InfinityDate or RealDate instance from an iso format representation"""
317
393
  lower_source_stripped = source.strip().lower()
318
- if lower_source_stripped == INFINITE_DATE_DISPLAY:
319
- return MAX
394
+ if lower_source_stripped == INFINITE_FUTURE_DATE_DISPLAY:
395
+ return INFINITE_FUTURE
320
396
  #
321
- if lower_source_stripped == NEGATIVE_INFINITE_DATE_DISPLAY:
322
- return MIN
397
+ if lower_source_stripped == INFINITE_PAST_DATE_DISPLAY:
398
+ return INFINITE_PAST
323
399
  #
324
- return from_datetime_object(date.fromisoformat(source))
400
+ return _from_datetime_object(date.fromisoformat(source))
325
401
 
326
402
 
327
403
  def fromisocalendar(year: int, week: int, weekday: int) -> GenericDate:
328
404
  """Create a RealDate instance from an iso calendar date"""
329
- return from_datetime_object(date.fromisocalendar(year, week, weekday))
405
+ return _from_datetime_object(date.fromisocalendar(year, week, weekday))
330
406
 
331
407
 
332
408
  def today() -> GenericDate:
333
409
  """Today as RealDate object"""
334
- return from_datetime_object(date.today())
410
+ return _from_datetime_object(date.today())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: infdate
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Date object wrapper supporting infinity
5
5
  Project-URL: Homepage, https://gitlab.com/blackstream-x/infdate
6
6
  Project-URL: CI, https://gitlab.com/blackstream-x/infdate/-/pipelines
@@ -38,11 +38,11 @@ _Python module for date calculations implementing a concept of infinity_
38
38
 
39
39
  The base class **GenericDate** should not be instantiated
40
40
  but can be used as a type annotation. In fact, it should be preferred
41
- over the other classes in typing annotations.
41
+ over the other classes for that purpose.
42
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.
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
46
 
47
47
  **RealDate** instances represent real dates like the standard library’s
48
48
  **[datetime.date]** class, with mostly equal or similar semantics.
@@ -57,14 +57,14 @@ infdate.MIN < infdate.REAL_MIN <= real_date_instance <= infdate.REAL_MAX < infda
57
57
 
58
58
  ### Module-level constants
59
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**
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
68
  * **RESOLUTION** = `1` → represents the lowest possible date difference: _one day_
69
69
 
70
70
 
@@ -81,21 +81,23 @@ are provided as module-level functions:
81
81
 
82
82
  Two additional factory functions are provided:
83
83
 
84
- * **from_datetime_object()** to create a **RealDate** instance from a
84
+ * **fromdatetime()** to create a **RealDate** instance from a
85
85
  **datetime.date** or **datetime.datetime** instance
86
+ (deprecated old name: _from_datetime_object()_).
86
87
 
87
- * **from_native_type()** to create an **InfinityDate** or **RealDate**
88
- instance from a string, from **None**, **-math.inf** or **math.inf**.
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()_).
89
91
 
90
92
  This can come handy when dealing with API representations of dates,
91
93
  eg. in GitLab’s [Personal Access Tokens API].
92
94
 
93
95
 
94
- ### Differences between infdate classes and datetime.date
96
+ ### Differences between the infdate module classes and datetime.date
95
97
 
96
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):
97
99
 
98
- * infdate classes have no **max**, **min** or **resolution** attributes,
100
+ * infdate module classes have no **max**, **min** or **resolution** attributes,
99
101
  but there are [module-level constants] serving the same purpose.
100
102
 
101
103
  * The **.toordinal()** method returns **int**, **math.inf**, or **-math.inf**.
@@ -107,6 +109,9 @@ Some notable difference from the **datetime.date** class, mainly due to the desi
107
109
  from an **InfinityDate** or **RealDate**, only **float** or **int**
108
110
  (support for adding and subtracting datetime.timedelta instances might be added in the future, [see the feature request]).
109
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
+
110
115
 
111
116
  ## Example usage
112
117
 
@@ -114,27 +119,25 @@ Some notable difference from the **datetime.date** class, mainly due to the desi
114
119
  >>> import infdate
115
120
  >>> today = infdate.today()
116
121
  >>> today
117
- RealDate(2025, 6, 27)
122
+ RealDate(2025, 6, 30)
118
123
  >>> print(f"US date notation: {today:%m/%d/%y}")
119
- US date notation: 06/27/25
124
+ US date notation: 06/30/25
120
125
  >>> today.ctime()
121
- 'Fri Jun 27 00:00:00 2025'
126
+ 'Mon Jun 30 00:00:00 2025'
122
127
  >>> today.isocalendar()
123
- datetime.IsoCalendarDate(year=2025, week=26, weekday=5)
124
- >>>
128
+ datetime.IsoCalendarDate(year=2025, week=27, weekday=1)
125
129
  >>> yesterday = today - 1
126
130
  >>> yesterday.ctime()
127
- 'Thu Jun 26 00:00:00 2025'
128
- >>>
131
+ 'Sun Jun 29 00:00:00 2025'
129
132
  >>> today - yesterday
130
133
  1
131
- >>> infdate.MIN
134
+ >>> infdate.INFINITE_PAST
132
135
  InfinityDate(past_bound=True)
133
- >>> infdate.MAX
136
+ >>> infdate.INFINITE_FUTURE
134
137
  InfinityDate(past_bound=False)
135
- >>> infdate.MAX - today
138
+ >>> infdate.INFINITE_FUTURE - today
136
139
  inf
137
- >>> infdate.MAX - infdate.MIN
140
+ >>> infdate.INFINITE_FUTURE - infdate.INFINITE_PAST
138
141
  inf
139
142
  ```
140
143
 
@@ -145,6 +148,8 @@ Subtracting **InfinityDate** or **RealDate** and **datetime.date** instances fro
145
148
  ``` pycon
146
149
  >>> from datetime import date
147
150
  >>> stdlib_today = date.today()
151
+ >>> stdlib_today
152
+ datetime.date(2025, 6, 30)
148
153
  >>> today == stdlib_today
149
154
  True
150
155
  >>> yesterday < stdlib_today
@@ -153,7 +158,7 @@ True
153
158
  -1
154
159
  >>> stdlib_today - yesterday
155
160
  1
156
- >>> stdlib_today - infdate.MIN
161
+ >>> stdlib_today - infdate.INFINITE_PAST
157
162
  inf
158
163
  ```
159
164
 
@@ -0,0 +1,6 @@
1
+ infdate/__init__.py,sha256=IIJ8ZoTGNoBeFTt86_ysMCUVBj36r8vcqpReJa6EiUM,13523
2
+ infdate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ infdate-0.2.4.dist-info/METADATA,sha256=O22fG8KKeSgLV2hDAN8pkM2JmKh4i-_yCX_glIlTGp8,6569
4
+ infdate-0.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ infdate-0.2.4.dist-info/licenses/LICENSE,sha256=867pxriiObx28vCU1JsRtu3H9kUKyl54e0-xl1IIv3Y,913
6
+ infdate-0.2.4.dist-info/RECORD,,
@@ -1,6 +0,0 @@
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,,