OpenFisca-Core-test 46.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. openfisca_core/__init__.py +0 -0
  2. openfisca_core/commons/__init__.py +18 -0
  3. openfisca_core/commons/dummy.py +25 -0
  4. openfisca_core/commons/formulas.py +121 -0
  5. openfisca_core/commons/misc.py +109 -0
  6. openfisca_core/commons/py.typed +0 -0
  7. openfisca_core/commons/rates.py +114 -0
  8. openfisca_core/commons/tests/__init__.py +0 -0
  9. openfisca_core/commons/tests/test_dummy.py +9 -0
  10. openfisca_core/commons/tests/test_formulas.py +75 -0
  11. openfisca_core/commons/tests/test_rates.py +26 -0
  12. openfisca_core/data_storage/__init__.py +7 -0
  13. openfisca_core/data_storage/in_memory_storage.py +198 -0
  14. openfisca_core/data_storage/on_disk_storage.py +307 -0
  15. openfisca_core/data_storage/types.py +14 -0
  16. openfisca_core/entities/__init__.py +23 -0
  17. openfisca_core/entities/_core_entity.py +217 -0
  18. openfisca_core/entities/_description.py +55 -0
  19. openfisca_core/entities/entity.py +60 -0
  20. openfisca_core/entities/group_entity.py +123 -0
  21. openfisca_core/entities/helpers.py +165 -0
  22. openfisca_core/entities/py.typed +0 -0
  23. openfisca_core/entities/role.py +92 -0
  24. openfisca_core/entities/tests/__init__.py +0 -0
  25. openfisca_core/entities/tests/test_entity.py +10 -0
  26. openfisca_core/entities/tests/test_group_entity.py +70 -0
  27. openfisca_core/entities/tests/test_role.py +11 -0
  28. openfisca_core/entities/types.py +44 -0
  29. openfisca_core/errors/__init__.py +64 -0
  30. openfisca_core/errors/cycle_error.py +2 -0
  31. openfisca_core/errors/empty_argument_error.py +39 -0
  32. openfisca_core/errors/nan_creation_error.py +2 -0
  33. openfisca_core/errors/parameter_not_found_error.py +16 -0
  34. openfisca_core/errors/parameter_parsing_error.py +18 -0
  35. openfisca_core/errors/period_mismatch_error.py +9 -0
  36. openfisca_core/errors/situation_parsing_error.py +26 -0
  37. openfisca_core/errors/spiral_error.py +2 -0
  38. openfisca_core/errors/variable_name_config_error.py +2 -0
  39. openfisca_core/errors/variable_not_found_error.py +29 -0
  40. openfisca_core/experimental/__init__.py +9 -0
  41. openfisca_core/experimental/_errors.py +5 -0
  42. openfisca_core/experimental/_memory_config.py +41 -0
  43. openfisca_core/formula_helpers.py +9 -0
  44. openfisca_core/holders/__init__.py +33 -0
  45. openfisca_core/holders/helpers.py +93 -0
  46. openfisca_core/holders/holder.py +328 -0
  47. openfisca_core/holders/tests/__init__.py +0 -0
  48. openfisca_core/holders/tests/test_helpers.py +134 -0
  49. openfisca_core/holders/types.py +3 -0
  50. openfisca_core/indexed_enums/__init__.py +18 -0
  51. openfisca_core/indexed_enums/_enum_type.py +79 -0
  52. openfisca_core/indexed_enums/_errors.py +35 -0
  53. openfisca_core/indexed_enums/_guards.py +209 -0
  54. openfisca_core/indexed_enums/_utils.py +180 -0
  55. openfisca_core/indexed_enums/config.py +6 -0
  56. openfisca_core/indexed_enums/enum.py +218 -0
  57. openfisca_core/indexed_enums/enum_array.py +316 -0
  58. openfisca_core/indexed_enums/py.typed +0 -0
  59. openfisca_core/indexed_enums/tests/__init__.py +0 -0
  60. openfisca_core/indexed_enums/tests/test_enum.py +135 -0
  61. openfisca_core/indexed_enums/tests/test_enum_array.py +30 -0
  62. openfisca_core/indexed_enums/types.py +49 -0
  63. openfisca_core/memory_config.py +9 -0
  64. openfisca_core/model_api.py +71 -0
  65. openfisca_core/parameters/__init__.py +71 -0
  66. openfisca_core/parameters/at_instant_like.py +17 -0
  67. openfisca_core/parameters/config.py +55 -0
  68. openfisca_core/parameters/helpers.py +106 -0
  69. openfisca_core/parameters/parameter.py +217 -0
  70. openfisca_core/parameters/parameter_at_instant.py +75 -0
  71. openfisca_core/parameters/parameter_node.py +176 -0
  72. openfisca_core/parameters/parameter_node_at_instant.py +57 -0
  73. openfisca_core/parameters/parameter_scale.py +121 -0
  74. openfisca_core/parameters/parameter_scale_bracket.py +7 -0
  75. openfisca_core/parameters/values_history.py +5 -0
  76. openfisca_core/parameters/vectorial_parameter_node_at_instant.py +179 -0
  77. openfisca_core/periods/__init__.py +77 -0
  78. openfisca_core/periods/_errors.py +28 -0
  79. openfisca_core/periods/_parsers.py +122 -0
  80. openfisca_core/periods/config.py +20 -0
  81. openfisca_core/periods/date_unit.py +110 -0
  82. openfisca_core/periods/helpers.py +314 -0
  83. openfisca_core/periods/instant_.py +226 -0
  84. openfisca_core/periods/period_.py +919 -0
  85. openfisca_core/periods/py.typed +0 -0
  86. openfisca_core/periods/tests/__init__.py +0 -0
  87. openfisca_core/periods/tests/helpers/__init__.py +0 -0
  88. openfisca_core/periods/tests/helpers/test_helpers.py +65 -0
  89. openfisca_core/periods/tests/helpers/test_instant.py +73 -0
  90. openfisca_core/periods/tests/helpers/test_period.py +134 -0
  91. openfisca_core/periods/tests/test_instant.py +32 -0
  92. openfisca_core/periods/tests/test_parsers.py +129 -0
  93. openfisca_core/periods/tests/test_period.py +283 -0
  94. openfisca_core/populations/__init__.py +64 -0
  95. openfisca_core/populations/_core_population.py +454 -0
  96. openfisca_core/populations/_errors.py +65 -0
  97. openfisca_core/populations/group_population.py +326 -0
  98. openfisca_core/populations/population.py +142 -0
  99. openfisca_core/populations/tests/__init__.py +0 -0
  100. openfisca_core/populations/tests/test_members_position.py +116 -0
  101. openfisca_core/populations/types.py +110 -0
  102. openfisca_core/projectors/__init__.py +39 -0
  103. openfisca_core/projectors/entity_to_person_projector.py +12 -0
  104. openfisca_core/projectors/first_person_to_entity_projector.py +13 -0
  105. openfisca_core/projectors/helpers.py +139 -0
  106. openfisca_core/projectors/projector.py +38 -0
  107. openfisca_core/projectors/typing.py +27 -0
  108. openfisca_core/projectors/unique_role_to_entity_projector.py +14 -0
  109. openfisca_core/rates.py +9 -0
  110. openfisca_core/reforms/__init__.py +24 -0
  111. openfisca_core/reforms/in_yaml_test_reform.py +28 -0
  112. openfisca_core/reforms/reform.py +86 -0
  113. openfisca_core/scripts/__init__.py +106 -0
  114. openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.3.3.pyc +0 -0
  115. openfisca_core/scripts/__pycache__/__init__.cpython-310-pytest-8.4.2.pyc +0 -0
  116. openfisca_core/scripts/__pycache__/__init__.cpython-310.pyc +0 -0
  117. openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.3.3.pyc +0 -0
  118. openfisca_core/scripts/__pycache__/__init__.cpython-311-pytest-8.4.2.pyc +0 -0
  119. openfisca_core/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  120. openfisca_core/scripts/__pycache__/__init__.cpython-312-pytest-8.4.2.pyc +0 -0
  121. openfisca_core/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
  122. openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.3.3.pyc +0 -0
  123. openfisca_core/scripts/__pycache__/__init__.cpython-313-pytest-8.4.2.pyc +0 -0
  124. openfisca_core/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  125. openfisca_core/scripts/__pycache__/find_placeholders.cpython-311.pyc +0 -0
  126. openfisca_core/scripts/__pycache__/find_placeholders.cpython-313.pyc +0 -0
  127. openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-311.pyc +0 -0
  128. openfisca_core/scripts/__pycache__/measure_numpy_condition_notations.cpython-313.pyc +0 -0
  129. openfisca_core/scripts/__pycache__/measure_performances.cpython-311.pyc +0 -0
  130. openfisca_core/scripts/__pycache__/measure_performances.cpython-313.pyc +0 -0
  131. openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-311.pyc +0 -0
  132. openfisca_core/scripts/__pycache__/measure_performances_fancy_indexing.cpython-313.pyc +0 -0
  133. openfisca_core/scripts/__pycache__/openfisca_command.cpython-310.pyc +0 -0
  134. openfisca_core/scripts/__pycache__/openfisca_command.cpython-311.pyc +0 -0
  135. openfisca_core/scripts/__pycache__/openfisca_command.cpython-312.pyc +0 -0
  136. openfisca_core/scripts/__pycache__/openfisca_command.cpython-313.pyc +0 -0
  137. openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-311.pyc +0 -0
  138. openfisca_core/scripts/__pycache__/remove_fuzzy.cpython-313.pyc +0 -0
  139. openfisca_core/scripts/__pycache__/run_test.cpython-310.pyc +0 -0
  140. openfisca_core/scripts/__pycache__/run_test.cpython-311.pyc +0 -0
  141. openfisca_core/scripts/__pycache__/run_test.cpython-312.pyc +0 -0
  142. openfisca_core/scripts/__pycache__/run_test.cpython-313.pyc +0 -0
  143. openfisca_core/scripts/__pycache__/simulation_generator.cpython-311.pyc +0 -0
  144. openfisca_core/scripts/__pycache__/simulation_generator.cpython-313.pyc +0 -0
  145. openfisca_core/scripts/assets/__init__.py +0 -0
  146. openfisca_core/scripts/assets/__pycache__/__init__.cpython-311.pyc +0 -0
  147. openfisca_core/scripts/assets/__pycache__/__init__.cpython-313.pyc +0 -0
  148. openfisca_core/scripts/assets/index.html +140 -0
  149. openfisca_core/scripts/find_placeholders.py +51 -0
  150. openfisca_core/scripts/measure_numpy_condition_notations.py +137 -0
  151. openfisca_core/scripts/measure_performances.py +291 -0
  152. openfisca_core/scripts/measure_performances_fancy_indexing.py +89 -0
  153. openfisca_core/scripts/migrations/__init__.py +0 -0
  154. openfisca_core/scripts/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
  155. openfisca_core/scripts/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
  156. openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-311.pyc +0 -0
  157. openfisca_core/scripts/migrations/__pycache__/v24_to_25.cpython-313.pyc +0 -0
  158. openfisca_core/scripts/migrations/v16_2_to_v17/__init__.py +0 -0
  159. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-311.pyc +0 -0
  160. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/__init__.cpython-313.pyc +0 -0
  161. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-311.pyc +0 -0
  162. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_country_template.cpython-313.pyc +0 -0
  163. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-311.pyc +0 -0
  164. openfisca_core/scripts/migrations/v16_2_to_v17/__pycache__/xml_to_yaml_extension_template.cpython-313.pyc +0 -0
  165. openfisca_core/scripts/migrations/v16_2_to_v17/legislation.xsd +102 -0
  166. openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_country_template.py +30 -0
  167. openfisca_core/scripts/migrations/v16_2_to_v17/xml_to_yaml_extension_template.py +26 -0
  168. openfisca_core/scripts/migrations/v24_to_25.py +156 -0
  169. openfisca_core/scripts/openfisca_command.py +192 -0
  170. openfisca_core/scripts/remove_fuzzy.py +213 -0
  171. openfisca_core/scripts/run_test.py +51 -0
  172. openfisca_core/scripts/simulation_generator.py +90 -0
  173. openfisca_core/simulation_builder.py +16 -0
  174. openfisca_core/simulations/__init__.py +45 -0
  175. openfisca_core/simulations/_build_default_simulation.py +158 -0
  176. openfisca_core/simulations/_build_from_variables.py +230 -0
  177. openfisca_core/simulations/_type_guards.py +298 -0
  178. openfisca_core/simulations/helpers.py +111 -0
  179. openfisca_core/simulations/simulation.py +618 -0
  180. openfisca_core/simulations/simulation_builder.py +855 -0
  181. openfisca_core/simulations/typing.py +203 -0
  182. openfisca_core/taxbenefitsystems/__init__.py +26 -0
  183. openfisca_core/taxbenefitsystems/tax_benefit_system.py +584 -0
  184. openfisca_core/taxscales/__init__.py +35 -0
  185. openfisca_core/taxscales/abstract_rate_tax_scale.py +41 -0
  186. openfisca_core/taxscales/abstract_tax_scale.py +53 -0
  187. openfisca_core/taxscales/amount_tax_scale_like.py +55 -0
  188. openfisca_core/taxscales/helpers.py +44 -0
  189. openfisca_core/taxscales/linear_average_rate_tax_scale.py +76 -0
  190. openfisca_core/taxscales/marginal_amount_tax_scale.py +34 -0
  191. openfisca_core/taxscales/marginal_rate_tax_scale.py +286 -0
  192. openfisca_core/taxscales/rate_tax_scale_like.py +214 -0
  193. openfisca_core/taxscales/single_amount_tax_scale.py +32 -0
  194. openfisca_core/taxscales/tax_scale_like.py +65 -0
  195. openfisca_core/tools/__init__.py +94 -0
  196. openfisca_core/tools/parallel_plugin.py +55 -0
  197. openfisca_core/tools/simulation_dumper.py +137 -0
  198. openfisca_core/tools/test_runner.py +802 -0
  199. openfisca_core/tracers/__init__.py +40 -0
  200. openfisca_core/tracers/computation_log.py +196 -0
  201. openfisca_core/tracers/flat_trace.py +87 -0
  202. openfisca_core/tracers/full_tracer.py +172 -0
  203. openfisca_core/tracers/performance_log.py +133 -0
  204. openfisca_core/tracers/simple_tracer.py +64 -0
  205. openfisca_core/tracers/trace_node.py +121 -0
  206. openfisca_core/tracers/tracing_parameter_node_at_instant.py +81 -0
  207. openfisca_core/types.py +548 -0
  208. openfisca_core/variables/__init__.py +26 -0
  209. openfisca_core/variables/config.py +53 -0
  210. openfisca_core/variables/helpers.py +67 -0
  211. openfisca_core/variables/tests/__init__.py +0 -0
  212. openfisca_core/variables/tests/test_definition_period.py +43 -0
  213. openfisca_core/variables/variable.py +474 -0
  214. openfisca_core/warnings/__init__.py +25 -0
  215. openfisca_core/warnings/libyaml_warning.py +2 -0
  216. openfisca_core/warnings/tempfile_warning.py +2 -0
  217. openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/CHANGELOG.md +4209 -0
  218. openfisca_core_test-46.0.0.data/data/share/openfisca/openfisca-core/README.md +152 -0
  219. openfisca_core_test-46.0.0.dist-info/METADATA +194 -0
  220. openfisca_core_test-46.0.0.dist-info/RECORD +237 -0
  221. openfisca_core_test-46.0.0.dist-info/WHEEL +5 -0
  222. openfisca_core_test-46.0.0.dist-info/entry_points.txt +3 -0
  223. openfisca_core_test-46.0.0.dist-info/licenses/LICENSE +661 -0
  224. openfisca_core_test-46.0.0.dist-info/top_level.txt +2 -0
  225. openfisca_web_api/__init__.py +0 -0
  226. openfisca_web_api/app.py +226 -0
  227. openfisca_web_api/errors.py +11 -0
  228. openfisca_web_api/handlers.py +90 -0
  229. openfisca_web_api/loader/__init__.py +22 -0
  230. openfisca_web_api/loader/entities.py +28 -0
  231. openfisca_web_api/loader/parameters.py +122 -0
  232. openfisca_web_api/loader/spec.py +168 -0
  233. openfisca_web_api/loader/tax_benefit_system.py +27 -0
  234. openfisca_web_api/loader/variables.py +121 -0
  235. openfisca_web_api/openAPI.yml +434 -0
  236. openfisca_web_api/scripts/__init__.py +0 -0
  237. openfisca_web_api/scripts/serve.py +99 -0
@@ -0,0 +1,919 @@
1
+ from __future__ import annotations
2
+
3
+ import calendar
4
+ import datetime
5
+ from collections.abc import Sequence
6
+
7
+ import pendulum
8
+
9
+ from openfisca_core import types as t
10
+
11
+ from . import helpers
12
+ from .date_unit import DateUnit
13
+ from .instant_ import Instant
14
+
15
+
16
+ class Period(tuple[t.DateUnit, t.Instant, int]):
17
+ """Toolbox to handle date intervals.
18
+
19
+ A :class:`.Period` is a triple (``unit``, ``start``, ``size``).
20
+
21
+ Attributes:
22
+ unit (:obj:`str`):
23
+ Either ``year``, ``month``, ``day`` or ``eternity``.
24
+ start (:obj:`.Instant`):
25
+ The "instant" the :obj:`.Period` starts at.
26
+ size (:obj:`int`):
27
+ The amount of ``unit``, starting at ``start``, at least ``1``.
28
+
29
+ Args:
30
+ (tuple(tuple(str, .Instant, int))):
31
+ The ``unit``, ``start``, and ``size``, accordingly.
32
+
33
+ Examples:
34
+ >>> instant = Instant((2021, 10, 1))
35
+ >>> period = Period((DateUnit.YEAR, instant, 3))
36
+
37
+ >>> repr(Period)
38
+ "<class 'openfisca_core.periods.period_.Period'>"
39
+
40
+ >>> repr(period)
41
+ "Period((<DateUnit.YEAR: 'year'>, Instant((2021, 10, 1)), 3))"
42
+
43
+ >>> str(period)
44
+ 'year:2021-10:3'
45
+
46
+ >>> dict([period, instant])
47
+ Traceback (most recent call last):
48
+ ValueError: dictionary update sequence element #0 has length 3...
49
+
50
+ >>> list(period)
51
+ [<DateUnit.YEAR: 'year'>, Instant((2021, 10, 1)), 3]
52
+
53
+ >>> period[0]
54
+ <DateUnit.YEAR: 'year'>
55
+
56
+ >>> period[0] in period
57
+ True
58
+
59
+ >>> len(period)
60
+ 3
61
+
62
+ >>> period == Period((DateUnit.YEAR, instant, 3))
63
+ True
64
+
65
+ >>> period != Period((DateUnit.YEAR, instant, 3))
66
+ False
67
+
68
+ >>> period > Period((DateUnit.YEAR, instant, 3))
69
+ False
70
+
71
+ >>> period < Period((DateUnit.YEAR, instant, 3))
72
+ False
73
+
74
+ >>> period >= Period((DateUnit.YEAR, instant, 3))
75
+ True
76
+
77
+ >>> period <= Period((DateUnit.YEAR, instant, 3))
78
+ True
79
+
80
+ >>> period.days
81
+ 1096
82
+
83
+ >>> period.size_in_months
84
+ 36
85
+
86
+ >>> period.size_in_days
87
+ 1096
88
+
89
+ >>> period.stop
90
+ Instant((2024, 9, 30))
91
+
92
+ >>> period.unit
93
+ <DateUnit.YEAR: 'year'>
94
+
95
+ >>> period.last_3_months
96
+ Period((<DateUnit.MONTH: 'month'>, Instant((2021, 7, 1)), 3))
97
+
98
+ >>> period.last_month
99
+ Period((<DateUnit.MONTH: 'month'>, Instant((2021, 9, 1)), 1))
100
+
101
+ >>> period.last_year
102
+ Period((<DateUnit.YEAR: 'year'>, Instant((2020, 1, 1)), 1))
103
+
104
+ >>> period.n_2
105
+ Period((<DateUnit.YEAR: 'year'>, Instant((2019, 1, 1)), 1))
106
+
107
+ >>> period.this_year
108
+ Period((<DateUnit.YEAR: 'year'>, Instant((2021, 1, 1)), 1))
109
+
110
+ >>> period.first_month
111
+ Period((<DateUnit.MONTH: 'month'>, Instant((2021, 10, 1)), 1))
112
+
113
+ >>> period.first_day
114
+ Period((<DateUnit.DAY: 'day'>, Instant((2021, 10, 1)), 1))
115
+
116
+
117
+ Since a period is a triple it can be used as a dictionary key.
118
+
119
+ """
120
+
121
+ __slots__ = ()
122
+
123
+ def __repr__(self) -> str:
124
+ return f"{self.__class__.__name__}({super().__repr__()})"
125
+
126
+ def __str__(self) -> t.PeriodStr:
127
+ unit, start_instant, size = self
128
+
129
+ if unit == DateUnit.ETERNITY:
130
+ return t.PeriodStr(unit.upper())
131
+
132
+ # ISO format date units.
133
+ f_year, month, day = start_instant
134
+
135
+ # ISO calendar date units.
136
+ c_year, week, weekday = datetime.date(f_year, month, day).isocalendar()
137
+
138
+ # 1 year long period
139
+ if unit == DateUnit.MONTH and size == 12 or unit == DateUnit.YEAR and size == 1:
140
+ if month == 1:
141
+ # civil year starting from january
142
+ return t.PeriodStr(str(f_year))
143
+ # rolling year
144
+ return t.PeriodStr(f"{DateUnit.YEAR}:{f_year}-{month:02d}")
145
+
146
+ # simple month
147
+ if unit == DateUnit.MONTH and size == 1:
148
+ return t.PeriodStr(f"{f_year}-{month:02d}")
149
+
150
+ # several civil years
151
+ if unit == DateUnit.YEAR and month == 1:
152
+ return t.PeriodStr(f"{unit}:{f_year}:{size}")
153
+
154
+ if unit == DateUnit.DAY:
155
+ if size == 1:
156
+ return t.PeriodStr(f"{f_year}-{month:02d}-{day:02d}")
157
+ return t.PeriodStr(f"{unit}:{f_year}-{month:02d}-{day:02d}:{size}")
158
+
159
+ # 1 week
160
+ if unit == DateUnit.WEEK and size == 1:
161
+ if week < 10:
162
+ return t.PeriodStr(f"{c_year}-W0{week}")
163
+
164
+ return t.PeriodStr(f"{c_year}-W{week}")
165
+
166
+ # several weeks
167
+ if unit == DateUnit.WEEK and size > 1:
168
+ if week < 10:
169
+ return t.PeriodStr(f"{unit}:{c_year}-W0{week}:{size}")
170
+
171
+ return t.PeriodStr(f"{unit}:{c_year}-W{week}:{size}")
172
+
173
+ # 1 weekday
174
+ if unit == DateUnit.WEEKDAY and size == 1:
175
+ if week < 10:
176
+ return t.PeriodStr(f"{c_year}-W0{week}-{weekday}")
177
+
178
+ return t.PeriodStr(f"{c_year}-W{week}-{weekday}")
179
+
180
+ # several weekdays
181
+ if unit == DateUnit.WEEKDAY and size > 1:
182
+ if week < 10:
183
+ return t.PeriodStr(f"{unit}:{c_year}-W0{week}-{weekday}:{size}")
184
+
185
+ return t.PeriodStr(f"{unit}:{c_year}-W{week}-{weekday}:{size}")
186
+
187
+ # complex period
188
+ return t.PeriodStr(f"{unit}:{f_year}-{month:02d}:{size}")
189
+
190
+ @property
191
+ def unit(self) -> t.DateUnit:
192
+ """The ``unit`` of the ``Period``.
193
+
194
+ Example:
195
+ >>> instant = Instant((2021, 10, 1))
196
+ >>> period = Period((DateUnit.YEAR, instant, 3))
197
+ >>> period.unit
198
+ <DateUnit.YEAR: 'year'>
199
+
200
+ """
201
+ return self[0]
202
+
203
+ @property
204
+ def start(self) -> t.Instant:
205
+ """The ``Instant`` at which the ``Period`` starts.
206
+
207
+ Example:
208
+ >>> instant = Instant((2021, 10, 1))
209
+ >>> period = Period((DateUnit.YEAR, instant, 3))
210
+ >>> period.start
211
+ Instant((2021, 10, 1))
212
+
213
+ """
214
+ return self[1]
215
+
216
+ @property
217
+ def size(self) -> int:
218
+ """The ``size`` of the ``Period``.
219
+
220
+ Example:
221
+ >>> instant = Instant((2021, 10, 1))
222
+ >>> period = Period((DateUnit.YEAR, instant, 3))
223
+ >>> period.size
224
+ 3
225
+
226
+ """
227
+ return self[2]
228
+
229
+ @property
230
+ def date(self) -> pendulum.Date:
231
+ """The date representation of the ``Period`` start date.
232
+
233
+ Examples:
234
+ >>> instant = Instant((2021, 10, 1))
235
+
236
+ >>> period = Period((DateUnit.YEAR, instant, 1))
237
+ >>> period.date
238
+ Date(2021, 10, 1)
239
+
240
+ >>> period = Period((DateUnit.YEAR, instant, 3))
241
+ >>> period.date
242
+ Traceback (most recent call last):
243
+ ValueError: "date" is undefined for a period of size > 1: year:2021-10:3.
244
+
245
+ """
246
+ if self.size != 1:
247
+ msg = f'"date" is undefined for a period of size > 1: {self}.'
248
+ raise ValueError(msg)
249
+
250
+ return self.start.date
251
+
252
+ @property
253
+ def size_in_years(self) -> int:
254
+ """The ``size`` of the ``Period`` in years.
255
+
256
+ Examples:
257
+ >>> instant = Instant((2021, 10, 1))
258
+
259
+ >>> period = Period((DateUnit.YEAR, instant, 3))
260
+ >>> period.size_in_years
261
+ 3
262
+
263
+ >>> period = Period((DateUnit.MONTH, instant, 3))
264
+ >>> period.size_in_years
265
+ Traceback (most recent call last):
266
+ ValueError: Can't calculate number of years in a month.
267
+
268
+ """
269
+ if self.unit == DateUnit.YEAR:
270
+ return self.size
271
+
272
+ msg = f"Can't calculate number of years in a {self.unit}."
273
+ raise ValueError(msg)
274
+
275
+ @property
276
+ def size_in_months(self) -> int:
277
+ """The ``size`` of the ``Period`` in months.
278
+
279
+ Examples:
280
+ >>> instant = Instant((2021, 10, 1))
281
+
282
+ >>> period = Period((DateUnit.YEAR, instant, 3))
283
+ >>> period.size_in_months
284
+ 36
285
+
286
+ >>> period = Period((DateUnit.DAY, instant, 3))
287
+ >>> period.size_in_months
288
+ Traceback (most recent call last):
289
+ ValueError: Can't calculate number of months in a day.
290
+
291
+ """
292
+ if self.unit == DateUnit.YEAR:
293
+ return self.size * 12
294
+
295
+ if self.unit == DateUnit.MONTH:
296
+ return self.size
297
+
298
+ msg = f"Can't calculate number of months in a {self.unit}."
299
+ raise ValueError(msg)
300
+
301
+ @property
302
+ def size_in_days(self) -> int:
303
+ """The ``size`` of the ``Period`` in days.
304
+
305
+ Examples:
306
+ >>> instant = Instant((2019, 10, 1))
307
+
308
+ >>> period = Period((DateUnit.YEAR, instant, 3))
309
+ >>> period.size_in_days
310
+ 1096
311
+
312
+ >>> period = Period((DateUnit.MONTH, instant, 3))
313
+ >>> period.size_in_days
314
+ 92
315
+
316
+ """
317
+ if self.unit in (DateUnit.YEAR, DateUnit.MONTH):
318
+ last = self.start.offset(self.size, self.unit)
319
+ if last is None:
320
+ raise NotImplementedError
321
+ last_day = last.offset(-1, DateUnit.DAY)
322
+ if last_day is None:
323
+ raise NotImplementedError
324
+ return (last_day.date - self.start.date).days + 1
325
+
326
+ if self.unit == DateUnit.WEEK:
327
+ return self.size * 7
328
+
329
+ if self.unit in (DateUnit.DAY, DateUnit.WEEKDAY):
330
+ return self.size
331
+
332
+ msg = f"Can't calculate number of days in a {self.unit}."
333
+ raise ValueError(msg)
334
+
335
+ @property
336
+ def size_in_weeks(self) -> int:
337
+ """The ``size`` of the ``Period`` in weeks.
338
+
339
+ Examples:
340
+ >>> instant = Instant((2019, 10, 1))
341
+
342
+ >>> period = Period((DateUnit.YEAR, instant, 3))
343
+ >>> period.size_in_weeks
344
+ 156
345
+
346
+ >>> period = Period((DateUnit.YEAR, instant, 5))
347
+ >>> period.size_in_weeks
348
+ 261
349
+
350
+ """
351
+ if self.unit == DateUnit.YEAR:
352
+ start = self.start.date
353
+ cease = start.add(years=self.size)
354
+ delta = start.diff(cease)
355
+ return delta.in_weeks()
356
+
357
+ if self.unit == DateUnit.MONTH:
358
+ start = self.start.date
359
+ cease = start.add(months=self.size)
360
+ delta = start.diff(cease)
361
+ return delta.in_weeks()
362
+
363
+ if self.unit == DateUnit.WEEK:
364
+ return self.size
365
+
366
+ msg = f"Can't calculate number of weeks in a {self.unit}."
367
+ raise ValueError(msg)
368
+
369
+ @property
370
+ def size_in_weekdays(self) -> int:
371
+ """The ``size`` of the ``Period`` in weekdays.
372
+
373
+ Examples:
374
+ >>> instant = Instant((2019, 10, 1))
375
+
376
+ >>> period = Period((DateUnit.YEAR, instant, 3))
377
+ >>> period.size_in_weekdays
378
+ 1092
379
+
380
+ >>> period = Period((DateUnit.WEEK, instant, 3))
381
+ >>> period.size_in_weekdays
382
+ 21
383
+
384
+ """
385
+ if self.unit == DateUnit.YEAR:
386
+ return self.size_in_weeks * 7
387
+
388
+ if DateUnit.MONTH in self.unit:
389
+ last = self.start.offset(self.size, self.unit)
390
+ if last is None:
391
+ raise NotImplementedError
392
+ last_day = last.offset(-1, DateUnit.DAY)
393
+ if last_day is None:
394
+ raise NotImplementedError
395
+ return (last_day.date - self.start.date).days + 1
396
+
397
+ if self.unit == DateUnit.WEEK:
398
+ return self.size * 7
399
+
400
+ if self.unit in (DateUnit.DAY, DateUnit.WEEKDAY):
401
+ return self.size
402
+
403
+ msg = f"Can't calculate number of weekdays in a {self.unit}."
404
+ raise ValueError(msg)
405
+
406
+ @property
407
+ def days(self) -> int:
408
+ """Same as ``size_in_days``."""
409
+ return (self.stop.date - self.start.date).days + 1
410
+
411
+ def intersection(
412
+ self, start: t.Instant | None, stop: t.Instant | None
413
+ ) -> t.Period | None:
414
+ if start is None and stop is None:
415
+ return self
416
+ period_start = self[1]
417
+ period_stop = self.stop
418
+ if start is None:
419
+ start = period_start
420
+ if stop is None:
421
+ stop = period_stop
422
+ if stop < period_start or period_stop < start:
423
+ return None
424
+ intersection_start = max(period_start, start)
425
+ intersection_stop = min(period_stop, stop)
426
+ if intersection_start == period_start and intersection_stop == period_stop:
427
+ return self
428
+ if (
429
+ intersection_start.day == 1
430
+ and intersection_start.month == 1
431
+ and intersection_stop.day == 31
432
+ and intersection_stop.month == 12
433
+ ):
434
+ return self.__class__(
435
+ (
436
+ DateUnit.YEAR,
437
+ intersection_start,
438
+ intersection_stop.year - intersection_start.year + 1,
439
+ ),
440
+ )
441
+ if (
442
+ intersection_start.day == 1
443
+ and intersection_stop.day
444
+ == calendar.monthrange(intersection_stop.year, intersection_stop.month)[1]
445
+ ):
446
+ return self.__class__(
447
+ (
448
+ DateUnit.MONTH,
449
+ intersection_start,
450
+ (
451
+ (intersection_stop.year - intersection_start.year) * 12
452
+ + intersection_stop.month
453
+ - intersection_start.month
454
+ + 1
455
+ ),
456
+ ),
457
+ )
458
+ return self.__class__(
459
+ (
460
+ DateUnit.DAY,
461
+ intersection_start,
462
+ (intersection_stop.date - intersection_start.date).days + 1,
463
+ ),
464
+ )
465
+
466
+ def get_subperiods(self, unit: t.DateUnit) -> Sequence[t.Period]:
467
+ """Return the list of periods of unit ``unit`` contained in self.
468
+
469
+ Examples:
470
+ >>> period = Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1))
471
+ >>> period.get_subperiods(DateUnit.MONTH)
472
+ [Period((<DateUnit.MONTH: 'month'>, Instant((2021, 1, 1)), 1)),...]
473
+
474
+ >>> period = Period((DateUnit.YEAR, Instant((2021, 1, 1)), 2))
475
+ >>> period.get_subperiods(DateUnit.YEAR)
476
+ [Period((<DateUnit.YEAR: 'year'>, Instant((2021, 1, 1)), 1)), P...]
477
+
478
+ """
479
+ if helpers.unit_weight(self.unit) < helpers.unit_weight(unit):
480
+ msg = f"Cannot subdivide {self.unit} into {unit}"
481
+ raise ValueError(msg)
482
+
483
+ if unit == DateUnit.YEAR:
484
+ return [self.this_year.offset(i, DateUnit.YEAR) for i in range(self.size)]
485
+
486
+ if unit == DateUnit.MONTH:
487
+ return [
488
+ self.first_month.offset(i, DateUnit.MONTH)
489
+ for i in range(self.size_in_months)
490
+ ]
491
+
492
+ if unit == DateUnit.DAY:
493
+ return [
494
+ self.first_day.offset(i, DateUnit.DAY) for i in range(self.size_in_days)
495
+ ]
496
+
497
+ if unit == DateUnit.WEEK:
498
+ return [
499
+ self.first_week.offset(i, DateUnit.WEEK)
500
+ for i in range(self.size_in_weeks)
501
+ ]
502
+
503
+ if unit == DateUnit.WEEKDAY:
504
+ return [
505
+ self.first_weekday.offset(i, DateUnit.WEEKDAY)
506
+ for i in range(self.size_in_weekdays)
507
+ ]
508
+
509
+ msg = f"Cannot subdivide {self.unit} into {unit}"
510
+ raise ValueError(msg)
511
+
512
+ def offset(self, offset: str | int, unit: t.DateUnit | None = None) -> t.Period:
513
+ """Increment (or decrement) the given period with offset units.
514
+
515
+ Examples:
516
+ >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(1)
517
+ Period((<DateUnit.DAY: 'day'>, Instant((2021, 1, 2)), 365))
518
+
519
+ >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(
520
+ ... 1, DateUnit.DAY
521
+ ... )
522
+ Period((<DateUnit.DAY: 'day'>, Instant((2021, 1, 2)), 365))
523
+
524
+ >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(
525
+ ... 1, DateUnit.MONTH
526
+ ... )
527
+ Period((<DateUnit.DAY: 'day'>, Instant((2021, 2, 1)), 365))
528
+
529
+ >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(
530
+ ... 1, DateUnit.YEAR
531
+ ... )
532
+ Period((<DateUnit.DAY: 'day'>, Instant((2022, 1, 1)), 365))
533
+
534
+ >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(1)
535
+ Period((<DateUnit.MONTH: 'month'>, Instant((2021, 2, 1)), 12))
536
+
537
+ >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(
538
+ ... 1, DateUnit.DAY
539
+ ... )
540
+ Period((<DateUnit.MONTH: 'month'>, Instant((2021, 1, 2)), 12))
541
+
542
+ >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(
543
+ ... 1, DateUnit.MONTH
544
+ ... )
545
+ Period((<DateUnit.MONTH: 'month'>, Instant((2021, 2, 1)), 12))
546
+
547
+ >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(
548
+ ... 1, DateUnit.YEAR
549
+ ... )
550
+ Period((<DateUnit.MONTH: 'month'>, Instant((2022, 1, 1)), 12))
551
+
552
+ >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset(1)
553
+ Period((<DateUnit.YEAR: 'year'>, Instant((2022, 1, 1)), 1))
554
+
555
+ >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset(
556
+ ... 1, DateUnit.DAY
557
+ ... )
558
+ Period((<DateUnit.YEAR: 'year'>, Instant((2021, 1, 2)), 1))
559
+
560
+ >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset(
561
+ ... 1, DateUnit.MONTH
562
+ ... )
563
+ Period((<DateUnit.YEAR: 'year'>, Instant((2021, 2, 1)), 1))
564
+
565
+ >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset(
566
+ ... 1, DateUnit.YEAR
567
+ ... )
568
+ Period((<DateUnit.YEAR: 'year'>, Instant((2022, 1, 1)), 1))
569
+
570
+ >>> Period((DateUnit.DAY, Instant((2011, 2, 28)), 1)).offset(1)
571
+ Period((<DateUnit.DAY: 'day'>, Instant((2011, 3, 1)), 1))
572
+
573
+ >>> Period((DateUnit.MONTH, Instant((2011, 2, 28)), 1)).offset(1)
574
+ Period((<DateUnit.MONTH: 'month'>, Instant((2011, 3, 28)), 1))
575
+
576
+ >>> Period((DateUnit.YEAR, Instant((2011, 2, 28)), 1)).offset(1)
577
+ Period((<DateUnit.YEAR: 'year'>, Instant((2012, 2, 28)), 1))
578
+
579
+ >>> Period((DateUnit.DAY, Instant((2011, 3, 1)), 1)).offset(-1)
580
+ Period((<DateUnit.DAY: 'day'>, Instant((2011, 2, 28)), 1))
581
+
582
+ >>> Period((DateUnit.MONTH, Instant((2011, 3, 1)), 1)).offset(-1)
583
+ Period((<DateUnit.MONTH: 'month'>, Instant((2011, 2, 1)), 1))
584
+
585
+ >>> Period((DateUnit.YEAR, Instant((2011, 3, 1)), 1)).offset(-1)
586
+ Period((<DateUnit.YEAR: 'year'>, Instant((2010, 3, 1)), 1))
587
+
588
+ >>> Period((DateUnit.DAY, Instant((2014, 1, 30)), 1)).offset(3)
589
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 2)), 1))
590
+
591
+ >>> Period((DateUnit.MONTH, Instant((2014, 1, 30)), 1)).offset(3)
592
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 4, 30)), 1))
593
+
594
+ >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset(3)
595
+ Period((<DateUnit.YEAR: 'year'>, Instant((2017, 1, 30)), 1))
596
+
597
+ >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(-3)
598
+ Period((<DateUnit.DAY: 'day'>, Instant((2020, 12, 29)), 365))
599
+
600
+ >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(-3)
601
+ Period((<DateUnit.MONTH: 'month'>, Instant((2020, 10, 1)), 12))
602
+
603
+ >>> Period((DateUnit.YEAR, Instant((2014, 1, 1)), 1)).offset(-3)
604
+ Period((<DateUnit.YEAR: 'year'>, Instant((2011, 1, 1)), 1))
605
+
606
+ >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset(
607
+ ... "first-of", DateUnit.MONTH
608
+ ... )
609
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 1)), 1))
610
+
611
+ >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset(
612
+ ... "first-of", DateUnit.YEAR
613
+ ... )
614
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 1, 1)), 1))
615
+
616
+ >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset(
617
+ ... "first-of", DateUnit.MONTH
618
+ ... )
619
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 1)), 4))
620
+
621
+ >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset(
622
+ ... "first-of", DateUnit.YEAR
623
+ ... )
624
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 1, 1)), 4))
625
+
626
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("first-of")
627
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 1)), 1))
628
+
629
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset(
630
+ ... "first-of", DateUnit.MONTH
631
+ ... )
632
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 1)), 1))
633
+
634
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset(
635
+ ... "first-of", DateUnit.YEAR
636
+ ... )
637
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 1, 1)), 1))
638
+
639
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("first-of")
640
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 1)), 4))
641
+
642
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset(
643
+ ... "first-of", DateUnit.MONTH
644
+ ... )
645
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 1)), 4))
646
+
647
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset(
648
+ ... "first-of", DateUnit.YEAR
649
+ ... )
650
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 1, 1)), 4))
651
+
652
+ >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset("first-of")
653
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1))
654
+
655
+ >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset(
656
+ ... "first-of", DateUnit.MONTH
657
+ ... )
658
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1))
659
+
660
+ >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset(
661
+ ... "first-of", DateUnit.YEAR
662
+ ... )
663
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1))
664
+
665
+ >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("first-of")
666
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1))
667
+
668
+ >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset(
669
+ ... "first-of", DateUnit.MONTH
670
+ ... )
671
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 2, 1)), 1))
672
+
673
+ >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset(
674
+ ... "first-of", DateUnit.YEAR
675
+ ... )
676
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1))
677
+
678
+ >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset(
679
+ ... "last-of", DateUnit.MONTH
680
+ ... )
681
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 28)), 1))
682
+
683
+ >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset(
684
+ ... "last-of", DateUnit.YEAR
685
+ ... )
686
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 12, 31)), 1))
687
+
688
+ >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset(
689
+ ... "last-of", DateUnit.MONTH
690
+ ... )
691
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 28)), 4))
692
+
693
+ >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset(
694
+ ... "last-of", DateUnit.YEAR
695
+ ... )
696
+ Period((<DateUnit.DAY: 'day'>, Instant((2014, 12, 31)), 4))
697
+
698
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("last-of")
699
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 28)), 1))
700
+
701
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset(
702
+ ... "last-of", DateUnit.MONTH
703
+ ... )
704
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 28)), 1))
705
+
706
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset(
707
+ ... "last-of", DateUnit.YEAR
708
+ ... )
709
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 12, 31)), 1))
710
+
711
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("last-of")
712
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 28)), 4))
713
+
714
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset(
715
+ ... "last-of", DateUnit.MONTH
716
+ ... )
717
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 28)), 4))
718
+
719
+ >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset(
720
+ ... "last-of", DateUnit.YEAR
721
+ ... )
722
+ Period((<DateUnit.MONTH: 'month'>, Instant((2014, 12, 31)), 4))
723
+
724
+ >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("last-of")
725
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 12, 31)), 1))
726
+
727
+ >>> Period((DateUnit.YEAR, Instant((2014, 1, 1)), 1)).offset(
728
+ ... "last-of", DateUnit.MONTH
729
+ ... )
730
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 31)), 1))
731
+
732
+ >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset(
733
+ ... "last-of", DateUnit.YEAR
734
+ ... )
735
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 12, 31)), 1))
736
+
737
+ >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("last-of")
738
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 12, 31)), 1))
739
+
740
+ >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset(
741
+ ... "last-of", DateUnit.MONTH
742
+ ... )
743
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 2, 28)), 1))
744
+
745
+ >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset(
746
+ ... "last-of", DateUnit.YEAR
747
+ ... )
748
+ Period((<DateUnit.YEAR: 'year'>, Instant((2014, 12, 31)), 1))
749
+
750
+ """
751
+
752
+ start: None | t.Instant = self[1].offset(
753
+ offset, self[0] if unit is None else unit
754
+ )
755
+
756
+ if start is None:
757
+ raise NotImplementedError
758
+
759
+ return self.__class__(
760
+ (
761
+ self[0],
762
+ start,
763
+ self[2],
764
+ ),
765
+ )
766
+
767
+ def contains(self, other: t.Period) -> bool:
768
+ """Returns ``True`` if the period contains ``other``.
769
+
770
+ For instance, ``period(2015)`` contains ``period(2015-01)``.
771
+
772
+ """
773
+ return self.start <= other.start and self.stop >= other.stop
774
+
775
+ @property
776
+ def stop(self) -> t.Instant:
777
+ """Return the last day of the period as an Instant instance.
778
+
779
+ Examples:
780
+ >>> Period((DateUnit.YEAR, Instant((2022, 1, 1)), 1)).stop
781
+ Instant((2022, 12, 31))
782
+
783
+ >>> Period((DateUnit.MONTH, Instant((2022, 1, 1)), 12)).stop
784
+ Instant((2022, 12, 31))
785
+
786
+ >>> Period((DateUnit.DAY, Instant((2022, 1, 1)), 365)).stop
787
+ Instant((2022, 12, 31))
788
+
789
+ >>> Period((DateUnit.YEAR, Instant((2012, 2, 29)), 1)).stop
790
+ Instant((2013, 2, 27))
791
+
792
+ >>> Period((DateUnit.MONTH, Instant((2012, 2, 29)), 1)).stop
793
+ Instant((2012, 3, 28))
794
+
795
+ >>> Period((DateUnit.DAY, Instant((2012, 2, 29)), 1)).stop
796
+ Instant((2012, 2, 29))
797
+
798
+ >>> Period((DateUnit.YEAR, Instant((2012, 2, 29)), 2)).stop
799
+ Instant((2014, 2, 27))
800
+
801
+ >>> Period((DateUnit.MONTH, Instant((2012, 2, 29)), 2)).stop
802
+ Instant((2012, 4, 28))
803
+
804
+ >>> Period((DateUnit.DAY, Instant((2012, 2, 29)), 2)).stop
805
+ Instant((2012, 3, 1))
806
+
807
+ """
808
+ unit, start_instant, size = self
809
+
810
+ if unit == DateUnit.ETERNITY:
811
+ return Instant.eternity()
812
+
813
+ if unit == DateUnit.YEAR:
814
+ date = start_instant.date.add(years=size, days=-1)
815
+ return Instant((date.year, date.month, date.day))
816
+
817
+ if unit == DateUnit.MONTH:
818
+ date = start_instant.date.add(months=size, days=-1)
819
+ return Instant((date.year, date.month, date.day))
820
+
821
+ if unit == DateUnit.WEEK:
822
+ date = start_instant.date.add(weeks=size, days=-1)
823
+ return Instant((date.year, date.month, date.day))
824
+
825
+ if unit in (DateUnit.DAY, DateUnit.WEEKDAY):
826
+ date = start_instant.date.add(days=size - 1)
827
+ return Instant((date.year, date.month, date.day))
828
+
829
+ raise ValueError
830
+
831
+ @property
832
+ def is_eternal(self) -> bool:
833
+ return self == self.eternity()
834
+
835
+ # Reference periods
836
+
837
+ @property
838
+ def last_week(self) -> t.Period:
839
+ return self.first_week.offset(-1)
840
+
841
+ @property
842
+ def last_fortnight(self) -> t.Period:
843
+ start: t.Instant = self.first_week.start
844
+ return self.__class__((DateUnit.WEEK, start, 1)).offset(-2)
845
+
846
+ @property
847
+ def last_2_weeks(self) -> t.Period:
848
+ start: t.Instant = self.first_week.start
849
+ return self.__class__((DateUnit.WEEK, start, 2)).offset(-2)
850
+
851
+ @property
852
+ def last_26_weeks(self) -> t.Period:
853
+ start: t.Instant = self.first_week.start
854
+ return self.__class__((DateUnit.WEEK, start, 26)).offset(-26)
855
+
856
+ @property
857
+ def last_52_weeks(self) -> t.Period:
858
+ start: t.Instant = self.first_week.start
859
+ return self.__class__((DateUnit.WEEK, start, 52)).offset(-52)
860
+
861
+ @property
862
+ def last_month(self) -> t.Period:
863
+ return self.first_month.offset(-1)
864
+
865
+ @property
866
+ def last_3_months(self) -> t.Period:
867
+ start: t.Instant = self.first_month.start
868
+ return self.__class__((DateUnit.MONTH, start, 3)).offset(-3)
869
+
870
+ @property
871
+ def last_year(self) -> t.Period:
872
+ start: None | t.Instant = self.start.offset("first-of", DateUnit.YEAR)
873
+ if start is None:
874
+ raise NotImplementedError
875
+ return self.__class__((DateUnit.YEAR, start, 1)).offset(-1)
876
+
877
+ @property
878
+ def n_2(self) -> t.Period:
879
+ start: None | t.Instant = self.start.offset("first-of", DateUnit.YEAR)
880
+ if start is None:
881
+ raise NotImplementedError
882
+ return self.__class__((DateUnit.YEAR, start, 1)).offset(-2)
883
+
884
+ @property
885
+ def this_year(self) -> t.Period:
886
+ start: None | t.Instant = self.start.offset("first-of", DateUnit.YEAR)
887
+ if start is None:
888
+ raise NotImplementedError
889
+ return self.__class__((DateUnit.YEAR, start, 1))
890
+
891
+ @property
892
+ def first_month(self) -> t.Period:
893
+ start: None | t.Instant = self.start.offset("first-of", DateUnit.MONTH)
894
+ if start is None:
895
+ raise NotImplementedError
896
+ return self.__class__((DateUnit.MONTH, start, 1))
897
+
898
+ @property
899
+ def first_day(self) -> t.Period:
900
+ return self.__class__((DateUnit.DAY, self.start, 1))
901
+
902
+ @property
903
+ def first_week(self) -> t.Period:
904
+ start: None | t.Instant = self.start.offset("first-of", DateUnit.WEEK)
905
+ if start is None:
906
+ raise NotImplementedError
907
+ return self.__class__((DateUnit.WEEK, start, 1))
908
+
909
+ @property
910
+ def first_weekday(self) -> t.Period:
911
+ return self.__class__((DateUnit.WEEKDAY, self.start, 1))
912
+
913
+ @classmethod
914
+ def eternity(cls) -> t.Period:
915
+ """Return an eternity period."""
916
+ return cls((DateUnit.ETERNITY, Instant.eternity(), -1))
917
+
918
+
919
+ __all__ = ["Period"]