infdate 0.1.0__tar.gz → 0.2.0__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.0/PKG-INFO +141 -0
- infdate-0.2.0/README.md +117 -0
- {infdate-0.1.0 → infdate-0.2.0}/pyproject.toml +2 -2
- infdate-0.2.0/reports/docs/build-info.json +8 -0
- {infdate-0.1.0 → infdate-0.2.0}/reports/docs/build-info.md +2 -2
- infdate-0.2.0/reports/test-junit.xml +1 -0
- infdate-0.2.0/src/infdate/__init__.py +288 -0
- infdate-0.2.0/tests/test_infdate.py +793 -0
- infdate-0.1.0/PKG-INFO +0 -42
- infdate-0.1.0/README.md +0 -18
- infdate-0.1.0/reports/docs/build-info.json +0 -8
- infdate-0.1.0/reports/test-junit.xml +0 -1
- infdate-0.1.0/src/infdate/__init__.py +0 -282
- infdate-0.1.0/src/infdate/__main__.py +0 -8
- infdate-0.1.0/tests/test_infdate.py +0 -341
- {infdate-0.1.0 → infdate-0.2.0}/.gitignore +0 -0
- {infdate-0.1.0 → infdate-0.2.0}/.gitlab-ci.yml +0 -0
- {infdate-0.1.0 → infdate-0.2.0}/LICENSE +0 -0
- {infdate-0.1.0 → infdate-0.2.0}/src/infdate/py.typed +0 -0
- {infdate-0.1.0 → infdate-0.2.0}/tests/__init__.py +0 -0
- {infdate-0.1.0 → infdate-0.2.0}/tools/ci_get_pypi_token.py +0 -0
- {infdate-0.1.0 → infdate-0.2.0}/tools/codestyle.sh +0 -0
- {infdate-0.1.0 → infdate-0.2.0}/tools/pytest.sh +0 -0
infdate-0.2.0/PKG-INFO
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: infdate
|
3
|
+
Version: 0.2.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
|
+
## Module description
|
30
|
+
|
31
|
+
Class hierarchy:
|
32
|
+
|
33
|
+
└── GenericDate
|
34
|
+
├── InfinityDate
|
35
|
+
└── RealDate
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
**InfinityDate** can represent either positive or negative infinity.
|
40
|
+
The module-level constants **MIN** and **MAX** contain the two possible
|
41
|
+
**InfinityDate** instance variations.
|
42
|
+
|
43
|
+
**RealDate** instances represent real dates like the standard library’s
|
44
|
+
**datetime.date** class, with mostly equal or similöar semantics.
|
45
|
+
|
46
|
+
For any valid **RealDate** instance, the following is **True**:
|
47
|
+
|
48
|
+
``` python
|
49
|
+
infdate.MIN < infdate.RealDate(1, 1, 1) <= real_date_instance <= infdate.RealDate(9999, 12, 31) < infdate.MAX
|
50
|
+
```
|
51
|
+
|
52
|
+
The following factory methods from the **datetime.date** class
|
53
|
+
are provided as module-level functions:
|
54
|
+
|
55
|
+
* **fromordinal()** (also accepting **-math.inf** or **math.inf**)
|
56
|
+
* **fromisoformat()**
|
57
|
+
* **fromisocalendar()**
|
58
|
+
* **today()**
|
59
|
+
|
60
|
+
**fromtimestamp()** is still missing by mistake.
|
61
|
+
|
62
|
+
Two additional factory functions are provided in the module:
|
63
|
+
|
64
|
+
* **from_datetime_object()** to create a **RealDate** instance from a
|
65
|
+
**datetime.date** or **datetime.datetime** instance
|
66
|
+
|
67
|
+
* **from_native_type()** to create an **InfinityDate** or **RealDate**
|
68
|
+
instance from a string, from **None**, **-math.inf** or **math.inf**.
|
69
|
+
|
70
|
+
This can come handy when dealing with API representations of dates,
|
71
|
+
eg. in GitLab’s [Personal Access Tokens API].
|
72
|
+
|
73
|
+
Some notable difference from the **datetime.date** class:
|
74
|
+
|
75
|
+
* The **.toordinal()** method returns **float** instead of **int**
|
76
|
+
|
77
|
+
* The **resolution** attribute is **1.0** instead of **datetime.timedelta(days=1)**
|
78
|
+
but also represents exactly one day.
|
79
|
+
|
80
|
+
* Subtracting a date from an **InfinityDate** or **RealDate** always returns a float
|
81
|
+
(because **math.inf** is a float), not a **datetime.timedelta** instance.
|
82
|
+
|
83
|
+
* Likewise, you cannot add or subtract **datetime.timedelta** instances
|
84
|
+
from n **InfinityDate** or **RealDate**, only **loat** or **int**.
|
85
|
+
|
86
|
+
|
87
|
+
## Example usage
|
88
|
+
|
89
|
+
``` pycon
|
90
|
+
>>> import infdate
|
91
|
+
>>> today = infdate.today()
|
92
|
+
>>> today
|
93
|
+
RealDate(2025, 6, 25)
|
94
|
+
>>> print(today)
|
95
|
+
2025-06-25
|
96
|
+
>>> print(f"US date notation {today:%m/%d/%y}")
|
97
|
+
US date notation 06/25/25
|
98
|
+
>>> today.ctime()
|
99
|
+
'Wed Jun 25 00:00:00 2025'
|
100
|
+
>>> today.isocalendar()
|
101
|
+
datetime.IsoCalendarDate(year=2025, week=26, weekday=3)
|
102
|
+
>>>
|
103
|
+
>>> yesterday = today - 1
|
104
|
+
>>> yesterday.ctime()
|
105
|
+
'Tue Jun 24 00:00:00 2025'
|
106
|
+
>>>
|
107
|
+
>>> today - yesterday
|
108
|
+
1.0
|
109
|
+
>>> infdate.MIN
|
110
|
+
InfinityDate(past_bound=True)
|
111
|
+
>>> infdate.MAX
|
112
|
+
InfinityDate(past_bound=False)
|
113
|
+
>>> infdate.MAX - today
|
114
|
+
inf
|
115
|
+
>>> infdate.MAX - infdate.MIN
|
116
|
+
inf
|
117
|
+
```
|
118
|
+
|
119
|
+
You can compare **InfinityDate**, **RealDate** and **datetime.date** instances,
|
120
|
+
and subtract them from each other (although currently, `__rsub__` is not implemented yet,
|
121
|
+
so subtracting an **InfinityDate** or **RealDate** from a **datetime.date**
|
122
|
+
still gives a **TypeError**):
|
123
|
+
|
124
|
+
>>> from datetime import date
|
125
|
+
>>> stdlib_today = date.today()
|
126
|
+
>>> today == stdlib_today
|
127
|
+
True
|
128
|
+
>>> yesterday < stdlib_today
|
129
|
+
True
|
130
|
+
>>> yesterday - stdlib_today
|
131
|
+
-1.0
|
132
|
+
>>> stdlib_today - yesterday
|
133
|
+
Traceback (most recent call last):
|
134
|
+
File "<python-input-22>", line 1, in <module>
|
135
|
+
stdlib_today - yesterday
|
136
|
+
~~~~~~~~~~~~~^~~~~~~~~~~
|
137
|
+
TypeError: unsupported operand type(s) for -: 'datetime.date' and 'RealDate'
|
138
|
+
|
139
|
+
|
140
|
+
* * *
|
141
|
+
[Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
|
infdate-0.2.0/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# infdate
|
2
|
+
|
3
|
+
_Python module for date calculations implementing a concept of infinity_
|
4
|
+
|
5
|
+
## Module description
|
6
|
+
|
7
|
+
Class hierarchy:
|
8
|
+
|
9
|
+
└── GenericDate
|
10
|
+
├── InfinityDate
|
11
|
+
└── RealDate
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
**InfinityDate** can represent either positive or negative infinity.
|
16
|
+
The module-level constants **MIN** and **MAX** contain the two possible
|
17
|
+
**InfinityDate** instance variations.
|
18
|
+
|
19
|
+
**RealDate** instances represent real dates like the standard library’s
|
20
|
+
**datetime.date** class, with mostly equal or similöar semantics.
|
21
|
+
|
22
|
+
For any valid **RealDate** instance, the following is **True**:
|
23
|
+
|
24
|
+
``` python
|
25
|
+
infdate.MIN < infdate.RealDate(1, 1, 1) <= real_date_instance <= infdate.RealDate(9999, 12, 31) < infdate.MAX
|
26
|
+
```
|
27
|
+
|
28
|
+
The following factory methods from the **datetime.date** class
|
29
|
+
are provided as module-level functions:
|
30
|
+
|
31
|
+
* **fromordinal()** (also accepting **-math.inf** or **math.inf**)
|
32
|
+
* **fromisoformat()**
|
33
|
+
* **fromisocalendar()**
|
34
|
+
* **today()**
|
35
|
+
|
36
|
+
**fromtimestamp()** is still missing by mistake.
|
37
|
+
|
38
|
+
Two additional factory functions are provided in the module:
|
39
|
+
|
40
|
+
* **from_datetime_object()** to create a **RealDate** instance from a
|
41
|
+
**datetime.date** or **datetime.datetime** instance
|
42
|
+
|
43
|
+
* **from_native_type()** to create an **InfinityDate** or **RealDate**
|
44
|
+
instance from a string, from **None**, **-math.inf** or **math.inf**.
|
45
|
+
|
46
|
+
This can come handy when dealing with API representations of dates,
|
47
|
+
eg. in GitLab’s [Personal Access Tokens API].
|
48
|
+
|
49
|
+
Some notable difference from the **datetime.date** class:
|
50
|
+
|
51
|
+
* The **.toordinal()** method returns **float** instead of **int**
|
52
|
+
|
53
|
+
* The **resolution** attribute is **1.0** instead of **datetime.timedelta(days=1)**
|
54
|
+
but also represents exactly one day.
|
55
|
+
|
56
|
+
* Subtracting a date from an **InfinityDate** or **RealDate** always returns a float
|
57
|
+
(because **math.inf** is a float), not a **datetime.timedelta** instance.
|
58
|
+
|
59
|
+
* Likewise, you cannot add or subtract **datetime.timedelta** instances
|
60
|
+
from n **InfinityDate** or **RealDate**, only **loat** or **int**.
|
61
|
+
|
62
|
+
|
63
|
+
## Example usage
|
64
|
+
|
65
|
+
``` pycon
|
66
|
+
>>> import infdate
|
67
|
+
>>> today = infdate.today()
|
68
|
+
>>> today
|
69
|
+
RealDate(2025, 6, 25)
|
70
|
+
>>> print(today)
|
71
|
+
2025-06-25
|
72
|
+
>>> print(f"US date notation {today:%m/%d/%y}")
|
73
|
+
US date notation 06/25/25
|
74
|
+
>>> today.ctime()
|
75
|
+
'Wed Jun 25 00:00:00 2025'
|
76
|
+
>>> today.isocalendar()
|
77
|
+
datetime.IsoCalendarDate(year=2025, week=26, weekday=3)
|
78
|
+
>>>
|
79
|
+
>>> yesterday = today - 1
|
80
|
+
>>> yesterday.ctime()
|
81
|
+
'Tue Jun 24 00:00:00 2025'
|
82
|
+
>>>
|
83
|
+
>>> today - yesterday
|
84
|
+
1.0
|
85
|
+
>>> infdate.MIN
|
86
|
+
InfinityDate(past_bound=True)
|
87
|
+
>>> infdate.MAX
|
88
|
+
InfinityDate(past_bound=False)
|
89
|
+
>>> infdate.MAX - today
|
90
|
+
inf
|
91
|
+
>>> infdate.MAX - infdate.MIN
|
92
|
+
inf
|
93
|
+
```
|
94
|
+
|
95
|
+
You can compare **InfinityDate**, **RealDate** and **datetime.date** instances,
|
96
|
+
and subtract them from each other (although currently, `__rsub__` is not implemented yet,
|
97
|
+
so subtracting an **InfinityDate** or **RealDate** from a **datetime.date**
|
98
|
+
still gives a **TypeError**):
|
99
|
+
|
100
|
+
>>> from datetime import date
|
101
|
+
>>> stdlib_today = date.today()
|
102
|
+
>>> today == stdlib_today
|
103
|
+
True
|
104
|
+
>>> yesterday < stdlib_today
|
105
|
+
True
|
106
|
+
>>> yesterday - stdlib_today
|
107
|
+
-1.0
|
108
|
+
>>> stdlib_today - yesterday
|
109
|
+
Traceback (most recent call last):
|
110
|
+
File "<python-input-22>", line 1, in <module>
|
111
|
+
stdlib_today - yesterday
|
112
|
+
~~~~~~~~~~~~~^~~~~~~~~~~
|
113
|
+
TypeError: unsupported operand type(s) for -: 'datetime.date' and 'RealDate'
|
114
|
+
|
115
|
+
|
116
|
+
* * *
|
117
|
+
[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.
|
3
|
+
version = "0.2.0"
|
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-25T15:48:59Z",
|
3
|
+
"ci_pipeline_id": "1889338569",
|
4
|
+
"ci_pipeline_url": "https://gitlab.com/blackstream-x/infdate/-/pipelines/1889338569",
|
5
|
+
"ci_project_title": "infdate",
|
6
|
+
"ci_project_url": "https://gitlab.com/blackstream-x/infdate",
|
7
|
+
"ci_commit_sha": "2f339a6673ac21ac72292598799f6c602866afaf"
|
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
|
-
[
|
8
|
-
(build started 2025-06-
|
7
|
+
[1889338569](https://gitlab.com/blackstream-x/infdate/-/pipelines/1889338569)
|
8
|
+
(build started 2025-06-25T15:48:59Z)
|
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="33" time="0.768" timestamp="2025-06-25T15:49:40.687820+00:00" hostname="runner-xxurkrix-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.055" /><testcase classname="tests.test_infdate.GenericDate" name="test_gt" time="0.046" /><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.055" /><testcase classname="tests.test_infdate.GenericDate" name="test_lt" time="0.045" /><testcase classname="tests.test_infdate.GenericDate" name="test_ne" time="0.045" /><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.001" /><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.005" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_fromisocalendar" time="0.003" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_fromisoformat" time="0.002" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_fromordinal" time="0.002" /><testcase classname="tests.test_infdate.FactoryFunctions" name="test_today" time="0.002" /></testsuite></testsuites>
|
@@ -0,0 +1,288 @@
|
|
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
|
+
INFINITE_DATE_DISPLAY: Final[str] = "<inf>"
|
14
|
+
NEGATIVE_INFINITE_DATE_DISPLAY: Final[str] = "<-inf>"
|
15
|
+
|
16
|
+
INFINITY_SYMBOL: Final[str] = "∝"
|
17
|
+
UP_TO_SYMBOL: Final[str] = "⤒"
|
18
|
+
FROM_ON_SYMBOL: Final[str] = "↥"
|
19
|
+
|
20
|
+
ISO_DATE_FORMAT: Final[str] = "%Y-%m-%d"
|
21
|
+
ISO_DATETIME_FORMAT_UTC: Final[str] = f"{ISO_DATE_FORMAT}T%H:%M:%S.%fZ"
|
22
|
+
|
23
|
+
MIN_ORDINAL: Final[int] = date.min.toordinal()
|
24
|
+
MAX_ORDINAL: Final[int] = date.max.toordinal()
|
25
|
+
|
26
|
+
|
27
|
+
_GD = TypeVar("_GD", bound="GenericDate")
|
28
|
+
|
29
|
+
|
30
|
+
class GenericDate:
|
31
|
+
"""Base Date object derived from an ordinal"""
|
32
|
+
|
33
|
+
# pylint: disable=invalid-name
|
34
|
+
resolution: Final = 1
|
35
|
+
# pylint: enable=invalid-name
|
36
|
+
|
37
|
+
def __init__(self, ordinal: float, /) -> None:
|
38
|
+
"""Create a date-like object"""
|
39
|
+
self.__ordinal = ordinal
|
40
|
+
|
41
|
+
def toordinal(self: _GD) -> float:
|
42
|
+
"""to ordinal (almost like date.toordinal())"""
|
43
|
+
return self.__ordinal
|
44
|
+
|
45
|
+
def __lt__(self: _GD, other: _GD, /) -> bool:
|
46
|
+
"""Rich comparison: less"""
|
47
|
+
return self.__ordinal < other.toordinal()
|
48
|
+
|
49
|
+
def __le__(self: _GD, other: _GD, /) -> bool:
|
50
|
+
"""Rich comparison: less or equal"""
|
51
|
+
return self < other or self == other
|
52
|
+
|
53
|
+
def __gt__(self: _GD, other: _GD, /) -> bool:
|
54
|
+
"""Rich comparison: greater"""
|
55
|
+
return self.__ordinal > other.toordinal()
|
56
|
+
|
57
|
+
def __ge__(self: _GD, other: _GD, /) -> bool:
|
58
|
+
"""Rich comparison: greater or equal"""
|
59
|
+
return self > other or self == other
|
60
|
+
|
61
|
+
def __eq__(self: _GD, other, /) -> bool:
|
62
|
+
"""Rich comparison: equals"""
|
63
|
+
return self.__ordinal == other.toordinal()
|
64
|
+
|
65
|
+
def __ne__(self: _GD, other, /) -> bool:
|
66
|
+
"""Rich comparison: does not equal"""
|
67
|
+
return self.__ordinal != other.toordinal()
|
68
|
+
|
69
|
+
def __bool__(self: _GD, /) -> bool:
|
70
|
+
"""True only if a real date is wrapped"""
|
71
|
+
return False
|
72
|
+
|
73
|
+
def __hash__(self: _GD, /) -> int:
|
74
|
+
"""hash value"""
|
75
|
+
return hash(f"date with ordinal {self.__ordinal}")
|
76
|
+
|
77
|
+
def _add_days(self: _GD, delta: int | float, /):
|
78
|
+
"""Add other, respecting maybe-nondeterministic values"""
|
79
|
+
# Check for infinity in either self or delta,
|
80
|
+
# and return a matching InfinityDate if found.
|
81
|
+
# Re-use existing objects if possible.
|
82
|
+
for observed_item in (delta, self.__ordinal):
|
83
|
+
for infinity_form in (inf, -inf):
|
84
|
+
if observed_item == infinity_form:
|
85
|
+
if observed_item == self.__ordinal:
|
86
|
+
return self
|
87
|
+
#
|
88
|
+
return fromordinal(observed_item)
|
89
|
+
#
|
90
|
+
#
|
91
|
+
#
|
92
|
+
# +/- 0 corner case
|
93
|
+
if not delta:
|
94
|
+
return self
|
95
|
+
#
|
96
|
+
# Return a RealDate instance if possible
|
97
|
+
return fromordinal(self.__ordinal + delta)
|
98
|
+
|
99
|
+
def __add__(self: _GD, delta: int | float, /) -> _GD:
|
100
|
+
"""gd_instance1 + number capability"""
|
101
|
+
return self._add_days(delta)
|
102
|
+
|
103
|
+
@overload
|
104
|
+
def __sub__(self: _GD, other: int | float, /) -> _GD: ...
|
105
|
+
@overload
|
106
|
+
def __sub__(self: _GD, other: _GD, /) -> int | float: ...
|
107
|
+
@final
|
108
|
+
def __sub__(self: _GD, other: _GD | int | float, /) -> _GD | int | float:
|
109
|
+
"""subtract other, respecting possibly nondeterministic values"""
|
110
|
+
if isinstance(other, (int, float)):
|
111
|
+
return self._add_days(-other)
|
112
|
+
#
|
113
|
+
return self.__ordinal - other.toordinal()
|
114
|
+
|
115
|
+
def __repr__(self: _GD, /) -> str:
|
116
|
+
"""String representation of the object"""
|
117
|
+
return f"{self.__class__.__name__}({repr(self.__ordinal)})"
|
118
|
+
|
119
|
+
def __str__(self: _GD, /) -> str:
|
120
|
+
"""String display of the object"""
|
121
|
+
return self.isoformat()
|
122
|
+
|
123
|
+
def isoformat(self: _GD, /) -> str:
|
124
|
+
"""Date representation in ISO format"""
|
125
|
+
return self.strftime(ISO_DATE_FORMAT)
|
126
|
+
|
127
|
+
def strftime(self: _GD, fmt: str, /) -> str:
|
128
|
+
"""String representation of the date"""
|
129
|
+
raise NotImplementedError
|
130
|
+
|
131
|
+
def replace(self: _GD, /, year: int = 0, month: int = 0, day: int = 0) -> _GD:
|
132
|
+
"""Return a copy with year, month, and/or date replaced"""
|
133
|
+
raise NotImplementedError
|
134
|
+
|
135
|
+
|
136
|
+
class InfinityDate(GenericDate):
|
137
|
+
"""Infinity Date object"""
|
138
|
+
|
139
|
+
def __init__(self, /, *, past_bound: bool = False) -> None:
|
140
|
+
"""Store -inf or inf"""
|
141
|
+
ordinal = -inf if past_bound else inf
|
142
|
+
super().__init__(ordinal)
|
143
|
+
|
144
|
+
def __repr__(self, /) -> str:
|
145
|
+
"""String representation of the object"""
|
146
|
+
return f"{self.__class__.__name__}(past_bound={self.toordinal() == -inf})"
|
147
|
+
|
148
|
+
def strftime(self, fmt: str, /) -> str:
|
149
|
+
"""String representation of the date"""
|
150
|
+
if self.toordinal() == inf:
|
151
|
+
return INFINITE_DATE_DISPLAY
|
152
|
+
#
|
153
|
+
return NEGATIVE_INFINITE_DATE_DISPLAY
|
154
|
+
|
155
|
+
__format__ = strftime
|
156
|
+
|
157
|
+
def replace(self, /, year: int = 0, month: int = 0, day: int = 0):
|
158
|
+
"""Not supported in this class"""
|
159
|
+
raise TypeError(
|
160
|
+
f"{self.__class__.__name__} instances do not support .replace()"
|
161
|
+
)
|
162
|
+
|
163
|
+
|
164
|
+
# pylint: disable=too-many-instance-attributes
|
165
|
+
class RealDate(GenericDate):
|
166
|
+
"""Real (deterministic) Date object based on date"""
|
167
|
+
|
168
|
+
def __init__(self, year: int, month: int, day: int) -> None:
|
169
|
+
"""Create a date-like object"""
|
170
|
+
self._wrapped_date_object = date(year, month, day)
|
171
|
+
self.year = year
|
172
|
+
self.month = month
|
173
|
+
self.day = day
|
174
|
+
super().__init__(float(self._wrapped_date_object.toordinal()))
|
175
|
+
self.timetuple = self._wrapped_date_object.timetuple
|
176
|
+
self.weekday = self._wrapped_date_object.weekday
|
177
|
+
self.isoweekday = self._wrapped_date_object.isoweekday
|
178
|
+
self.isocalendar = self._wrapped_date_object.isocalendar
|
179
|
+
self.ctime = self._wrapped_date_object.ctime
|
180
|
+
|
181
|
+
def __bool__(self, /) -> bool:
|
182
|
+
"""True if a real date is wrapped"""
|
183
|
+
return True
|
184
|
+
|
185
|
+
def __repr__(self, /) -> str:
|
186
|
+
"""String representation of the object"""
|
187
|
+
return f"{self.__class__.__name__}({self.year}, {self.month}, {self.day})"
|
188
|
+
|
189
|
+
def strftime(self, fmt: str, /) -> str:
|
190
|
+
"""String representation of the date"""
|
191
|
+
return self._wrapped_date_object.strftime(fmt or ISO_DATE_FORMAT)
|
192
|
+
|
193
|
+
__format__ = strftime
|
194
|
+
|
195
|
+
def replace(self, /, year: int = 0, month: int = 0, day: int = 0):
|
196
|
+
"""Return a copy with year, month, and/or date replaced"""
|
197
|
+
internal_object = self._wrapped_date_object
|
198
|
+
return from_datetime_object(
|
199
|
+
internal_object.replace(
|
200
|
+
year=year or internal_object.year,
|
201
|
+
month=month or internal_object.month,
|
202
|
+
day=day or internal_object.day,
|
203
|
+
)
|
204
|
+
)
|
205
|
+
|
206
|
+
|
207
|
+
# Absolute minimum and maximum dates
|
208
|
+
MIN: Final[GenericDate] = InfinityDate(past_bound=True)
|
209
|
+
MAX: Final[GenericDate] = InfinityDate(past_bound=False)
|
210
|
+
|
211
|
+
|
212
|
+
# Factory functions
|
213
|
+
|
214
|
+
|
215
|
+
def from_datetime_object(source: date | datetime, /) -> GenericDate:
|
216
|
+
"""Create a new RealDate instance from a
|
217
|
+
date or datetime object
|
218
|
+
"""
|
219
|
+
return RealDate(source.year, source.month, source.day)
|
220
|
+
|
221
|
+
|
222
|
+
def from_native_type(
|
223
|
+
source: Any,
|
224
|
+
/,
|
225
|
+
*,
|
226
|
+
fmt: str = ISO_DATETIME_FORMAT_UTC,
|
227
|
+
past_bound: bool = False,
|
228
|
+
) -> GenericDate:
|
229
|
+
"""Create an InfinityDate or RealDate instance from string or another type,
|
230
|
+
assuming infinity in the latter case
|
231
|
+
"""
|
232
|
+
if isinstance(source, str):
|
233
|
+
return from_datetime_object(datetime.strptime(source, fmt))
|
234
|
+
#
|
235
|
+
if source == -inf or source is None and past_bound:
|
236
|
+
return MIN
|
237
|
+
#
|
238
|
+
if source == inf or source is None and not past_bound:
|
239
|
+
return MAX
|
240
|
+
#
|
241
|
+
raise ValueError(f"Don’t know how to convert {source!r} into a date")
|
242
|
+
|
243
|
+
|
244
|
+
def fromordinal(ordinal: float | int) -> GenericDate:
|
245
|
+
"""Create an InfinityDate or RealDate instance from the provided ordinal"""
|
246
|
+
if ordinal == -inf:
|
247
|
+
return MIN
|
248
|
+
#
|
249
|
+
if ordinal == inf:
|
250
|
+
return MAX
|
251
|
+
#
|
252
|
+
try:
|
253
|
+
new_ordinal = int(ordinal)
|
254
|
+
except ValueError as error:
|
255
|
+
raise ValueError(f"Cannot convert {ordinal!r} to integer") from error
|
256
|
+
#
|
257
|
+
if not MIN_ORDINAL <= new_ordinal <= MAX_ORDINAL:
|
258
|
+
raise OverflowError("RealDate value out of range")
|
259
|
+
#
|
260
|
+
stdlib_date_object = date.fromordinal(new_ordinal)
|
261
|
+
return from_datetime_object(stdlib_date_object)
|
262
|
+
|
263
|
+
|
264
|
+
def fromisoformat(source: str, /) -> GenericDate:
|
265
|
+
"""Create an InfinityDate or RealDate instance from an iso format representation"""
|
266
|
+
lower_source_stripped = source.strip().lower()
|
267
|
+
if lower_source_stripped == INFINITE_DATE_DISPLAY:
|
268
|
+
return MAX
|
269
|
+
#
|
270
|
+
if lower_source_stripped == NEGATIVE_INFINITE_DATE_DISPLAY:
|
271
|
+
return MIN
|
272
|
+
#
|
273
|
+
return from_datetime_object(date.fromisoformat(source))
|
274
|
+
|
275
|
+
|
276
|
+
def fromisocalendar(year: int, week: int, weekday: int) -> GenericDate:
|
277
|
+
"""Create a RealDate instance from an iso calendar date"""
|
278
|
+
return from_datetime_object(date.fromisocalendar(year, week, weekday))
|
279
|
+
|
280
|
+
|
281
|
+
def today() -> GenericDate:
|
282
|
+
"""Today as RealDate object"""
|
283
|
+
return from_datetime_object(date.today())
|
284
|
+
|
285
|
+
|
286
|
+
# Minimum and maximum real dates
|
287
|
+
# setattr(RealDate, "min", fromordinal(MIN_ORDINAL))
|
288
|
+
# setattr(RealDate, "max", fromordinal(MAX_ORDINAL))
|