infdate 0.1.0__tar.gz → 0.2.1__tar.gz

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-0.2.1/PKG-INFO ADDED
@@ -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,123 @@
1
+ # infdate
2
+
3
+ _Python module for date calculations implementing a concept of infinity_
4
+
5
+ ## Module description
6
+
7
+ ### Classes overview
8
+
9
+ └── GenericDate
10
+ ├── InfinityDate
11
+ └── RealDate
12
+
13
+
14
+ The base class **GenericDate** should not be instantiated but can be used as a type annotation.
15
+
16
+ **InfinityDate** can represent either positive or negative infinity.
17
+ The module-level constants **MIN** and **MAX** contain the two possible
18
+ **InfinityDate** instance variations.
19
+
20
+ **RealDate** instances represent real dates like the standard library’s
21
+ **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.
22
+
23
+ For any valid **RealDate** instance, the following is **True**:
24
+
25
+ ``` python
26
+ infdate.MIN < infdate.REAL_MIN <= real_date_instance <= infdate.REAL_MAX < infdate.MAX
27
+ ```
28
+
29
+ ### Module-level factory functions
30
+
31
+
32
+ The following factory methods from the **datetime.date** class
33
+ are provided as module-level functions:
34
+
35
+ * **fromtimestamp()** (also accepting **-math.inf** or **math.inf**)
36
+ * **fromordinal()** (also accepting **-math.inf** or **math.inf**)
37
+ * **fromisoformat()**
38
+ * **fromisocalendar()**
39
+ * **today()**
40
+
41
+ Two additional factory functions are provided:
42
+
43
+ * **from_datetime_object()** to create a **RealDate** instance from a
44
+ **datetime.date** or **datetime.datetime** instance
45
+
46
+ * **from_native_type()** to create an **InfinityDate** or **RealDate**
47
+ instance from a string, from **None**, **-math.inf** or **math.inf**.
48
+
49
+ This can come handy when dealing with API representations of dates,
50
+ eg. in GitLab’s [Personal Access Tokens API].
51
+
52
+
53
+ ### Differences between infdate classes and datetime.date
54
+
55
+ 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):
56
+
57
+ * The **.toordinal()** method returns **float** instead of **int**
58
+
59
+ * The **resolution** attribute is **1.0** instead of **datetime.timedelta(days=1)**
60
+ but also represents exactly one day.
61
+
62
+ * Subtracting a date from an **InfinityDate** or **RealDate** always returns a float instead of a **datetime.timedelta** instance.
63
+
64
+ * Likewise, you cannot add or subtract **datetime.timedelta** instances
65
+ from an **InfinityDate** or **RealDate**, only **float** or **int** (support for adding and subtracting datetime.timedelta instances might be added in the future).
66
+
67
+
68
+ ## Example usage
69
+
70
+ ``` pycon
71
+ >>> import infdate
72
+ >>> today = infdate.today()
73
+ >>> today
74
+ RealDate(2025, 6, 25)
75
+ >>> print(today)
76
+ 2025-06-25
77
+ >>> print(f"US date notation {today:%m/%d/%y}")
78
+ US date notation 06/25/25
79
+ >>> today.ctime()
80
+ 'Wed Jun 25 00:00:00 2025'
81
+ >>> today.isocalendar()
82
+ datetime.IsoCalendarDate(year=2025, week=26, weekday=3)
83
+ >>>
84
+ >>> yesterday = today - 1
85
+ >>> yesterday.ctime()
86
+ 'Tue Jun 24 00:00:00 2025'
87
+ >>>
88
+ >>> today - yesterday
89
+ 1.0
90
+ >>> infdate.MIN
91
+ InfinityDate(past_bound=True)
92
+ >>> infdate.MAX
93
+ InfinityDate(past_bound=False)
94
+ >>> infdate.MAX - today
95
+ inf
96
+ >>> infdate.MAX - infdate.MIN
97
+ inf
98
+ ```
99
+
100
+ **InfinityDate** and **RealDate** instances can be compared with each other, and also with **datetime.date** instances.
101
+
102
+ 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**
103
+ currently still raises a **TypeError**):
104
+
105
+ >>> from datetime import date
106
+ >>> stdlib_today = date.today()
107
+ >>> today == stdlib_today
108
+ True
109
+ >>> yesterday < stdlib_today
110
+ True
111
+ >>> yesterday - stdlib_today
112
+ -1.0
113
+ >>> stdlib_today - yesterday
114
+ Traceback (most recent call last):
115
+ File "<python-input-22>", line 1, in <module>
116
+ stdlib_today - yesterday
117
+ ~~~~~~~~~~~~~^~~~~~~~~~~
118
+ TypeError: unsupported operand type(s) for -: 'datetime.date' and 'RealDate'
119
+
120
+
121
+
122
+ * * *
123
+ [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "infdate"
3
- version = "0.1.0"
3
+ version = "0.2.1"
4
4
  description = "Date object wrapper supporting infinity"
5
5
  readme = "README.md"
6
6
  authors = [
7
- { name = "Rainer Schwarzbach", email = "rainer@blackstream.de" }
7
+ { name = "Rainer Schwarzbach", email = "rainer@blackstream.de" },
8
8
  ]
9
9
  requires-python = ">=3.11"
10
10
  classifiers = [
@@ -0,0 +1,8 @@
1
+ {
2
+ "ci_pipeline_created_at": "2025-06-26T07:29:16Z",
3
+ "ci_pipeline_id": "1890497916",
4
+ "ci_pipeline_url": "https://gitlab.com/blackstream-x/infdate/-/pipelines/1890497916",
5
+ "ci_project_title": "infdate",
6
+ "ci_project_url": "https://gitlab.com/blackstream-x/infdate",
7
+ "ci_commit_sha": "7fd06146e7f50df22dc97ddaa53005459d17a1d4"
8
+ }
@@ -4,6 +4,6 @@ _provided by [glsr-present](https://pypi.org/project/glsr-present/) 0.3.6_
4
4
 
5
5
  [infdate](https://gitlab.com/blackstream-x/infdate)
6
6
  built with pipeline
7
- [1884836730](https://gitlab.com/blackstream-x/infdate/-/pipelines/1884836730)
8
- (build started 2025-06-23T16:10:13Z)
7
+ [1890497916](https://gitlab.com/blackstream-x/infdate/-/pipelines/1890497916)
8
+ (build started 2025-06-26T07:29:16Z)
9
9
 
@@ -0,0 +1 @@
1
+ <?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="35" time="0.784" timestamp="2025-06-26T07:29:48.605912+00:00" hostname="runner-nn8vmrs9z-project-71015407-concurrent-0"><testcase classname="tests.test_infdate.GenericDate" name="test_add" time="0.009" /><testcase classname="tests.test_infdate.GenericDate" name="test_add_days" time="0.005" /><testcase classname="tests.test_infdate.GenericDate" name="test_bool" time="0.001" /><testcase classname="tests.test_infdate.GenericDate" name="test_eq" time="0.001" /><testcase classname="tests.test_infdate.GenericDate" name="test_ge" time="0.056" /><testcase classname="tests.test_infdate.GenericDate" name="test_gt" time="0.047" /><testcase classname="tests.test_infdate.GenericDate" name="test_hash" time="0.001" /><testcase classname="tests.test_infdate.GenericDate" name="test_isoformat" time="0.001" /><testcase classname="tests.test_infdate.GenericDate" name="test_le" time="0.056" /><testcase classname="tests.test_infdate.GenericDate" name="test_lt" time="0.046" /><testcase classname="tests.test_infdate.GenericDate" name="test_ne" time="0.046" /><testcase classname="tests.test_infdate.GenericDate" name="test_replace" time="0.001" /><testcase classname="tests.test_infdate.GenericDate" name="test_repr" time="0.001" /><testcase classname="tests.test_infdate.GenericDate" name="test_str" time="0.001" /><testcase classname="tests.test_infdate.GenericDate" name="test_strftime" time="0.001" /><testcase classname="tests.test_infdate.GenericDate" name="test_sub_date" time="0.001" /><testcase classname="tests.test_infdate.GenericDate" name="test_sub_number" time="0.004" /><testcase classname="tests.test_infdate.GenericDate" name="test_toordinal" time="0.001" /><testcase classname="tests.test_infdate.InfinityDate" name="test_replace" time="0.001" /><testcase classname="tests.test_infdate.InfinityDate" name="test_repr" time="0.001" /><testcase classname="tests.test_infdate.InfinityDate" name="test_strftime" time="0.001" /><testcase classname="tests.test_infdate.RealDate" name="test_attributes" time="0.002" /><testcase classname="tests.test_infdate.RealDate" name="test_bool" time="0.001" /><testcase classname="tests.test_infdate.RealDate" name="test_proxied_methods" time="0.001" /><testcase classname="tests.test_infdate.RealDate" name="test_replace" time="0.002" /><testcase classname="tests.test_infdate.RealDate" name="test_repr" time="0.001" /><testcase classname="tests.test_infdate.RealDate" name="test_strftime" time="0.003" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_from_datetime_object" time="0.002" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_from_native_type" time="0.006" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_fromisocalendar" time="0.002" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_fromisoformat" time="0.002" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_fromordinal" time="0.003" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_fromtimestamp" time="0.003" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_fromtimestamp_errors" time="0.001" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_today" time="0.002" /></testsuite></testsuites>
@@ -0,0 +1,330 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ infdate: a wrapper around standard library’s datetime.date objects,
5
+ capable of representing positive and negative infinity
6
+ """
7
+
8
+ from datetime import date, datetime
9
+ from math import inf
10
+ from typing import final, overload, Any, Final, TypeVar
11
+
12
+
13
+ # -----------------------------------------------------------------------------
14
+ # Public module constants:
15
+ # display definitions, format strings, upper and loer ordinal boundaries
16
+ # -----------------------------------------------------------------------------
17
+
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()
30
+
31
+
32
+ # -----------------------------------------------------------------------------
33
+ # Internal module constants:
34
+ # TypeVar for GenericDate
35
+ # -----------------------------------------------------------------------------
36
+
37
+ _GD = TypeVar("_GD", bound="GenericDate")
38
+
39
+
40
+ # -----------------------------------------------------------------------------
41
+ # Classes
42
+ # -----------------------------------------------------------------------------
43
+
44
+
45
+ class GenericDate:
46
+ """Base Date object derived from an ordinal"""
47
+
48
+ # pylint: disable=invalid-name
49
+ resolution: Final = 1
50
+ # pylint: enable=invalid-name
51
+
52
+ def __init__(self, ordinal: float, /) -> None:
53
+ """Create a date-like object"""
54
+ self.__ordinal = ordinal
55
+
56
+ def toordinal(self: _GD) -> float:
57
+ """to ordinal (almost like date.toordinal())"""
58
+ return self.__ordinal
59
+
60
+ def __lt__(self: _GD, other: _GD, /) -> bool:
61
+ """Rich comparison: less"""
62
+ return self.__ordinal < other.toordinal()
63
+
64
+ def __le__(self: _GD, other: _GD, /) -> bool:
65
+ """Rich comparison: less or equal"""
66
+ return self < other or self == other
67
+
68
+ def __gt__(self: _GD, other: _GD, /) -> bool:
69
+ """Rich comparison: greater"""
70
+ return self.__ordinal > other.toordinal()
71
+
72
+ def __ge__(self: _GD, other: _GD, /) -> bool:
73
+ """Rich comparison: greater or equal"""
74
+ return self > other or self == other
75
+
76
+ def __eq__(self: _GD, other, /) -> bool:
77
+ """Rich comparison: equals"""
78
+ return self.__ordinal == other.toordinal()
79
+
80
+ def __ne__(self: _GD, other, /) -> bool:
81
+ """Rich comparison: does not equal"""
82
+ return self.__ordinal != other.toordinal()
83
+
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:
89
+ """hash value"""
90
+ return hash(f"date with ordinal {self.__ordinal}")
91
+
92
+ def _add_days(self: _GD, delta: int | float, /):
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.
97
+ for observed_item in (delta, self.__ordinal):
98
+ for infinity_form in (inf, -inf):
99
+ if observed_item == infinity_form:
100
+ if observed_item == self.__ordinal:
101
+ return self
102
+ #
103
+ return fromordinal(observed_item)
104
+ #
105
+ #
106
+ #
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)
117
+
118
+ @overload
119
+ def __sub__(self: _GD, other: int | float, /) -> _GD: ...
120
+ @overload
121
+ def __sub__(self: _GD, other: _GD, /) -> int | float: ...
122
+ @final
123
+ def __sub__(self: _GD, other: _GD | int | float, /) -> _GD | int | float:
124
+ """subtract other, respecting possibly nondeterministic values"""
125
+ if isinstance(other, (int, float)):
126
+ return self._add_days(-other)
127
+ #
128
+ return self.__ordinal - other.toordinal()
129
+
130
+ def __repr__(self: _GD, /) -> str:
131
+ """String representation of the object"""
132
+ return f"{self.__class__.__name__}({repr(self.__ordinal)})"
133
+
134
+ def __str__(self: _GD, /) -> str:
135
+ """String display of the object"""
136
+ return self.isoformat()
137
+
138
+ def isoformat(self: _GD, /) -> str:
139
+ """Date representation in ISO format"""
140
+ return self.strftime(ISO_DATE_FORMAT)
141
+
142
+ def strftime(self: _GD, fmt: str, /) -> str:
143
+ """String representation of the date"""
144
+ raise NotImplementedError
145
+
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
149
+
150
+
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:
160
+ """String representation of the object"""
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
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"""
182
+
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:
205
+ """String representation of the date"""
206
+ return self._wrapped_date_object.strftime(fmt or ISO_DATE_FORMAT)
207
+
208
+ __format__ = strftime
209
+
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
+ )
220
+
221
+
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())