infdate 0.2.1__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: infdate
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Date object wrapper supporting infinity
5
5
  Project-URL: Homepage, https://gitlab.com/blackstream-x/infdate
6
6
  Project-URL: CI, https://gitlab.com/blackstream-x/infdate/-/pipelines
@@ -14,12 +14,13 @@ Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
17
18
  Classifier: Programming Language :: Python :: 3.11
18
19
  Classifier: Programming Language :: Python :: 3.12
19
20
  Classifier: Programming Language :: Python :: 3.13
20
21
  Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
- Requires-Python: >=3.11
23
+ Requires-Python: >=3.10
23
24
  Description-Content-Type: text/markdown
24
25
 
25
26
  # infdate
@@ -35,14 +36,18 @@ _Python module for date calculations implementing a concept of infinity_
35
36
  └── RealDate
36
37
 
37
38
 
38
- The base class **GenericDate** should not be instantiated but can be used as a type annotation.
39
+ The base class **GenericDate** should not be instantiated
40
+ but can be used as a type annotation. In fact, it should be preferred
41
+ over the other classes in typing annotations.
39
42
 
40
43
  **InfinityDate** can represent either positive or negative infinity.
41
44
  The module-level constants **MIN** and **MAX** contain the two possible
42
45
  **InfinityDate** instance variations.
43
46
 
44
47
  **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.
48
+ **[datetime.date]** class, with mostly equal or similar semantics.
49
+ The module -level constants **REAL_MIN** and **REAL_MAX** are the eqivalents
50
+ of **datetime.date.min** and **datetime.date.max** as **RealDate** instances.
46
51
 
47
52
  For any valid **RealDate** instance, the following is **True**:
48
53
 
@@ -50,9 +55,21 @@ For any valid **RealDate** instance, the following is **True**:
50
55
  infdate.MIN < infdate.REAL_MIN <= real_date_instance <= infdate.REAL_MAX < infdate.MAX
51
56
  ```
52
57
 
53
- ### Module-level factory functions
58
+ ### Module-level constants
59
+
60
+ * **MIN** = **InfinityDate(**_past_bound_=`True`**)** → the beginning of time
61
+ * **MAX** = **InfinityDate(**_past_bound_=`False`**)** → the end of time
62
+ * **MIN_ORDINAL** = `1` → same as **datetime.date.min.toordinal()**
63
+ * **MAX_ORDINAL** = `3652059` → same as **datetime.date.max.toordinal()**
64
+ * **REAL_MIN** = **fromordinal(MIN_ORDINAL)**
65
+ → represents _0001-01-01_, the same date as **datetime.date.min**
66
+ * **REAL_MAX** = **fromordinal(MAX_ORDINAL)**
67
+ → represents _9999-12-31_, the same date as **datetime.date.max**
68
+ * **RESOLUTION** = `1` → represents the lowest possible date difference: _one day_
54
69
 
55
70
 
71
+ ### Module-level factory functions
72
+
56
73
  The following factory methods from the **datetime.date** class
57
74
  are provided as module-level functions:
58
75
 
@@ -78,15 +95,17 @@ Two additional factory functions are provided:
78
95
 
79
96
  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
97
 
81
- * The **.toordinal()** method returns **float** instead of **int**
98
+ * infdate classes have no **max**, **min** or **resolution** attributes,
99
+ but there are [module-level constants] serving the same purpose.
82
100
 
83
- * The **resolution** attribute is **1.0** instead of **datetime.timedelta(days=1)**
84
- but also represents exactly one day.
101
+ * The **.toordinal()** method returns **int**, **math.inf**, or **-math.inf**.
85
102
 
86
- * Subtracting a date from an **InfinityDate** or **RealDate** always returns a float instead of a **datetime.timedelta** instance.
103
+ * Subtracting a date from an **InfinityDate** or **RealDate** always returns
104
+ an **int**, **math.inf**, or **-math.inf** instead of a **datetime.timedelta** instance.
87
105
 
88
106
  * 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).
107
+ from an **InfinityDate** or **RealDate**, only **float** or **int**
108
+ (support for adding and subtracting datetime.timedelta instances might be added in the future, [see the feature request]).
90
109
 
91
110
 
92
111
  ## Example usage
@@ -95,22 +114,20 @@ Some notable difference from the **datetime.date** class, mainly due to the desi
95
114
  >>> import infdate
96
115
  >>> today = infdate.today()
97
116
  >>> 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
117
+ RealDate(2025, 6, 27)
118
+ >>> print(f"US date notation: {today:%m/%d/%y}")
119
+ US date notation: 06/27/25
103
120
  >>> today.ctime()
104
- 'Wed Jun 25 00:00:00 2025'
121
+ 'Fri Jun 27 00:00:00 2025'
105
122
  >>> today.isocalendar()
106
- datetime.IsoCalendarDate(year=2025, week=26, weekday=3)
123
+ datetime.IsoCalendarDate(year=2025, week=26, weekday=5)
107
124
  >>>
108
125
  >>> yesterday = today - 1
109
126
  >>> yesterday.ctime()
110
- 'Tue Jun 24 00:00:00 2025'
127
+ 'Thu Jun 26 00:00:00 2025'
111
128
  >>>
112
129
  >>> today - yesterday
113
- 1.0
130
+ 1
114
131
  >>> infdate.MIN
115
132
  InfinityDate(past_bound=True)
116
133
  >>> infdate.MAX
@@ -123,25 +140,26 @@ inf
123
140
 
124
141
  **InfinityDate** and **RealDate** instances can be compared with each other, and also with **datetime.date** instances.
125
142
 
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
+ Subtracting **InfinityDate** or **RealDate** and **datetime.date** instances from each other also works:
143
144
 
145
+ ``` pycon
146
+ >>> from datetime import date
147
+ >>> stdlib_today = date.today()
148
+ >>> today == stdlib_today
149
+ True
150
+ >>> yesterday < stdlib_today
151
+ True
152
+ >>> yesterday - stdlib_today
153
+ -1
154
+ >>> stdlib_today - yesterday
155
+ 1
156
+ >>> stdlib_today - infdate.MIN
157
+ inf
158
+ ```
144
159
 
145
160
 
146
161
  * * *
147
- [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
162
+ [datetime.date]: https://docs.python.org/3/library/datetime.html#date-objects
163
+ [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
164
+ [module-level constants]: #module-level-constants
165
+ [see the feature request]: https://gitlab.com/blackstream-x/infdate/-/issues/6
@@ -11,14 +11,18 @@ _Python module for date calculations implementing a concept of infinity_
11
11
  └── RealDate
12
12
 
13
13
 
14
- The base class **GenericDate** should not be instantiated but can be used as a type annotation.
14
+ The base class **GenericDate** should not be instantiated
15
+ but can be used as a type annotation. In fact, it should be preferred
16
+ over the other classes in typing annotations.
15
17
 
16
18
  **InfinityDate** can represent either positive or negative infinity.
17
19
  The module-level constants **MIN** and **MAX** contain the two possible
18
20
  **InfinityDate** instance variations.
19
21
 
20
22
  **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.
23
+ **[datetime.date]** class, with mostly equal or similar semantics.
24
+ The module -level constants **REAL_MIN** and **REAL_MAX** are the eqivalents
25
+ of **datetime.date.min** and **datetime.date.max** as **RealDate** instances.
22
26
 
23
27
  For any valid **RealDate** instance, the following is **True**:
24
28
 
@@ -26,9 +30,21 @@ For any valid **RealDate** instance, the following is **True**:
26
30
  infdate.MIN < infdate.REAL_MIN <= real_date_instance <= infdate.REAL_MAX < infdate.MAX
27
31
  ```
28
32
 
29
- ### Module-level factory functions
33
+ ### Module-level constants
34
+
35
+ * **MIN** = **InfinityDate(**_past_bound_=`True`**)** → the beginning of time
36
+ * **MAX** = **InfinityDate(**_past_bound_=`False`**)** → the end of time
37
+ * **MIN_ORDINAL** = `1` → same as **datetime.date.min.toordinal()**
38
+ * **MAX_ORDINAL** = `3652059` → same as **datetime.date.max.toordinal()**
39
+ * **REAL_MIN** = **fromordinal(MIN_ORDINAL)**
40
+ → represents _0001-01-01_, the same date as **datetime.date.min**
41
+ * **REAL_MAX** = **fromordinal(MAX_ORDINAL)**
42
+ → represents _9999-12-31_, the same date as **datetime.date.max**
43
+ * **RESOLUTION** = `1` → represents the lowest possible date difference: _one day_
30
44
 
31
45
 
46
+ ### Module-level factory functions
47
+
32
48
  The following factory methods from the **datetime.date** class
33
49
  are provided as module-level functions:
34
50
 
@@ -54,15 +70,17 @@ Two additional factory functions are provided:
54
70
 
55
71
  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
72
 
57
- * The **.toordinal()** method returns **float** instead of **int**
73
+ * infdate classes have no **max**, **min** or **resolution** attributes,
74
+ but there are [module-level constants] serving the same purpose.
58
75
 
59
- * The **resolution** attribute is **1.0** instead of **datetime.timedelta(days=1)**
60
- but also represents exactly one day.
76
+ * The **.toordinal()** method returns **int**, **math.inf**, or **-math.inf**.
61
77
 
62
- * Subtracting a date from an **InfinityDate** or **RealDate** always returns a float instead of a **datetime.timedelta** instance.
78
+ * Subtracting a date from an **InfinityDate** or **RealDate** always returns
79
+ an **int**, **math.inf**, or **-math.inf** instead of a **datetime.timedelta** instance.
63
80
 
64
81
  * 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).
82
+ from an **InfinityDate** or **RealDate**, only **float** or **int**
83
+ (support for adding and subtracting datetime.timedelta instances might be added in the future, [see the feature request]).
66
84
 
67
85
 
68
86
  ## Example usage
@@ -71,22 +89,20 @@ Some notable difference from the **datetime.date** class, mainly due to the desi
71
89
  >>> import infdate
72
90
  >>> today = infdate.today()
73
91
  >>> 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
92
+ RealDate(2025, 6, 27)
93
+ >>> print(f"US date notation: {today:%m/%d/%y}")
94
+ US date notation: 06/27/25
79
95
  >>> today.ctime()
80
- 'Wed Jun 25 00:00:00 2025'
96
+ 'Fri Jun 27 00:00:00 2025'
81
97
  >>> today.isocalendar()
82
- datetime.IsoCalendarDate(year=2025, week=26, weekday=3)
98
+ datetime.IsoCalendarDate(year=2025, week=26, weekday=5)
83
99
  >>>
84
100
  >>> yesterday = today - 1
85
101
  >>> yesterday.ctime()
86
- 'Tue Jun 24 00:00:00 2025'
102
+ 'Thu Jun 26 00:00:00 2025'
87
103
  >>>
88
104
  >>> today - yesterday
89
- 1.0
105
+ 1
90
106
  >>> infdate.MIN
91
107
  InfinityDate(past_bound=True)
92
108
  >>> infdate.MAX
@@ -99,25 +115,26 @@ inf
99
115
 
100
116
  **InfinityDate** and **RealDate** instances can be compared with each other, and also with **datetime.date** instances.
101
117
 
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'
118
+ Subtracting **InfinityDate** or **RealDate** and **datetime.date** instances from each other also works:
119
119
 
120
+ ``` pycon
121
+ >>> from datetime import date
122
+ >>> stdlib_today = date.today()
123
+ >>> today == stdlib_today
124
+ True
125
+ >>> yesterday < stdlib_today
126
+ True
127
+ >>> yesterday - stdlib_today
128
+ -1
129
+ >>> stdlib_today - yesterday
130
+ 1
131
+ >>> stdlib_today - infdate.MIN
132
+ inf
133
+ ```
120
134
 
121
135
 
122
136
  * * *
123
- [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
137
+ [datetime.date]: https://docs.python.org/3/library/datetime.html#date-objects
138
+ [Personal Access Tokens API]: https://docs.gitlab.com/api/personal_access_tokens/
139
+ [module-level constants]: #module-level-constants
140
+ [see the feature request]: https://gitlab.com/blackstream-x/infdate/-/issues/6
@@ -1,17 +1,18 @@
1
1
  [project]
2
2
  name = "infdate"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Date object wrapper supporting infinity"
5
5
  readme = "README.md"
6
6
  authors = [
7
7
  { name = "Rainer Schwarzbach", email = "rainer@blackstream.de" },
8
8
  ]
9
- requires-python = ">=3.11"
9
+ requires-python = ">=3.10"
10
10
  classifiers = [
11
11
  "Development Status :: 4 - Beta",
12
12
  "License :: OSI Approved :: MIT License",
13
13
  "Operating System :: OS Independent",
14
14
  "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.10",
15
16
  "Programming Language :: Python :: 3.11",
16
17
  "Programming Language :: Python :: 3.12",
17
18
  "Programming Language :: Python :: 3.13",
@@ -0,0 +1,8 @@
1
+ {
2
+ "ci_pipeline_created_at": "2025-06-27T15:27:06Z",
3
+ "ci_pipeline_id": "1894255377",
4
+ "ci_pipeline_url": "https://gitlab.com/blackstream-x/infdate/-/pipelines/1894255377",
5
+ "ci_project_title": "infdate",
6
+ "ci_project_url": "https://gitlab.com/blackstream-x/infdate",
7
+ "ci_commit_sha": "80b8357d36a7b921e46ce45b5c82298080b08b5f"
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
- [1890497916](https://gitlab.com/blackstream-x/infdate/-/pipelines/1890497916)
8
- (build started 2025-06-26T07:29:16Z)
7
+ [1894255377](https://gitlab.com/blackstream-x/infdate/-/pipelines/1894255377)
8
+ (build started 2025-06-27T15:27:06Z)
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="39" time="1.137" timestamp="2025-06-27T15:27:43.088896+00:00" hostname="runner-xxurkrix-project-71015407-concurrent-0"><testcase classname="tests.test_infdate.GenericDateBase" name="test_bool" time="0.003" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_eq" time="0.001" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_ge" time="0.058" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_gt" time="0.046" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_hash" time="0.001" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_isoformat" time="0.002" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_le" time="0.057" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_lt" time="0.047" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_nan_not_allowed" time="0.002" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_ne" time="0.047" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_replace" time="0.001" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_repr" time="0.001" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_str" time="0.002" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_strftime" time="0.001" /><testcase classname="tests.test_infdate.GenericDateBase" name="test_toordinal" time="0.001" /><testcase classname="tests.test_infdate.GenericDateArithmetics" name="test_add_and_radd" time="0.008" /><testcase classname="tests.test_infdate.GenericDateArithmetics" name="test_add_days" time="0.005" /><testcase classname="tests.test_infdate.GenericDateArithmetics" name="test_rsub_stdlib_date" time="0.001" /><testcase classname="tests.test_infdate.GenericDateArithmetics" name="test_sub_date" time="0.001" /><testcase classname="tests.test_infdate.GenericDateArithmetics" name="test_sub_number" time="0.005" /><testcase classname="tests.test_infdate.GenericDateArithmetics" name="test_sub_stdlib_date" time="0.001" /><testcase classname="tests.test_infdate.InfinityDate" name="test_replace" time="0.002" /><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.002" /><testcase classname="tests.test_infdate.RealDate" name="test_random_date_within_limits" time="0.295" /><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.003" /></testsuite></testsuites>
@@ -6,7 +6,7 @@ capable of representing positive and negative infinity
6
6
  """
7
7
 
8
8
  from datetime import date, datetime
9
- from math import inf
9
+ from math import inf, trunc
10
10
  from typing import final, overload, Any, Final, TypeVar
11
11
 
12
12
 
@@ -28,6 +28,8 @@ ISO_DATETIME_FORMAT_UTC: Final[str] = f"{ISO_DATE_FORMAT}T%H:%M:%S.%fZ"
28
28
  MIN_ORDINAL: Final[int] = date.min.toordinal()
29
29
  MAX_ORDINAL: Final[int] = date.max.toordinal()
30
30
 
31
+ RESOLUTION: Final[int] = 1
32
+
31
33
 
32
34
  # -----------------------------------------------------------------------------
33
35
  # Internal module constants:
@@ -45,15 +47,15 @@ _GD = TypeVar("_GD", bound="GenericDate")
45
47
  class GenericDate:
46
48
  """Base Date object derived from an ordinal"""
47
49
 
48
- # pylint: disable=invalid-name
49
- resolution: Final = 1
50
- # pylint: enable=invalid-name
51
-
52
- def __init__(self, ordinal: float, /) -> None:
50
+ def __init__(self, ordinal: int | float, /) -> None:
53
51
  """Create a date-like object"""
54
- self.__ordinal = ordinal
52
+ if ordinal in (-inf, inf):
53
+ self.__ordinal = ordinal
54
+ else:
55
+ self.__ordinal = trunc(ordinal)
56
+ #
55
57
 
56
- def toordinal(self: _GD) -> float:
58
+ def toordinal(self: _GD) -> int | float:
57
59
  """to ordinal (almost like date.toordinal())"""
58
60
  return self.__ordinal
59
61
 
@@ -109,24 +111,30 @@ class GenericDate:
109
111
  return self
110
112
  #
111
113
  # Return a RealDate instance if possible
112
- return fromordinal(self.__ordinal + delta)
114
+ return fromordinal(self.__ordinal + trunc(delta))
113
115
 
114
116
  def __add__(self: _GD, delta: int | float, /) -> _GD:
115
117
  """gd_instance1 + number capability"""
116
118
  return self._add_days(delta)
117
119
 
120
+ __radd__ = __add__
121
+
118
122
  @overload
119
123
  def __sub__(self: _GD, other: int | float, /) -> _GD: ...
120
124
  @overload
121
- def __sub__(self: _GD, other: _GD, /) -> int | float: ...
125
+ def __sub__(self: _GD, other: _GD | date, /) -> int | float: ...
122
126
  @final
123
- def __sub__(self: _GD, other: _GD | int | float, /) -> _GD | int | float:
127
+ def __sub__(self: _GD, other: _GD | date | int | float, /) -> _GD | int | float:
124
128
  """subtract other, respecting possibly nondeterministic values"""
125
129
  if isinstance(other, (int, float)):
126
130
  return self._add_days(-other)
127
131
  #
128
132
  return self.__ordinal - other.toordinal()
129
133
 
134
+ def __rsub__(self: _GD, other: _GD | date, /) -> int | float:
135
+ """subtract from other, respecting possibly nondeterministic values"""
136
+ return other.toordinal() - self.__ordinal
137
+
130
138
  def __repr__(self: _GD, /) -> str:
131
139
  """String representation of the object"""
132
140
  return f"{self.__class__.__name__}({repr(self.__ordinal)})"
@@ -186,7 +194,7 @@ class RealDate(GenericDate):
186
194
  self.year = year
187
195
  self.month = month
188
196
  self.day = day
189
- super().__init__(float(self._wrapped_date_object.toordinal()))
197
+ super().__init__(self._wrapped_date_object.toordinal())
190
198
  self.timetuple = self._wrapped_date_object.timetuple
191
199
  self.weekday = self._wrapped_date_object.weekday
192
200
  self.isoweekday = self._wrapped_date_object.isoweekday
@@ -274,7 +282,7 @@ def fromtimestamp(timestamp: float) -> GenericDate:
274
282
  return from_datetime_object(stdlib_date_object)
275
283
 
276
284
 
277
- def fromordinal(ordinal: float | int) -> GenericDate:
285
+ def fromordinal(ordinal: int | float) -> GenericDate:
278
286
  """Create an InfinityDate or RealDate instance from the provided ordinal"""
279
287
  if ordinal == -inf:
280
288
  return MIN
@@ -282,11 +290,7 @@ def fromordinal(ordinal: float | int) -> GenericDate:
282
290
  if ordinal == inf:
283
291
  return MAX
284
292
  #
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
- #
293
+ new_ordinal = trunc(ordinal)
290
294
  if not MIN_ORDINAL <= new_ordinal <= MAX_ORDINAL:
291
295
  raise OverflowError("RealDate value out of range")
292
296
  #
@@ -18,6 +18,9 @@ import infdate
18
18
 
19
19
  MAX_ORDINAL = datetime.date.max.toordinal()
20
20
 
21
+ # This is an error message from Python itself
22
+ _NAN_INT_CONVERSION_ERROR_RE = "^cannot convert float NaN to integer$"
23
+
21
24
 
22
25
  def random_deterministic_date() -> infdate.GenericDate:
23
26
  """Helper function: create a random deterministic Date"""
@@ -32,14 +35,20 @@ class VerboseTestCase(unittest.TestCase):
32
35
  self.maxDiff = None # pylint: disable=invalid-name ; name from unittest module
33
36
 
34
37
 
35
- class GenericDate(VerboseTestCase):
36
- """GenericDate objects"""
38
+ class GenericDateBase(VerboseTestCase):
39
+ """GenericDate objects - base functionality"""
40
+
41
+ def test_nan_not_allowed(self):
42
+ """test initialization with nan"""
43
+ self.assertRaisesRegex(
44
+ ValueError, _NAN_INT_CONVERSION_ERROR_RE, infdate.GenericDate, nan
45
+ )
37
46
 
38
47
  def test_toordinal(self):
39
48
  """initialization and .toordinal() method"""
40
49
  num = 1.23
41
50
  gd = infdate.GenericDate(num)
42
- self.assertEqual(gd.toordinal(), num)
51
+ self.assertEqual(gd.toordinal(), 1)
43
52
 
44
53
  # pylint: disable=comparison-with-itself ; to show lt/gt ↔ le/ge difference
45
54
 
@@ -174,6 +183,8 @@ class GenericDate(VerboseTestCase):
174
183
  self.assertTrue(random_date == random_date)
175
184
  #
176
185
 
186
+ # pylint: enable=comparison-with-itself
187
+
177
188
  def test_bool(self):
178
189
  """bool(gd_instance) capability"""
179
190
  gd = infdate.GenericDate(3.579)
@@ -182,7 +193,61 @@ class GenericDate(VerboseTestCase):
182
193
  def test_hash(self):
183
194
  """bool(gd_instance) capability"""
184
195
  gd = infdate.GenericDate(0.5)
185
- self.assertEqual(hash(gd), hash("date with ordinal 0.5"))
196
+ self.assertEqual(hash(gd), hash("date with ordinal 0"))
197
+
198
+ def test_repr(self):
199
+ """repr(gd_instance) capability"""
200
+ for base, expected_display in (
201
+ (inf, "inf"),
202
+ (-inf, "-inf"),
203
+ (9.81, "9"),
204
+ (314, "314"),
205
+ ):
206
+ gd = infdate.GenericDate(base)
207
+ with self.subTest(
208
+ "representation of",
209
+ base=base,
210
+ expected_display=expected_display,
211
+ ):
212
+ self.assertEqual(repr(gd), f"GenericDate({expected_display})")
213
+ #
214
+ #
215
+
216
+ def test_str(self):
217
+ """str(gd_instance) capability"""
218
+ gd = infdate.GenericDate(777)
219
+ mocked_isoformat_result = "[777]"
220
+ with patch.object(gd, "isoformat") as mock_isoformat:
221
+ mock_isoformat.return_value = mocked_isoformat_result
222
+ result = str(gd)
223
+ self.assertEqual(result, mocked_isoformat_result)
224
+ mock_isoformat.assert_called_with()
225
+ #
226
+
227
+ def test_isoformat(self):
228
+ """.isoformat() method"""
229
+ gd = infdate.GenericDate(777)
230
+ mocked_strftime_result = "[777]"
231
+ with patch.object(gd, "strftime") as mock_strftime:
232
+ mock_strftime.return_value = mocked_strftime_result
233
+ result = str(gd)
234
+ self.assertEqual(result, mocked_strftime_result)
235
+ mock_strftime.assert_called_with(infdate.ISO_DATE_FORMAT)
236
+ #
237
+
238
+ def test_strftime(self):
239
+ """.strftime() method"""
240
+ gd = infdate.GenericDate(-inf)
241
+ self.assertRaises(NotImplementedError, gd.strftime, "")
242
+
243
+ def test_replace(self):
244
+ """.replace() method"""
245
+ gd = infdate.GenericDate(inf)
246
+ self.assertRaises(NotImplementedError, gd.replace, year=1)
247
+
248
+
249
+ class GenericDateArithmetics(VerboseTestCase):
250
+ """GenericDate objects - arithmetics"""
186
251
 
187
252
  # pylint: disable=protected-access ; ok for testing
188
253
 
@@ -197,10 +262,10 @@ class GenericDate(VerboseTestCase):
197
262
  (-inf, 77.98, -inf, None, True),
198
263
  (9.81, inf, inf, inf, False),
199
264
  (9.81, -inf, -inf, -inf, False),
200
- (1.234, 7.89, 1.234 + 7.89, 1.234 + 7.89, False),
201
- (7.62, 0, 7.62, None, True),
202
- (1.234, -5.678, 1.234 - 5.678, 1.234 - 5.678, False),
203
- (3.14, -0.0, 3.14, None, True),
265
+ (1.234, 7.89, 8, 8, False),
266
+ (7.62, 0, 7, None, True),
267
+ (1.234, -5.678, -4, -4, False),
268
+ (3.14, -0, 3, None, True),
204
269
  ):
205
270
  with patch.object(infdate, "fromordinal") as mock_fromordinal:
206
271
  gd = infdate.GenericDate(base)
@@ -238,7 +303,7 @@ class GenericDate(VerboseTestCase):
238
303
  #
239
304
  #
240
305
 
241
- def test_add(self):
306
+ def test_add_and_radd(self):
242
307
  """gd_instance + delta capability"""
243
308
  for base, delta in (
244
309
  (inf, inf),
@@ -258,9 +323,13 @@ class GenericDate(VerboseTestCase):
258
323
  with patch.object(gd, "_add_days") as mock_adder:
259
324
  _ = gd + delta
260
325
  with self.subTest(
261
- "._add_days() call with",
262
- base=base,
263
- delta=delta,
326
+ f"GenericDate({base}) + {delta} → Gen…()._add_days({delta}) call"
327
+ ):
328
+ mock_adder.assert_called_with(delta)
329
+ #
330
+ _ = delta + gd
331
+ with self.subTest(
332
+ f"{delta} + GenericDate({base}) → Gen…()._add_days({delta}) call"
264
333
  ):
265
334
  mock_adder.assert_called_with(delta)
266
335
  #
@@ -278,10 +347,10 @@ class GenericDate(VerboseTestCase):
278
347
  (-inf, 77.98, -inf, None, True),
279
348
  (9.81, -inf, inf, inf, False),
280
349
  (9.81, inf, -inf, -inf, False),
281
- (1.234, -7.89, 1.234 + 7.89, 7.89, False),
282
- (7.62, -0, 7.62, None, True),
283
- (1.234, 5.678, 1.234 - 5.678, -5.678, False),
284
- (3.14, 0.0, 3.14, None, True),
350
+ (1.234, -7.89, 8, 7.89, False),
351
+ (7.62, -0, 7, None, True),
352
+ (1.234, 5.678, -4, -5.678, False),
353
+ (3.14, 0.0, 3, None, True),
285
354
  ):
286
355
  gd = infdate.GenericDate(base)
287
356
  with patch.object(gd, "_add_days") as mock_adder:
@@ -334,10 +403,10 @@ class GenericDate(VerboseTestCase):
334
403
  (-inf, 77.98, -inf),
335
404
  (9.81, -inf, inf),
336
405
  (9.81, inf, -inf),
337
- (1.234, -7.89, 1.234 + 7.89),
338
- (7.62, -0, 7.62),
339
- (1.234, 5.678, 1.234 - 5.678),
340
- (3.14, 0.0, 3.14),
406
+ (1.234, -7.89, 8),
407
+ (7.62, -0, 7),
408
+ (1.234, 5.678, -4),
409
+ (3.14, 0.0, 3),
341
410
  ):
342
411
  gd1 = infdate.GenericDate(first)
343
412
  gd2 = infdate.GenericDate(second)
@@ -356,57 +425,51 @@ class GenericDate(VerboseTestCase):
356
425
  #
357
426
  #
358
427
 
359
- def test_repr(self):
360
- """repr(gd_instance) capability"""
361
- for base, expected_display in (
362
- (inf, "inf"),
363
- (-inf, "-inf"),
364
- (9.81, "9.81"),
365
- (nan, "nan"),
366
- (314, "314"),
428
+ def test_sub_stdlib_date(self):
429
+ """gd_instance - stdlib_date capability"""
430
+ for gd_ordinal, stdlib_date_ordinal, expected_result in (
431
+ (inf, 981, inf),
432
+ (-inf, 733981, -inf),
433
+ (12, 1234, -1222),
434
+ (762, 1, 761),
435
+ (99999.99, 7777, 92222),
367
436
  ):
368
- gd = infdate.GenericDate(base)
437
+ gd_instance = infdate.GenericDate(gd_ordinal)
438
+ stdlib_date = datetime.date.fromordinal(stdlib_date_ordinal)
439
+ result = gd_instance - stdlib_date
369
440
  with self.subTest(
370
- "representation of",
371
- base=base,
372
- expected_display=expected_display,
441
+ "result",
442
+ gd_ordinal=gd_ordinal,
443
+ stdlib_date_ordinal=stdlib_date_ordinal,
444
+ expected_result=expected_result,
373
445
  ):
374
- self.assertEqual(repr(gd), f"GenericDate({expected_display})")
446
+ self.assertEqual(result, expected_result)
375
447
  #
376
448
  #
377
449
 
378
- def test_str(self):
379
- """str(gd_instance) capability"""
380
- gd = infdate.GenericDate(777)
381
- mocked_isoformat_result = "[777]"
382
- with patch.object(gd, "isoformat") as mock_isoformat:
383
- mock_isoformat.return_value = mocked_isoformat_result
384
- result = str(gd)
385
- self.assertEqual(result, mocked_isoformat_result)
386
- mock_isoformat.assert_called_with()
387
- #
388
-
389
- def test_isoformat(self):
390
- """.isoformat() method"""
391
- gd = infdate.GenericDate(777)
392
- mocked_strftime_result = "[777]"
393
- with patch.object(gd, "strftime") as mock_strftime:
394
- mock_strftime.return_value = mocked_strftime_result
395
- result = str(gd)
396
- self.assertEqual(result, mocked_strftime_result)
397
- mock_strftime.assert_called_with(infdate.ISO_DATE_FORMAT)
450
+ def test_rsub_stdlib_date(self):
451
+ """stdlib_date - gd_instance capability"""
452
+ for stdlib_date_ordinal, gd_ordinal, expected_result in (
453
+ (981, -inf, inf),
454
+ (981, inf, -inf),
455
+ (1234, -7.89, 1241),
456
+ (762, -0, 762),
457
+ (1234, 5.678, 1229),
458
+ (314, 0.0, 314),
459
+ ):
460
+ stdlib_date = datetime.date.fromordinal(stdlib_date_ordinal)
461
+ gd_instance = infdate.GenericDate(gd_ordinal)
462
+ result = stdlib_date - gd_instance
463
+ with self.subTest(
464
+ "result",
465
+ stdlib_date_ordinal=stdlib_date_ordinal,
466
+ gd_ordinal=gd_ordinal,
467
+ expected_result=expected_result,
468
+ ):
469
+ self.assertEqual(result, expected_result)
470
+ #
398
471
  #
399
472
 
400
- def test_strftime(self):
401
- """.strftime() method"""
402
- gd = infdate.GenericDate(-inf)
403
- self.assertRaises(NotImplementedError, gd.strftime, "")
404
-
405
- def test_replace(self):
406
- """.replace() method"""
407
- gd = infdate.GenericDate(inf)
408
- self.assertRaises(NotImplementedError, gd.replace, year=1)
409
-
410
473
 
411
474
  class InfinityDate(VerboseTestCase):
412
475
  """InfinityDate class"""
@@ -606,6 +669,27 @@ class RealDate(VerboseTestCase):
606
669
  #
607
670
  #
608
671
 
672
+ def test_random_date_within_limits(self):
673
+ """The following relation
674
+ infdate.MIN < infdate.REAL_MIN <= real_date_instance <= …
675
+ … infdate.REAL_MAX < infdate.MAX
676
+ should always be True
677
+ """
678
+ for iteration in range(1, 10000):
679
+ random_date = random_deterministic_date()
680
+ with self.subTest(
681
+ "date within limits", iteration=iteration, random_date=random_date
682
+ ):
683
+ self.assertTrue(
684
+ infdate.MIN
685
+ < infdate.REAL_MIN
686
+ <= random_date
687
+ <= infdate.REAL_MAX
688
+ < infdate.MAX
689
+ )
690
+ #
691
+ #
692
+
609
693
 
610
694
  class FactoryFunctions(VerboseTestCase):
611
695
  """Factory functions in the module"""
@@ -770,7 +854,7 @@ class FactoryFunctions(VerboseTestCase):
770
854
  (-1, OverflowError, default_overflow_re),
771
855
  (0, OverflowError, default_overflow_re),
772
856
  (1234567890, OverflowError, default_overflow_re),
773
- (nan, ValueError, "^Cannot convert nan to integer$"),
857
+ (nan, ValueError, _NAN_INT_CONVERSION_ERROR_RE),
774
858
  ):
775
859
  with self.subTest("overflow error", ordinal=ordinal):
776
860
  self.assertRaisesRegex(
@@ -1,8 +0,0 @@
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
- }
@@ -1 +0,0 @@
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>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes