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 +267 -219
- infdate-0.2.1.dist-info/METADATA +147 -0
- infdate-0.2.1.dist-info/RECORD +6 -0
- infdate/__main__.py +0 -8
- infdate-0.1.0.dist-info/METADATA +0 -42
- infdate-0.1.0.dist-info/RECORD +0 -7
- {infdate-0.1.0.dist-info → infdate-0.2.1.dist-info}/WHEEL +0 -0
- {infdate-0.1.0.dist-info → infdate-0.2.1.dist-info}/licenses/LICENSE +0 -0
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
|
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
|
-
|
17
|
-
|
13
|
+
# -----------------------------------------------------------------------------
|
14
|
+
# Public module constants:
|
15
|
+
# display definitions, format strings, upper and loer ordinal boundaries
|
16
|
+
# -----------------------------------------------------------------------------
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
INFINITE_DATE_DISPLAY: Final[str] = "<inf>"
|
19
|
+
NEGATIVE_INFINITE_DATE_DISPLAY: Final[str] = "<-inf>"
|
21
20
|
|
22
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
-
|
73
|
-
|
74
|
-
return self.__ordinal
|
45
|
+
class GenericDate:
|
46
|
+
"""Base Date object derived from an ordinal"""
|
75
47
|
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
99
|
-
"""
|
100
|
-
|
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
|
110
|
-
"""
|
111
|
-
return self.
|
68
|
+
def __gt__(self: _GD, other: _GD, /) -> bool:
|
69
|
+
"""Rich comparison: greater"""
|
70
|
+
return self.__ordinal > other.toordinal()
|
112
71
|
|
113
|
-
def
|
114
|
-
"""
|
115
|
-
|
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
|
-
|
76
|
+
def __eq__(self: _GD, other, /) -> bool:
|
77
|
+
"""Rich comparison: equals"""
|
78
|
+
return self.__ordinal == other.toordinal()
|
129
79
|
|
130
|
-
def
|
131
|
-
"""
|
132
|
-
return self.
|
80
|
+
def __ne__(self: _GD, other, /) -> bool:
|
81
|
+
"""Rich comparison: does not equal"""
|
82
|
+
return self.__ordinal != other.toordinal()
|
133
83
|
|
134
|
-
def
|
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
|
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 (
|
98
|
+
for infinity_form in (inf, -inf):
|
142
99
|
if observed_item == infinity_form:
|
143
|
-
|
100
|
+
if observed_item == self.__ordinal:
|
101
|
+
return self
|
102
|
+
#
|
103
|
+
return fromordinal(observed_item)
|
144
104
|
#
|
145
105
|
#
|
146
106
|
#
|
147
|
-
|
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:
|
119
|
+
def __sub__(self: _GD, other: int | float, /) -> _GD: ...
|
151
120
|
@overload
|
152
|
-
def __sub__(self:
|
121
|
+
def __sub__(self: _GD, other: _GD, /) -> int | float: ...
|
153
122
|
@final
|
154
|
-
def __sub__(self:
|
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
|
126
|
+
return self._add_days(-other)
|
158
127
|
#
|
159
128
|
return self.__ordinal - other.toordinal()
|
160
129
|
|
161
|
-
def
|
162
|
-
"""
|
163
|
-
return self.
|
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
|
166
|
-
"""
|
167
|
-
return self
|
134
|
+
def __str__(self: _GD, /) -> str:
|
135
|
+
"""String display of the object"""
|
136
|
+
return self.isoformat()
|
168
137
|
|
169
|
-
def
|
170
|
-
"""
|
171
|
-
return self.
|
138
|
+
def isoformat(self: _GD, /) -> str:
|
139
|
+
"""Date representation in ISO format"""
|
140
|
+
return self.strftime(ISO_DATE_FORMAT)
|
172
141
|
|
173
|
-
def
|
174
|
-
"""
|
175
|
-
|
142
|
+
def strftime(self: _GD, fmt: str, /) -> str:
|
143
|
+
"""String representation of the date"""
|
144
|
+
raise NotImplementedError
|
176
145
|
|
177
|
-
def
|
178
|
-
"""
|
179
|
-
|
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
|
-
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
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.
|
206
|
+
return self._wrapped_date_object.strftime(fmt or ISO_DATE_FORMAT)
|
196
207
|
|
197
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
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
|
-
|
282
|
-
|
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
infdate-0.1.0.dist-info/METADATA
DELETED
@@ -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/
|
infdate-0.1.0.dist-info/RECORD
DELETED
@@ -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,,
|
File without changes
|
File without changes
|