infdate 0.1.0__py3-none-any.whl → 0.2.1__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
@@ -5,278 +5,326 @@ infdate: a wrapper around standard library’s datetime.date objects,
5
5
  capable of representing positive and negative infinity
6
6
  """
7
7
 
8
- import datetime
9
- import math
10
-
8
+ from datetime import date, datetime
9
+ from math import inf
11
10
  from typing import final, overload, Any, Final, TypeVar
12
11
 
13
- INFINITY: Final = math.inf
14
- NEGATIVE_INFINITY: Final = -math.inf
15
12
 
16
- INFINITE_DATE_DISPLAY: Final = "<inf>"
17
- NEGATIVE_INFINITE_DATE_DISPLAY: Final = "<-inf>"
13
+ # -----------------------------------------------------------------------------
14
+ # Public module constants:
15
+ # display definitions, format strings, upper and loer ordinal boundaries
16
+ # -----------------------------------------------------------------------------
18
17
 
19
- ISO_DATE_FORMAT: Final = "%Y-%m-%d"
20
- ISO_DATETIME_FORMAT_UTC: Final = f"{ISO_DATE_FORMAT}T%H:%M:%S.%f%Z"
18
+ INFINITE_DATE_DISPLAY: Final[str] = "<inf>"
19
+ NEGATIVE_INFINITE_DATE_DISPLAY: Final[str] = "<-inf>"
21
20
 
22
- D = TypeVar("D", bound="Date")
21
+ INFINITY_SYMBOL: Final[str] = ""
22
+ UP_TO_SYMBOL: Final[str] = "⤒"
23
+ FROM_ON_SYMBOL: Final[str] = "↥"
23
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"
24
27
 
25
- class DateMeta(type):
26
- """Date metaclass"""
28
+ MIN_ORDINAL: Final[int] = date.min.toordinal()
29
+ MAX_ORDINAL: Final[int] = date.max.toordinal()
27
30
 
28
- @property
29
- def min(cls: type[D], /) -> D: # type: ignore[misc]
30
- """Minimum possible Date"""
31
- return cls(NEGATIVE_INFINITY)
32
31
 
33
- @property
34
- def max(cls: type[D], /) -> D: # type: ignore[misc]
35
- """Maximum possible Date"""
36
- return cls(INFINITY)
32
+ # -----------------------------------------------------------------------------
33
+ # Internal module constants:
34
+ # TypeVar for GenericDate
35
+ # -----------------------------------------------------------------------------
37
36
 
37
+ _GD = TypeVar("_GD", bound="GenericDate")
38
38
 
39
- class Date(metaclass=DateMeta):
40
- """Date object capable of representing negative or positive infinity"""
41
39
 
42
- resolution = 1
40
+ # -----------------------------------------------------------------------------
41
+ # Classes
42
+ # -----------------------------------------------------------------------------
43
43
 
44
- @overload
45
- def __init__(self: D, year_or_strange_number: float, /) -> None: ...
46
- @overload
47
- def __init__(
48
- self: D, year_or_strange_number: int, month: int, day: int, /
49
- ) -> None: ...
50
- @final
51
- def __init__(
52
- self: D, year_or_strange_number: int | float, /, month: int = 0, day: int = 0
53
- ) -> None:
54
- """Create a date-like object"""
55
- if isinstance(year_or_strange_number, int):
56
- self.__wrapped_date_obj: datetime.date | None = datetime.date(
57
- int(year_or_strange_number), month, day
58
- )
59
- self.__ordinal: float | int = self.__wrapped_date_obj.toordinal()
60
- elif math.isnan(year_or_strange_number):
61
- raise ValueError("Cannot instantiate from NaN")
62
- elif year_or_strange_number in (
63
- INFINITY,
64
- NEGATIVE_INFINITY,
65
- ):
66
- self.__ordinal = year_or_strange_number
67
- self.__wrapped_date_obj = None
68
- else:
69
- raise ValueError("Cannot instantiate from a regular deterministic float")
70
- #
71
44
 
72
- def toordinal(self: D) -> float | int:
73
- """to ordinal (almost like datetime.date.toordinal())"""
74
- return self.__ordinal
45
+ class GenericDate:
46
+ """Base Date object derived from an ordinal"""
75
47
 
76
- def get_date_object(self: D) -> datetime.date:
77
- """Return the wrapped date object"""
78
- if isinstance(self.__wrapped_date_obj, datetime.date):
79
- return self.__wrapped_date_obj
80
- #
81
- raise ValueError("Non-deterministic date")
48
+ # pylint: disable=invalid-name
49
+ resolution: Final = 1
50
+ # pylint: enable=invalid-name
82
51
 
83
- @property
84
- def year(self: D) -> int:
85
- """shortcut: year"""
86
- return self.get_date_object().year
52
+ def __init__(self, ordinal: float, /) -> None:
53
+ """Create a date-like object"""
54
+ self.__ordinal = ordinal
87
55
 
88
- @property
89
- def month(self: D) -> int:
90
- """shortcut: month"""
91
- return self.get_date_object().month
56
+ def toordinal(self: _GD) -> float:
57
+ """to ordinal (almost like date.toordinal())"""
58
+ return self.__ordinal
92
59
 
93
- @property
94
- def day(self: D) -> int:
95
- """shortcut: day"""
96
- return self.get_date_object().day
60
+ def __lt__(self: _GD, other: _GD, /) -> bool:
61
+ """Rich comparison: less"""
62
+ return self.__ordinal < other.toordinal()
97
63
 
98
- def replace(self: D, /, year: int = 0, month: int = 0, day: int = 0) -> D:
99
- """Return a copy with year, month, and/or date replaced"""
100
- internal_object = self.get_date_object()
101
- return self.factory(
102
- internal_object.replace(
103
- year=year or internal_object.year,
104
- month=month or internal_object.month,
105
- day=day or internal_object.day,
106
- )
107
- )
64
+ def __le__(self: _GD, other: _GD, /) -> bool:
65
+ """Rich comparison: less or equal"""
66
+ return self < other or self == other
108
67
 
109
- def isoformat(self: D) -> str:
110
- """Date representation in ISO format"""
111
- return self.strftime(ISO_DATE_FORMAT)
68
+ def __gt__(self: _GD, other: _GD, /) -> bool:
69
+ """Rich comparison: greater"""
70
+ return self.__ordinal > other.toordinal()
112
71
 
113
- def strftime(self: D, fmt: str, /) -> str:
114
- """String representation of the date"""
115
- try:
116
- date_object = self.get_date_object()
117
- except ValueError as error:
118
- if self.__ordinal == INFINITY:
119
- return INFINITE_DATE_DISPLAY
120
- #
121
- if self.__ordinal == NEGATIVE_INFINITY:
122
- return NEGATIVE_INFINITE_DATE_DISPLAY
123
- #
124
- raise error from error
125
- #
126
- return date_object.strftime(fmt or ISO_DATE_FORMAT)
72
+ def __ge__(self: _GD, other: _GD, /) -> bool:
73
+ """Rich comparison: greater or equal"""
74
+ return self > other or self == other
127
75
 
128
- __format__ = strftime
76
+ def __eq__(self: _GD, other, /) -> bool:
77
+ """Rich comparison: equals"""
78
+ return self.__ordinal == other.toordinal()
129
79
 
130
- def __bool__(self: D) -> bool:
131
- """True if a real date is wrapped"""
132
- return self.__wrapped_date_obj is not None
80
+ def __ne__(self: _GD, other, /) -> bool:
81
+ """Rich comparison: does not equal"""
82
+ return self.__ordinal != other.toordinal()
133
83
 
134
- def __hash__(self: D) -> int:
84
+ def __bool__(self: _GD, /) -> bool:
85
+ """True only if a real date is wrapped"""
86
+ return False
87
+
88
+ def __hash__(self: _GD, /) -> int:
135
89
  """hash value"""
136
90
  return hash(f"date with ordinal {self.__ordinal}")
137
91
 
138
- def __add__(self: D, delta: int | float, /) -> D:
92
+ def _add_days(self: _GD, delta: int | float, /):
139
93
  """Add other, respecting maybe-nondeterministic values"""
94
+ # Check for infinity in either self or delta,
95
+ # and return a matching InfinityDate if found.
96
+ # Re-use existing objects if possible.
140
97
  for observed_item in (delta, self.__ordinal):
141
- for infinity_form in (INFINITY, NEGATIVE_INFINITY):
98
+ for infinity_form in (inf, -inf):
142
99
  if observed_item == infinity_form:
143
- return self.factory(infinity_form)
100
+ if observed_item == self.__ordinal:
101
+ return self
102
+ #
103
+ return fromordinal(observed_item)
144
104
  #
145
105
  #
146
106
  #
147
- return self.fromordinal(int(self.__ordinal) + int(delta))
107
+ # +/- 0 corner case
108
+ if not delta:
109
+ return self
110
+ #
111
+ # Return a RealDate instance if possible
112
+ return fromordinal(self.__ordinal + delta)
113
+
114
+ def __add__(self: _GD, delta: int | float, /) -> _GD:
115
+ """gd_instance1 + number capability"""
116
+ return self._add_days(delta)
148
117
 
149
118
  @overload
150
- def __sub__(self: D, other: int | float, /) -> D: ...
119
+ def __sub__(self: _GD, other: int | float, /) -> _GD: ...
151
120
  @overload
152
- def __sub__(self: D, other: D, /) -> int | float: ...
121
+ def __sub__(self: _GD, other: _GD, /) -> int | float: ...
153
122
  @final
154
- def __sub__(self: D, other: D | int | float, /) -> D | int | float:
123
+ def __sub__(self: _GD, other: _GD | int | float, /) -> _GD | int | float:
155
124
  """subtract other, respecting possibly nondeterministic values"""
156
125
  if isinstance(other, (int, float)):
157
- return self + -other
126
+ return self._add_days(-other)
158
127
  #
159
128
  return self.__ordinal - other.toordinal()
160
129
 
161
- def __lt__(self: D, other: D, /) -> bool:
162
- """Rich comparison: less"""
163
- return self.__ordinal < other.toordinal()
130
+ def __repr__(self: _GD, /) -> str:
131
+ """String representation of the object"""
132
+ return f"{self.__class__.__name__}({repr(self.__ordinal)})"
164
133
 
165
- def __le__(self: D, other: D, /) -> bool:
166
- """Rich comparison: less or equal"""
167
- return self < other or self == other
134
+ def __str__(self: _GD, /) -> str:
135
+ """String display of the object"""
136
+ return self.isoformat()
168
137
 
169
- def __gt__(self: D, other: D, /) -> bool:
170
- """Rich comparison: greater"""
171
- return self.__ordinal > other.toordinal()
138
+ def isoformat(self: _GD, /) -> str:
139
+ """Date representation in ISO format"""
140
+ return self.strftime(ISO_DATE_FORMAT)
172
141
 
173
- def __ge__(self: D, other: D, /) -> bool:
174
- """Rich comparison: greater or equal"""
175
- return self > other or self == other
142
+ def strftime(self: _GD, fmt: str, /) -> str:
143
+ """String representation of the date"""
144
+ raise NotImplementedError
176
145
 
177
- def __eq__(self: D, other, /) -> bool:
178
- """Rich comparison: equals"""
179
- return self.__ordinal == other.toordinal()
146
+ def replace(self: _GD, /, year: int = 0, month: int = 0, day: int = 0) -> _GD:
147
+ """Return a copy with year, month, and/or date replaced"""
148
+ raise NotImplementedError
180
149
 
181
- def __ne__(self: D, other, /) -> bool:
182
- """Rich comparison: does not equal"""
183
- return self.__ordinal != other.toordinal()
184
150
 
185
- def __repr__(self: D, /) -> str:
151
+ class InfinityDate(GenericDate):
152
+ """Infinity Date object"""
153
+
154
+ def __init__(self, /, *, past_bound: bool = False) -> None:
155
+ """Store -inf or inf"""
156
+ ordinal = -inf if past_bound else inf
157
+ super().__init__(ordinal)
158
+
159
+ def __repr__(self, /) -> str:
186
160
  """String representation of the object"""
187
- try:
188
- return f"{self.__class__.__name__}({self.year}, {self.month}, {self.day})"
189
- except ValueError:
190
- return f"{self.__class__.__name__}({repr(self.__ordinal)})"
161
+ return f"{self.__class__.__name__}(past_bound={self.toordinal() == -inf})"
162
+
163
+ def strftime(self, fmt: str, /) -> str:
164
+ """String representation of the date"""
165
+ if self.toordinal() == inf:
166
+ return INFINITE_DATE_DISPLAY
191
167
  #
168
+ return NEGATIVE_INFINITE_DATE_DISPLAY
169
+
170
+ __format__ = strftime
171
+
172
+ def replace(self, /, year: int = 0, month: int = 0, day: int = 0):
173
+ """Not supported in this class"""
174
+ raise TypeError(
175
+ f"{self.__class__.__name__} instances do not support .replace()"
176
+ )
177
+
178
+
179
+ # pylint: disable=too-many-instance-attributes
180
+ class RealDate(GenericDate):
181
+ """Real (deterministic) Date object based on date"""
192
182
 
193
- def __str__(self: D, /) -> str:
183
+ def __init__(self, year: int, month: int, day: int) -> None:
184
+ """Create a date-like object"""
185
+ self._wrapped_date_object = date(year, month, day)
186
+ self.year = year
187
+ self.month = month
188
+ self.day = day
189
+ super().__init__(float(self._wrapped_date_object.toordinal()))
190
+ self.timetuple = self._wrapped_date_object.timetuple
191
+ self.weekday = self._wrapped_date_object.weekday
192
+ self.isoweekday = self._wrapped_date_object.isoweekday
193
+ self.isocalendar = self._wrapped_date_object.isocalendar
194
+ self.ctime = self._wrapped_date_object.ctime
195
+
196
+ def __bool__(self, /) -> bool:
197
+ """True if a real date is wrapped"""
198
+ return True
199
+
200
+ def __repr__(self, /) -> str:
201
+ """String representation of the object"""
202
+ return f"{self.__class__.__name__}({self.year}, {self.month}, {self.day})"
203
+
204
+ def strftime(self, fmt: str, /) -> str:
194
205
  """String representation of the date"""
195
- return self.isoformat()
206
+ return self._wrapped_date_object.strftime(fmt or ISO_DATE_FORMAT)
196
207
 
197
- @classmethod
198
- def today(cls: type[D], /) -> D:
199
- """Today as Date object"""
200
- return cls.factory(datetime.date.today())
201
-
202
- @classmethod
203
- def fromisoformat(
204
- cls: type[D],
205
- source: str,
206
- /,
207
- ) -> D:
208
- """Create an instance from an iso format representation"""
209
- lower_source_stripped = source.strip().lower()
210
- if lower_source_stripped == INFINITE_DATE_DISPLAY:
211
- return cls(INFINITY)
212
- #
213
- if lower_source_stripped == NEGATIVE_INFINITE_DATE_DISPLAY:
214
- return cls(NEGATIVE_INFINITY)
215
- #
216
- return cls.factory(datetime.date.fromisoformat(lower_source_stripped))
217
-
218
- @classmethod
219
- def fromisocalendar(
220
- cls: type[D],
221
- /,
222
- year: int,
223
- week: int,
224
- day: int,
225
- ) -> D:
226
- """Create an instance from an iso calendar date"""
227
- return cls.factory(datetime.date.fromisocalendar(year, week, day))
228
-
229
- @classmethod
230
- def fromordinal(
231
- cls: type[D],
232
- source: float | int,
233
- /,
234
- ) -> D:
235
- """Create an instance from a date ordinal"""
236
- if isinstance(source, int):
237
- return cls.factory(datetime.date.fromordinal(source))
238
- #
239
- if source in (NEGATIVE_INFINITY, INFINITY):
240
- return cls(source)
241
- #
242
- raise ValueError(f"Invalid source for .fromordinal(): {source!r}")
243
-
244
- @classmethod
245
- def from_api_data(
246
- cls: type[D],
247
- source: Any,
248
- /,
249
- *,
250
- fmt: str = ISO_DATETIME_FORMAT_UTC,
251
- past_bound: bool = False,
252
- ) -> D:
253
- """Create an instance from string or another type,
254
- assuming infinity in the latter case
255
- """
256
- if isinstance(source, str):
257
- return cls.factory(datetime.datetime.strptime(source, fmt))
258
- #
259
- return cls(NEGATIVE_INFINITY if past_bound else INFINITY)
208
+ __format__ = strftime
260
209
 
261
- @overload
262
- @classmethod
263
- def factory(cls: type[D], source: datetime.date | datetime.datetime, /) -> D: ...
264
- @overload
265
- @classmethod
266
- def factory(cls: type[D], source: float, /) -> D: ...
267
- @final
268
- @classmethod
269
- def factory(
270
- cls: type[D], source: datetime.date | datetime.datetime | float, /
271
- ) -> D:
272
- """Create a new instance from a datetime.date or datetime.datetime object,
273
- from
274
- """
275
- if isinstance(source, (datetime.date, datetime.datetime)):
276
- return cls(source.year, source.month, source.day)
277
- #
278
- return cls(source)
210
+ def replace(self, /, year: int = 0, month: int = 0, day: int = 0):
211
+ """Return a copy with year, month, and/or date replaced"""
212
+ internal_object = self._wrapped_date_object
213
+ return from_datetime_object(
214
+ internal_object.replace(
215
+ year=year or internal_object.year,
216
+ month=month or internal_object.month,
217
+ day=day or internal_object.day,
218
+ )
219
+ )
279
220
 
280
221
 
281
- BEFORE_BIG_BANG: Final = Date.max
282
- SAINT_GLINGLIN: Final = Date.min
222
+ # -----------------------------------------------------------------------------
223
+ # Public module constants continued:
224
+ # absolute minimum and maximum dates
225
+ # -----------------------------------------------------------------------------
226
+
227
+ MIN: Final[GenericDate] = InfinityDate(past_bound=True)
228
+ MAX: Final[GenericDate] = InfinityDate(past_bound=False)
229
+
230
+
231
+ # -----------------------------------------------------------------------------
232
+ # Module-level factory functions
233
+ # -----------------------------------------------------------------------------
234
+
235
+
236
+ def from_datetime_object(source: date | datetime, /) -> GenericDate:
237
+ """Create a new RealDate instance from a
238
+ date or datetime object
239
+ """
240
+ return RealDate(source.year, source.month, source.day)
241
+
242
+
243
+ def from_native_type(
244
+ source: Any,
245
+ /,
246
+ *,
247
+ fmt: str = ISO_DATETIME_FORMAT_UTC,
248
+ past_bound: bool = False,
249
+ ) -> GenericDate:
250
+ """Create an InfinityDate or RealDate instance from string or another type,
251
+ assuming infinity in the latter case
252
+ """
253
+ if isinstance(source, str):
254
+ return from_datetime_object(datetime.strptime(source, fmt))
255
+ #
256
+ if source == -inf or source is None and past_bound:
257
+ return MIN
258
+ #
259
+ if source == inf or source is None and not past_bound:
260
+ return MAX
261
+ #
262
+ raise ValueError(f"Don’t know how to convert {source!r} into a date")
263
+
264
+
265
+ def fromtimestamp(timestamp: float) -> GenericDate:
266
+ """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
272
+ #
273
+ stdlib_date_object = date.fromtimestamp(timestamp)
274
+ return from_datetime_object(stdlib_date_object)
275
+
276
+
277
+ def fromordinal(ordinal: float | int) -> GenericDate:
278
+ """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
289
+ #
290
+ if not MIN_ORDINAL <= new_ordinal <= MAX_ORDINAL:
291
+ raise OverflowError("RealDate value out of range")
292
+ #
293
+ 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
+ # -----------------------------------------------------------------------------
309
+
310
+
311
+ def fromisoformat(source: str, /) -> GenericDate:
312
+ """Create an InfinityDate or RealDate instance from an iso format representation"""
313
+ lower_source_stripped = source.strip().lower()
314
+ if lower_source_stripped == INFINITE_DATE_DISPLAY:
315
+ return MAX
316
+ #
317
+ if lower_source_stripped == NEGATIVE_INFINITE_DATE_DISPLAY:
318
+ return MIN
319
+ #
320
+ return from_datetime_object(date.fromisoformat(source))
321
+
322
+
323
+ def fromisocalendar(year: int, week: int, weekday: int) -> GenericDate:
324
+ """Create a RealDate instance from an iso calendar date"""
325
+ return from_datetime_object(date.fromisocalendar(year, week, weekday))
326
+
327
+
328
+ def today() -> GenericDate:
329
+ """Today as RealDate object"""
330
+ return from_datetime_object(date.today())
@@ -0,0 +1,147 @@
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/
@@ -0,0 +1,6 @@
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,,
infdate/__main__.py DELETED
@@ -1,8 +0,0 @@
1
- """
2
- Script functionality
3
- """
4
-
5
-
6
- def hello() -> str:
7
- """hello pylint C0116"""
8
- return "Hello from infdate!"
@@ -1,42 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: infdate
3
- Version: 0.1.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
- The **Date** class provided in this package wraps the standard library’s
30
- **datetime.date** class and adds the capability to specify dates in positive
31
- (after everything else) or negative (before everything else) infinity,
32
- and to do calculations (add days, or subtract days or other **Date** instances)
33
- with these objects.
34
-
35
- For easier usage, differences are expressed as integers (1 = one day)
36
- or floats (inf and -inf _only_).
37
-
38
- These capabilities can come handy when dealing with API representations of dates,
39
- eg. in GitLab’s [Personal Access Tokens API].
40
-
41
- * * *
42
- [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
@@ -1,7 +0,0 @@
1
- infdate/__init__.py,sha256=_gJvzSUHx1bvs6xCWI9fhDniWhFR2RD5VQUaFKZoP7U,8890
2
- infdate/__main__.py,sha256=RtLjAiDLfFTpSFuU68nKhVfBUkxqDhhIY2jNxgL2cW4,113
3
- infdate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- infdate-0.1.0.dist-info/METADATA,sha256=p-zgj6Xlvz79e_QBIC0DrmHBilf83e-u7olcyyjLDA4,1821
5
- infdate-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
- infdate-0.1.0.dist-info/licenses/LICENSE,sha256=867pxriiObx28vCU1JsRtu3H9kUKyl54e0-xl1IIv3Y,913
7
- infdate-0.1.0.dist-info/RECORD,,