Skip to content

Commit 42208b0

Browse files
committed
Rationalise Instant doc & tests
1 parent c49180b commit 42208b0

2 files changed

Lines changed: 116 additions & 57 deletions

File tree

openfisca_core/periods/instant_.py

Lines changed: 89 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from __future__ import annotations
2+
3+
from typing import Union
4+
15
import calendar
26
import datetime
37

@@ -6,35 +10,39 @@
610

711

812
class Instant(tuple):
9-
"""An instant in time (year, month, day).
13+
"""An instant in time (``year``, ``month``, ``day``).
1014
11-
An :class:`.Instant` represents the most atomic and indivisible
12-
legislation's time unit.
15+
An ``Instant`` represents the most atomic and indivisible
16+
legislation's date unit.
1317
1418
Current implementation considers this unit to be a day, so
15-
:obj:`instants <.Instant>` can be thought of as "day dates".
19+
``instants`` can be thought of as "day dates".
1620
1721
Args:
18-
(tuple(tuple(int, int, int))):
22+
(tuple(int, int, int)):
1923
The ``year``, ``month``, and ``day``, accordingly.
2024
2125
Examples:
2226
>>> instant = Instant((2021, 9, 13))
2327
24-
>>> repr(Instant)
25-
"<class 'openfisca_core.periods.instant_.Instant'>"
28+
``Instants`` are represented as a ``tuple`` containing the date units:
2629
2730
>>> repr(instant)
2831
'Instant((2021, 9, 13))'
2932
33+
However, their user-friendly representation is as a date in the
34+
ISO format:
35+
3036
>>> str(instant)
3137
'2021-09-13'
3238
39+
Because ``Instants`` are ``tuples``, they are immutable, which allows
40+
us to use them as keys in hashmaps:
41+
3342
>>> dict([(instant, (2021, 9, 13))])
3443
{Instant((2021, 9, 13)): (2021, 9, 13)}
3544
36-
>>> list(instant)
37-
[2021, 9, 13]
45+
All the rest of the ``tuple`` protocols are inherited as well:
3846
3947
>>> instant[0]
4048
2021
@@ -48,74 +56,102 @@ class Instant(tuple):
4856
>>> instant == (2021, 9, 13)
4957
True
5058
51-
>>> instant != (2021, 9, 13)
52-
False
53-
5459
>>> instant > (2020, 9, 13)
5560
True
5661
57-
>>> instant < (2020, 9, 13)
58-
False
59-
60-
>>> instant >= (2020, 9, 13)
61-
True
62-
63-
>>> instant <= (2020, 9, 13)
64-
False
65-
66-
>>> instant.year
67-
2021
68-
69-
>>> instant.month
70-
9
71-
72-
>>> instant.day
73-
13
74-
75-
>>> instant.date
76-
datetime.date(2021, 9, 13)
77-
7862
>>> year, month, day = instant
7963
8064
"""
8165

82-
def __repr__(self):
66+
def __repr__(self) -> str:
8367
return '{}({})'.format(self.__class__.__name__, super(Instant, self).__repr__())
8468

85-
def __str__(self):
69+
def __str__(self) -> str:
8670
instant_str = config.str_by_instant_cache.get(self)
8771
if instant_str is None:
8872
config.str_by_instant_cache[self] = instant_str = self.date.isoformat()
8973
return instant_str
9074

9175
@property
92-
def date(self):
76+
def date(self) -> "datetime.date":
77+
"""The date representation of the ``Instant``.
78+
79+
Example:
80+
>>> instant = Instant((2021, 10, 1))
81+
>>> instant.date
82+
datetime.date(2021, 10, 1)
83+
84+
Returns:
85+
A datetime.time.
86+
87+
"""
88+
9389
instant_date = config.date_by_instant_cache.get(self)
9490
if instant_date is None:
9591
config.date_by_instant_cache[self] = instant_date = datetime.date(*self)
9692
return instant_date
9793

9894
@property
99-
def day(self):
100-
return self[2]
95+
def year(self) -> int:
96+
"""The ``year`` of the ``Instant``.
97+
98+
Example:
99+
>>> instant = Instant((2021, 10, 1))
100+
>>> instant.year
101+
2021
102+
103+
Returns:
104+
An int.
105+
106+
"""
107+
108+
return self[0]
101109

102110
@property
103-
def month(self):
111+
def month(self) -> int:
112+
"""The ``month`` of the ``Instant``.
113+
114+
Example:
115+
>>> instant = Instant((2021, 10, 1))
116+
>>> instant.month
117+
10
118+
119+
Returns:
120+
An int.
121+
122+
"""
123+
104124
return self[1]
105125

106-
def period(self, unit, size = 1):
107-
"""Creates a new :obj:`.Period` starting at :obj:`.Instant`.
126+
@property
127+
def day(self) -> int:
128+
"""The ``day`` of the ``Instant``.
129+
130+
Example:
131+
>>> instant = Instant((2021, 10, 1))
132+
>>> instant.day
133+
1
134+
135+
Returns:
136+
An int.
137+
138+
"""
139+
140+
return self[2]
141+
142+
def period(self, unit: str, size: int = 1) -> "periods.Period":
143+
"""Creates a new ``Period`` starting at ``self``.
108144
109145
Args:
110-
unit: ``day`` or ``month`` or ``year``.
111-
size: How many of ``unit``.
146+
unit (str): ``day`` or ``month`` or ``year``.
147+
size (int): How many of ``unit``.
112148
113149
Returns:
114-
A new object :obj:`.Period`.
150+
Period: A new one.
115151
116152
Raises:
117-
:exc:`AssertionError`: When ``unit`` is not a date unit.
118-
:exc:`AssertionError`: When ``size`` is not an unsigned :obj:`int`.
153+
AssertionError: When ``unit`` is not a date unit.
154+
AssertionError: When ``size`` is not an unsigned ``int``.
119155
120156
Examples:
121157
>>> Instant((2021, 9, 13)).period("year")
@@ -130,20 +166,20 @@ def period(self, unit, size = 1):
130166
assert isinstance(size, int) and size >= 1, 'Invalid size: {} of type {}'.format(size, type(size))
131167
return periods.Period((unit, self, size))
132168

133-
def offset(self, offset, unit):
169+
def offset(self, offset: Union[str, int], unit: str) -> "Instant":
134170
"""Increments/decrements the given instant with offset units.
135171
136172
Args:
137-
offset: How much of ``unit`` to offset.
138-
unit: What to offset
173+
offset (str | int): How much of ``unit`` to offset.
174+
unit (str): What to offset.
139175
140176
Returns:
141-
:obj:`.Instant`: A new :obj:`.Instant` in time.
177+
Instant: A new one.
142178
143179
Raises:
144-
:exc:`AssertionError`: When ``unit`` is not a date unit.
145-
:exc:`AssertionError`: When ``offset`` is not either ``first-of``,
146-
``last-of``, or any :obj:`int`.
180+
AssertionError: When ``unit`` is not a date unit.
181+
AssertionError: When ``offset`` is not either ``first-of``,
182+
``last-of``, or any ``int``.
147183
148184
Examples:
149185
>>> Instant((2020, 12, 31)).offset("first-of", "month")
@@ -215,7 +251,3 @@ def offset(self, offset, unit):
215251
day = month_last_day
216252

217253
return self.__class__((year, month, day))
218-
219-
@property
220-
def year(self):
221-
return self[0]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pytest
2+
3+
from openfisca_core import periods
4+
from openfisca_core.periods import Instant
5+
6+
7+
@pytest.fixture
8+
def instant():
9+
return Instant((2020, 2, 29))
10+
11+
12+
@pytest.mark.parametrize("offset, unit, expected", [
13+
["first-of", periods.YEAR, Instant((2020, 1, 1))],
14+
["first-of", periods.MONTH, Instant((2020, 2, 1))],
15+
["first-of", periods.DAY, Instant((2020, 2, 29))],
16+
["last-of", periods.YEAR, Instant((2020, 12, 31))],
17+
["last-of", periods.MONTH, Instant((2020, 2, 29))],
18+
["last-of", periods.DAY, Instant((2020, 2, 29))],
19+
[-3, periods.YEAR, Instant((2017, 2, 28))],
20+
[-3, periods.MONTH, Instant((2019, 11, 29))],
21+
[-3, periods.DAY, Instant((2020, 2, 26))],
22+
[3, periods.YEAR, Instant((2023, 2, 28))],
23+
[3, periods.MONTH, Instant((2020, 5, 29))],
24+
[3, periods.DAY, Instant((2020, 3, 3))],
25+
])
26+
def test_offset(instant, offset, unit, expected):
27+
assert expected == instant.offset(offset, unit)

0 commit comments

Comments
 (0)