value-object-pattern 0.1.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 (73) hide show
  1. value_object_pattern/__init__.py +10 -0
  2. value_object_pattern/decorators/__init__.py +7 -0
  3. value_object_pattern/decorators/value_object_process.py +83 -0
  4. value_object_pattern/decorators/value_object_validation.py +78 -0
  5. value_object_pattern/models/__init__.py +3 -0
  6. value_object_pattern/models/value_object.py +383 -0
  7. value_object_pattern/py.typed +0 -0
  8. value_object_pattern/usables/__init__.py +54 -0
  9. value_object_pattern/usables/dates/__init__.py +9 -0
  10. value_object_pattern/usables/dates/date/__init__.py +7 -0
  11. value_object_pattern/usables/dates/date/date_value_object.py +162 -0
  12. value_object_pattern/usables/dates/date/string_date_value_object.py +201 -0
  13. value_object_pattern/usables/dates/datetime/__init__.py +7 -0
  14. value_object_pattern/usables/dates/datetime/datetime_value_object.py +193 -0
  15. value_object_pattern/usables/dates/datetime/string_datetime_value_object.py +237 -0
  16. value_object_pattern/usables/identifiers/__init__.py +7 -0
  17. value_object_pattern/usables/identifiers/country_ids/__init__.py +3 -0
  18. value_object_pattern/usables/identifiers/country_ids/spain/__init__.py +3 -0
  19. value_object_pattern/usables/identifiers/country_ids/spain/dni_value_object.py +63 -0
  20. value_object_pattern/usables/identifiers/string_uuid_value_object.py +56 -0
  21. value_object_pattern/usables/identifiers/uuid_value_object.py +40 -0
  22. value_object_pattern/usables/internet/__init__.py +38 -0
  23. value_object_pattern/usables/internet/api_keys/__init__.py +13 -0
  24. value_object_pattern/usables/internet/api_keys/aws_access_key_id_value_object.py +40 -0
  25. value_object_pattern/usables/internet/api_keys/aws_secret_access_key_value_object.py +40 -0
  26. value_object_pattern/usables/internet/api_keys/github_personal_access_token_value_object.py +41 -0
  27. value_object_pattern/usables/internet/api_keys/openai_api_key_value_object.py +40 -0
  28. value_object_pattern/usables/internet/api_keys/resend_api_key_value_object.py +40 -0
  29. value_object_pattern/usables/internet/aws_cloud_region_value_object.py +77 -0
  30. value_object_pattern/usables/internet/domain_value_object.py +149 -0
  31. value_object_pattern/usables/internet/host_value_object.py +143 -0
  32. value_object_pattern/usables/internet/ipv4_address_value_object.py +305 -0
  33. value_object_pattern/usables/internet/ipv4_network_value_object.py +165 -0
  34. value_object_pattern/usables/internet/ipv6_address_value_object.py +288 -0
  35. value_object_pattern/usables/internet/ipv6_network_value_object.py +145 -0
  36. value_object_pattern/usables/internet/mac_address_value_object.py +390 -0
  37. value_object_pattern/usables/internet/port_value_object.py +682 -0
  38. value_object_pattern/usables/internet/uri/__init__.py +11 -0
  39. value_object_pattern/usables/internet/uri/http_https_url_value_object.py +39 -0
  40. value_object_pattern/usables/internet/uri/http_url_value_object.py +39 -0
  41. value_object_pattern/usables/internet/uri/https_url_value_object.py +39 -0
  42. value_object_pattern/usables/internet/uri/url_value_object.py +396 -0
  43. value_object_pattern/usables/primitives/__init__.py +45 -0
  44. value_object_pattern/usables/primitives/boolean/__init__.py +9 -0
  45. value_object_pattern/usables/primitives/boolean/boolean_value_object.py +36 -0
  46. value_object_pattern/usables/primitives/boolean/false_value_object.py +37 -0
  47. value_object_pattern/usables/primitives/boolean/true_value_object.py +37 -0
  48. value_object_pattern/usables/primitives/bytes/__init__.py +3 -0
  49. value_object_pattern/usables/primitives/bytes/bytes_value_object.py +36 -0
  50. value_object_pattern/usables/primitives/float/__init__.py +9 -0
  51. value_object_pattern/usables/primitives/float/float_value_object.py +36 -0
  52. value_object_pattern/usables/primitives/float/negative_float_value_object.py +37 -0
  53. value_object_pattern/usables/primitives/float/positive_float_value_object.py +37 -0
  54. value_object_pattern/usables/primitives/integer/__init__.py +13 -0
  55. value_object_pattern/usables/primitives/integer/even_integer_value_object.py +37 -0
  56. value_object_pattern/usables/primitives/integer/integer_value_object.py +36 -0
  57. value_object_pattern/usables/primitives/integer/negative_integer_value_object.py +37 -0
  58. value_object_pattern/usables/primitives/integer/odd_integer_value_object.py +37 -0
  59. value_object_pattern/usables/primitives/integer/positive_integer_value_object.py +37 -0
  60. value_object_pattern/usables/primitives/string/__init__.py +21 -0
  61. value_object_pattern/usables/primitives/string/alpha_value_object.py +37 -0
  62. value_object_pattern/usables/primitives/string/alphanumeric_value_object.py +37 -0
  63. value_object_pattern/usables/primitives/string/digit_value_object.py +37 -0
  64. value_object_pattern/usables/primitives/string/lowercase_string_value_object.py +37 -0
  65. value_object_pattern/usables/primitives/string/non_empty_string_value_object.py +37 -0
  66. value_object_pattern/usables/primitives/string/printable_string_value_object.py +37 -0
  67. value_object_pattern/usables/primitives/string/string_value_object.py +36 -0
  68. value_object_pattern/usables/primitives/string/trimmed_string_value_object.py +37 -0
  69. value_object_pattern/usables/primitives/string/uppercase_string_value_object.py +37 -0
  70. value_object_pattern-0.1.0.dist-info/METADATA +95 -0
  71. value_object_pattern-0.1.0.dist-info/RECORD +73 -0
  72. value_object_pattern-0.1.0.dist-info/WHEEL +4 -0
  73. value_object_pattern-0.1.0.dist-info/licenses/LICENSE.md +21 -0
@@ -0,0 +1,162 @@
1
+ """
2
+ DateValueObject value object.
3
+ """
4
+
5
+ from datetime import UTC, date, datetime
6
+
7
+ from dateutil.relativedelta import relativedelta
8
+
9
+ from value_object_pattern.decorators import validation
10
+ from value_object_pattern.models import ValueObject
11
+
12
+
13
+ class DateValueObject(ValueObject[date]):
14
+ """
15
+ DateValueObject value object ensures the provided value is a date.
16
+
17
+ Example:
18
+ ```python
19
+ from datetime import date
20
+
21
+ from value_object_pattern.usables.dates import DateValueObject
22
+
23
+ now = date(year=1900, month=1, day=1)
24
+ date_ = DateValueObject(value=now)
25
+
26
+ print(repr(date_))
27
+ # >>> DateValueObject(value=1900-01-01)
28
+ ```
29
+ """
30
+
31
+ @validation(order=0)
32
+ def _ensure_value_is_date(self, value: date) -> None:
33
+ """
34
+ Ensures the value object value is a date.
35
+
36
+ Args:
37
+ value (date): Value.
38
+
39
+ Raises:
40
+ TypeError: If the value is not a date.
41
+ """
42
+ if type(value) is not date:
43
+ raise TypeError(f'DateValueObject value <<<{value}>>> must be a date. Got <<<{type(value).__name__}>>> type.') # noqa: E501 # fmt: skip
44
+
45
+ def is_today(self, *, reference_date: date | None = None) -> bool:
46
+ """
47
+ Determines whether the stored date value is today's date.
48
+
49
+ Args:
50
+ reference_date (date | None, optional): The date to compare against. If None, the current date (UTC) is
51
+ used.
52
+
53
+ Raises:
54
+ TypeError: If the value is not a date.
55
+ TypeError: If the reference_date is not a date.
56
+
57
+ Returns:
58
+ bool: True if the stored date matches today's date, False otherwise.
59
+
60
+ Example:
61
+ ```python
62
+ from datetime import date
63
+
64
+ from value_object_pattern.usables.dates import DateValueObject
65
+
66
+ now = date(year=1900, month=1, day=1)
67
+ today = date(year=1900, month=1, day=1)
68
+ is_today = DateValueObject(value=now).is_today(reference_date=today)
69
+
70
+ print(is_today)
71
+ # >>> True
72
+ ```
73
+ """
74
+ if reference_date is None:
75
+ reference_date = datetime.now(tz=UTC).date()
76
+
77
+ DateValueObject(value=reference_date)
78
+
79
+ return self.value == reference_date
80
+
81
+ def is_in_range(self, *, start_date: date, end_date: date) -> bool:
82
+ """
83
+ Determines whether the stored date value falls within the specified date range.
84
+
85
+ Args:
86
+ start_date (date): The beginning of the date range (inclusive).
87
+ end_date (date): The end of the date range (inclusive).
88
+
89
+ Raises:
90
+ TypeError: If start_date is not a date.
91
+ TypeError: If end_date is not a date.
92
+ ValueError: If start_date is later than end_date.
93
+
94
+ Returns:
95
+ bool: True if the stored date is within the range, False otherwise.
96
+
97
+ Example:
98
+ ```python
99
+ from datetime import date
100
+
101
+ from value_object_pattern.usables.dates import DateValueObject
102
+
103
+ now = date(year=1900, month=1, day=1)
104
+ start_date = date(year=1899, month=12, day=31)
105
+ end_date = date(year=1900, month=1, day=2)
106
+ is_in_range = DateValueObject(
107
+ value=now,
108
+ ).is_in_range(
109
+ start_date=start_date,
110
+ end_date=end_date,
111
+ )
112
+
113
+ print(is_in_range)
114
+ # >>> True
115
+ ```
116
+ """
117
+ DateValueObject(value=start_date)
118
+ DateValueObject(value=end_date)
119
+
120
+ if start_date > end_date:
121
+ raise ValueError(f'DateValueObject start_date <<<{start_date.isoformat()}>>> must be earlier than or equal to end_date <<<{end_date.isoformat()}>>>.') # noqa: E501 # fmt: skip
122
+
123
+ return start_date <= self.value <= end_date
124
+
125
+ def calculate_age(self, *, reference_date: date | None = None) -> int:
126
+ """
127
+ Calculates the age of the stored date value.
128
+
129
+ Args:
130
+ reference_date (date | None, optional): The date to calculate the age against. If None, the current date
131
+ (UTC) is used.
132
+
133
+ Raises:
134
+ TypeError: If the reference_date is not a date.
135
+ ValueError: If the stored date is later than the reference_date.
136
+
137
+ Returns:
138
+ int: The age in years of the stored date.
139
+
140
+ Example:
141
+ ```python
142
+ from datetime import date
143
+
144
+ from value_object_pattern.usables.dates import DateValueObject
145
+
146
+ now = date(year=2000, month=1, day=1)
147
+ today = date(year=2000, month=1, day=1)
148
+ age = DateValueObject(value=now).calculate_age(reference_date=today)
149
+
150
+ print(age)
151
+ # >>> 100
152
+ ```
153
+ """
154
+ if reference_date is None:
155
+ reference_date = datetime.now(tz=UTC).date()
156
+
157
+ DateValueObject(value=reference_date)
158
+
159
+ if self.value > reference_date:
160
+ raise ValueError(f'DateValueObject value <<<{self.value.isoformat()}>>> must be earlier than or equal to reference_date <<<{reference_date.isoformat()}>>>.') # noqa: E501 # fmt: skip
161
+
162
+ return relativedelta(dt1=reference_date, dt2=self.value).years
@@ -0,0 +1,201 @@
1
+ """
2
+ StringDateValueObject value object.
3
+ """
4
+
5
+ from datetime import UTC, date, datetime
6
+
7
+ from dateutil.parser import ParserError, parse
8
+ from dateutil.relativedelta import relativedelta
9
+
10
+ from value_object_pattern.decorators import process, validation
11
+ from value_object_pattern.usables import NotEmptyStringValueObject, TrimmedStringValueObject
12
+
13
+ from .date_value_object import DateValueObject
14
+
15
+
16
+ class StringDateValueObject(NotEmptyStringValueObject, TrimmedStringValueObject):
17
+ """
18
+ StringDateValueObject value object ensures the provided value is a valid date.
19
+
20
+ Example:
21
+ ```python
22
+ from value_object_pattern.usables.dates import StringDateValueObject
23
+
24
+ now = '1900-01-01'
25
+ date = StringDateValueObject(value=now)
26
+
27
+ print(repr(date))
28
+ # >>> StringDateValueObject(value=1900-01-01)
29
+ ```
30
+ """
31
+
32
+ @process(order=0)
33
+ def _ensure_value_is_normalized(self, value: str) -> str:
34
+ """
35
+ Ensures the value object value is normalized date string (ISO 8601, YYYY-MM-DD).
36
+
37
+ Args:
38
+ value (str): Value.
39
+
40
+ Returns:
41
+ str: Value with the normalized date string.
42
+ """
43
+ return self._date_normalize(value=value).isoformat()
44
+
45
+ @validation(order=0)
46
+ def _ensure_value_is_date(self, value: str) -> None:
47
+ """
48
+ Ensures the value object value is a date.
49
+
50
+ Args:
51
+ value (str): Value.
52
+
53
+ Raises:
54
+ ValueError: If the value is not a date.
55
+ """
56
+ self._date_normalize(value=value)
57
+
58
+ @classmethod
59
+ def _date_normalize(cls, value: str) -> date:
60
+ """
61
+ Normalizes the given date.
62
+
63
+ Args:
64
+ value (str): Date.
65
+
66
+ Raises:
67
+ TypeError: If the value is not a string.
68
+ ValueError: If the value is not a valid date.
69
+
70
+ Returns:
71
+ str: Normalized date.
72
+ """
73
+ if type(value) is not str:
74
+ raise TypeError(f'StringDateValueObject value <<<{value}>>> must be a string. Got <<<{type(value).__name__}>>> type.') # noqa: E501 # fmt: skip
75
+
76
+ try:
77
+ return parse(timestr=value).date()
78
+
79
+ except ParserError as error:
80
+ raise ValueError(f'StringDateValueObject value <<<{value}>>> is not a valid date.') from error
81
+
82
+ def is_today(self, *, reference_date: date | None = None) -> bool:
83
+ """
84
+ Determines whether the stored date value is today's date.
85
+
86
+ Args:
87
+ reference_date (date | None, optional): The date to compare against. If None, the current date (UTC) is
88
+ used.
89
+
90
+ Raises:
91
+ TypeError: If the reference_date is not a date.
92
+
93
+ Returns:
94
+ bool: True if the stored date matches today's date, False otherwise.
95
+
96
+ Example:
97
+ ```python
98
+ from datetime import date
99
+
100
+ from value_object_pattern.usables.dates import StringDateValueObject
101
+
102
+ now = '1900-01-01'
103
+ today = date(year=1900, month=1, day=1)
104
+ is_today = StringDateValueObject(value=now).is_today(reference_date=today)
105
+
106
+ print(is_today)
107
+ # >>> True
108
+ ```
109
+ """
110
+ if reference_date is None:
111
+ reference_date = datetime.now(tz=UTC).date()
112
+
113
+ date_value = self._date_normalize(value=self.value)
114
+ DateValueObject(value=reference_date)
115
+
116
+ return date_value == reference_date
117
+
118
+ def is_in_range(self, *, start_date: date, end_date: date) -> bool:
119
+ """
120
+ Determines whether the stored date value falls within the specified date range.
121
+
122
+ Args:
123
+ start_date (date): The beginning of the date range (inclusive).
124
+ end_date (date): The end of the date range (inclusive).
125
+
126
+ Raises:
127
+ TypeError: If start_date is not a date.
128
+ TypeError: If end_date is not a date.
129
+ ValueError: If start_date is later than end_date.
130
+
131
+ Returns:
132
+ bool: True if the stored date is within the range, False otherwise.
133
+
134
+ Example:
135
+ ```python
136
+ from datetime import date
137
+
138
+ from value_object_pattern.usables.dates import StringDateValueObject
139
+
140
+ now = '1900-01-01'
141
+ start_date = date(year=1899, month=12, day=31)
142
+ end_date = date(year=1900, month=1, day=2)
143
+ is_in_range = StringDateValueObject(
144
+ value=now,
145
+ ).is_in_range(
146
+ start_date=start_date,
147
+ end_date=end_date,
148
+ )
149
+
150
+ print(is_in_range)
151
+ # >>> True
152
+ ```
153
+ """
154
+ date_value = self._date_normalize(value=self.value)
155
+ DateValueObject(value=start_date)
156
+ DateValueObject(value=end_date)
157
+
158
+ if start_date > end_date:
159
+ raise ValueError(f'StringDateValueObject start_date <<<{start_date.isoformat()}>>> must be earlier than or equal to end_date <<<{end_date.isoformat()}>>>.') # noqa: E501 # fmt: skip
160
+
161
+ return start_date <= date_value <= end_date
162
+
163
+ def calculate_age(self, *, reference_date: date | None = None) -> int:
164
+ """
165
+ Calculates the age of the stored date value.
166
+
167
+ Args:
168
+ reference_date (date | None, optional): The date to calculate the age from. If None, the current date (UTC)
169
+ is used.
170
+
171
+ Raises:
172
+ TypeError: If the reference_date is not a date.
173
+ ValueError: If the stored date is later than the reference_date.
174
+
175
+ Returns:
176
+ int: The age in years of the stored date.
177
+
178
+ Example:
179
+ ```python
180
+ from datetime import date
181
+
182
+ from value_object_pattern.usables.dates import StringDateValueObject
183
+
184
+ now = '1900-01-01'
185
+ today = date(year=2000, month=1, day=1)
186
+ age = StringDateValueObject(value=now).calculate_age(reference_date=today)
187
+
188
+ print(age)
189
+ # >>> 100
190
+ ```
191
+ """
192
+ if reference_date is None:
193
+ reference_date = datetime.now(tz=UTC).date()
194
+
195
+ date_value = self._date_normalize(value=self.value)
196
+ DateValueObject(value=reference_date)
197
+
198
+ if date_value > reference_date:
199
+ raise ValueError(f'StringDateValueObject value <<<{date_value.isoformat()}>>> must be earlier than or equal to reference_date <<<{reference_date.isoformat()}>>>.') # noqa: E501 # fmt: skip
200
+
201
+ return relativedelta(dt1=reference_date, dt2=date_value).years
@@ -0,0 +1,7 @@
1
+ from .datetime_value_object import DatetimeValueObject
2
+ from .string_datetime_value_object import StringDatetimeValueObject
3
+
4
+ __all__ = (
5
+ 'DatetimeValueObject',
6
+ 'StringDatetimeValueObject',
7
+ )
@@ -0,0 +1,193 @@
1
+ """
2
+ DatetimeValueObject value object.
3
+ """
4
+
5
+ from datetime import UTC, datetime
6
+
7
+ from dateutil.relativedelta import relativedelta
8
+
9
+ from value_object_pattern.decorators import validation
10
+ from value_object_pattern.models import ValueObject
11
+
12
+
13
+ class DatetimeValueObject(ValueObject[datetime]):
14
+ """
15
+ DatetimeValueObject value object ensures the provided value is a datetime.
16
+
17
+ Example:
18
+ ```python
19
+ from datetime import UTC, datetime
20
+
21
+ from value_object_pattern.usables.dates import DatetimeValueObject
22
+
23
+ now = datetime(year=1900, month=1, day=1, hour=0, minute=0, second=0, tzinfo=UTC)
24
+ date = DatetimeValueObject(value=now)
25
+
26
+ print(repr(date))
27
+ # >>> DatetimeValueObject(value=1900-01-01T00:00:00+00:00)
28
+ ```
29
+ """
30
+
31
+ @validation(order=0)
32
+ def _ensure_value_is_datetime(self, value: datetime) -> None:
33
+ """
34
+ Ensures the value object value is a datetime.
35
+
36
+ Args:
37
+ value (datetime): Value.
38
+
39
+ Raises:
40
+ TypeError: If the value is not a datetime.
41
+ """
42
+ if type(value) is not datetime:
43
+ raise TypeError(f'DatetimeValueObject value <<<{value}>>> must be a datetime. Got <<<{type(value).__name__}>>> type.') # noqa: E501 # fmt: skip
44
+
45
+ def is_now(self, *, reference_datetime: datetime | None = None) -> bool:
46
+ """
47
+ Determines whether the stored datetime value matches the current datetime.
48
+
49
+ Args:
50
+ reference_datetime (datetime | None, optional): The datetime to compare against. If None, the current
51
+ datetime (UTC) is used.
52
+
53
+ Raises:
54
+ TypeError: If the reference_datetime is not a datetime.
55
+
56
+ Returns:
57
+ bool: True if the stored datetime matches the current datetime, False otherwise.
58
+
59
+ Example:
60
+ ```python
61
+ from datetime import UTC, datetime
62
+
63
+ from value_object_pattern.usables.dates import DatetimeValueObject
64
+
65
+ now = datetime(year=1900, month=1, day=1, hour=8, minute=30, second=0, tzinfo=UTC)
66
+ today = datetime(year=1900, month=1, day=1, hour=0, minute=0, second=0, tzinfo=UTC)
67
+ is_now = DatetimeValueObject(value=now).is_now(reference_datetime=today)
68
+
69
+ print(is_now)
70
+ # >>> False
71
+ ```
72
+ """
73
+ if reference_datetime is None:
74
+ reference_datetime = datetime.now(tz=UTC)
75
+
76
+ DatetimeValueObject(value=reference_datetime)
77
+
78
+ return self.value == reference_datetime
79
+
80
+ def is_today(self, *, reference_datetime: datetime | None = None) -> bool:
81
+ """
82
+ Determines whether the stored datetime value is today's datetime.
83
+
84
+ Args:
85
+ reference_datetime (datetime | None, optional): The datetime to compare against. If None, the current
86
+ datetime (UTC) is used.
87
+
88
+ Raises:
89
+ TypeError: If the reference_datetime is not a datetime.
90
+
91
+ Returns:
92
+ bool: True if the stored datetime matches today's datetime, False otherwise.
93
+
94
+ Example:
95
+ ```python
96
+ from datetime import UTC, datetime
97
+
98
+ from value_object_pattern.usables.dates import DatetimeValueObject
99
+
100
+ now = datetime(year=1900, month=1, day=1, hour=8, minute=30, second=0, tzinfo=UTC)
101
+ today = datetime(year=1900, month=1, day=1, hour=0, minute=0, second=0, tzinfo=UTC)
102
+ is_today = DatetimeValueObject(value=now).is_today(reference_datetime=today)
103
+
104
+ print(is_today)
105
+ # >>> True
106
+ ```
107
+ """
108
+ if reference_datetime is None:
109
+ reference_datetime = datetime.now(tz=UTC)
110
+
111
+ DatetimeValueObject(value=reference_datetime)
112
+
113
+ return self.value.date() == reference_datetime.date()
114
+
115
+ def is_in_range(self, *, start_datetime: datetime, end_datetime: datetime) -> bool:
116
+ """
117
+ Determines whether the stored datetime value falls within the specified datetime range.
118
+
119
+ Args:
120
+ start_datetime (datetime): The beginning of the datetime range (inclusive).
121
+ end_datetime (datetime): The end of the datetime range (inclusive).
122
+
123
+ Raises:
124
+ TypeError: If start_datetime is not a datetime.
125
+ TypeError: If end_datetime is not a datetime.
126
+ ValueError: If start_datetime is later than end_datetime.
127
+
128
+ Returns:
129
+ bool: True if the stored datetime is within the range, False otherwise.
130
+
131
+ Example:
132
+ ```python
133
+ from datetime import UTC, datetime
134
+
135
+ from value_object_pattern.usables.dates import DatetimeValueObject
136
+
137
+ now = datetime(year=1900, month=1, day=1, hour=0, minute=0, second=0, tzinfo=UTC)
138
+ start_datetime = datetime(year=1899, month=12, day=31, hour=23, minute=59, second=59, tzinfo=UTC)
139
+ end_datetime = datetime(year=1900, month=1, day=2, hour=00, minute=00, second=00, tzinfo=UTC)
140
+ is_in_range = DatetimeValueObject(
141
+ value=now,
142
+ ).is_in_range(
143
+ start_datetime=start_datetime,
144
+ end_datetime=end_datetime,
145
+ )
146
+
147
+ print(is_in_range)
148
+ # >>> True
149
+ ```
150
+ """
151
+ DatetimeValueObject(value=start_datetime)
152
+ DatetimeValueObject(value=end_datetime)
153
+
154
+ if start_datetime > end_datetime:
155
+ raise ValueError(f'DatetimeValueObject start_datetime <<<{start_datetime.isoformat()}>>> must be earlier than or equal to end_datetime <<<{end_datetime.isoformat()}>>>.') # noqa: E501 # fmt: skip
156
+
157
+ return start_datetime <= self.value <= end_datetime
158
+
159
+ def calculate_age(self, *, reference_datetime: datetime | None = None) -> int:
160
+ """
161
+ Calculates the age of a given datetime.
162
+
163
+ Args:
164
+ reference_datetime (datetime | None, optional): The datetime to calculate the age against. If None, the
165
+ current datetime (UTC) is used.
166
+
167
+ Raises:
168
+ TypeError: If the reference_datetime is not a datetime.
169
+ ValueError: If the stored datetime is later than the reference_datetime.
170
+
171
+ Returns:
172
+ int: The age in years of the given datetime.
173
+
174
+ Example:
175
+ ```python
176
+ from datetime import UTC, datetime
177
+
178
+ from value_object_pattern.usables.dates import DatetimeValueObject
179
+
180
+ now = datetime(year=1900, month=1, day=1, hour=0, minute=0, second=0, tzinfo=UTC)
181
+ today = datetime(year=2000, month=1, day=1, hour=0, minute=0, second=0, tzinfo=UTC)
182
+ age = DatetimeValueObject(value=now).calculate_age(reference_datetime=today)
183
+
184
+ print(age)
185
+ # >>> 100
186
+ ```
187
+ """
188
+ if reference_datetime is None:
189
+ reference_datetime = datetime.now(tz=UTC)
190
+
191
+ DatetimeValueObject(value=reference_datetime)
192
+
193
+ return relativedelta(dt1=reference_datetime, dt2=self.value).years