opendate 0.1.34__cp39-cp39-win_amd64.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.
- date/__init__.py +3 -0
- opendate/__init__.py +150 -0
- opendate/_opendate.cp39-win_amd64.pyd +0 -0
- opendate/calendars.py +350 -0
- opendate/constants.py +58 -0
- opendate/date_.py +255 -0
- opendate/datetime_.py +339 -0
- opendate/decorators.py +198 -0
- opendate/extras.py +88 -0
- opendate/helpers.py +169 -0
- opendate/interval.py +408 -0
- opendate/metaclass.py +123 -0
- opendate/mixins/__init__.py +4 -0
- opendate/mixins/business.py +295 -0
- opendate/mixins/extras_.py +98 -0
- opendate/time_.py +139 -0
- opendate-0.1.34.dist-info/METADATA +469 -0
- opendate-0.1.34.dist-info/RECORD +20 -0
- opendate-0.1.34.dist-info/WHEEL +4 -0
- opendate-0.1.34.dist-info/licenses/LICENSE +23 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from opendate.calendars import get_calendar, get_default_calendar
|
|
7
|
+
from opendate.constants import MAX_YEAR, MIN_YEAR, WeekDay
|
|
8
|
+
from opendate.decorators import expect_date, store_calendar
|
|
9
|
+
|
|
10
|
+
if sys.version_info >= (3, 11):
|
|
11
|
+
from typing import Self
|
|
12
|
+
else:
|
|
13
|
+
from typing_extensions import Self
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from opendate.calendars import Calendar
|
|
17
|
+
from opendate.datetime_ import DateTime
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DateBusinessMixin:
|
|
21
|
+
"""Mixin class providing business day functionality.
|
|
22
|
+
|
|
23
|
+
This mixin adds business day awareness to Date and DateTime classes,
|
|
24
|
+
allowing date operations to account for weekends and holidays according
|
|
25
|
+
to a specified calendar.
|
|
26
|
+
|
|
27
|
+
Features not available in pendulum:
|
|
28
|
+
- Business day mode toggle
|
|
29
|
+
- Calendar-specific rules (exchanges, custom)
|
|
30
|
+
- Business-aware date arithmetic
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
_calendar: Calendar | None = None
|
|
34
|
+
_business: bool = False
|
|
35
|
+
|
|
36
|
+
def business(self) -> Self:
|
|
37
|
+
"""Switch to business day mode for date calculations.
|
|
38
|
+
|
|
39
|
+
In business day mode, date arithmetic only counts business days
|
|
40
|
+
as defined by the associated calendar (default NYSE).
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
Self instance for method chaining
|
|
44
|
+
"""
|
|
45
|
+
self._business = True
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def b(self) -> Self:
|
|
50
|
+
"""Shorthand property for business() method.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
Self instance for method chaining
|
|
54
|
+
"""
|
|
55
|
+
return self.business()
|
|
56
|
+
|
|
57
|
+
def calendar(self, cal: str | Calendar | None = None) -> Self:
|
|
58
|
+
"""Set the calendar for business day calculations.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
cal: Calendar name (str), Calendar instance, or None for default
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
Self instance for method chaining
|
|
65
|
+
|
|
66
|
+
Examples
|
|
67
|
+
d.calendar('NYSE').b.add(days=1)
|
|
68
|
+
d.calendar('LSE').b.subtract(days=5)
|
|
69
|
+
d.calendar(my_custom_calendar).is_business_day()
|
|
70
|
+
"""
|
|
71
|
+
if cal is None:
|
|
72
|
+
cal = get_default_calendar()
|
|
73
|
+
if isinstance(cal, str):
|
|
74
|
+
self._calendar = get_calendar(cal)
|
|
75
|
+
else:
|
|
76
|
+
self._calendar = cal
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def _active_calendar(self) -> Calendar:
|
|
81
|
+
"""Get the active calendar (uses module default if not set).
|
|
82
|
+
"""
|
|
83
|
+
if self._calendar is None:
|
|
84
|
+
return get_calendar(get_default_calendar())
|
|
85
|
+
return self._calendar
|
|
86
|
+
|
|
87
|
+
def _is_out_of_range(self) -> bool:
|
|
88
|
+
"""Check if date is outside valid calendar range (1900-2100)."""
|
|
89
|
+
return self.year < MIN_YEAR or self.year > MAX_YEAR
|
|
90
|
+
|
|
91
|
+
@store_calendar
|
|
92
|
+
def add(self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0, **kwargs) -> Self:
|
|
93
|
+
"""Add time periods to the current date or datetime.
|
|
94
|
+
|
|
95
|
+
Extends pendulum's add method with business day awareness. When in business mode,
|
|
96
|
+
only counts business days for the 'days' parameter.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
years: Number of years to add
|
|
100
|
+
months: Number of months to add
|
|
101
|
+
weeks: Number of weeks to add
|
|
102
|
+
days: Number of days to add (business days if in business mode)
|
|
103
|
+
**kwargs: Additional time units to add
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
New instance with added time
|
|
107
|
+
"""
|
|
108
|
+
_business = self._business
|
|
109
|
+
self._business = False
|
|
110
|
+
if _business:
|
|
111
|
+
if days == 0:
|
|
112
|
+
return self._business_or_next()
|
|
113
|
+
if days < 0:
|
|
114
|
+
return self.business().subtract(days=abs(days))
|
|
115
|
+
if self._is_out_of_range() and self.year > MAX_YEAR:
|
|
116
|
+
return self
|
|
117
|
+
target = self._business_or_next() if self._is_out_of_range() else self
|
|
118
|
+
result = target._add_business_days(days)
|
|
119
|
+
return result if result is not None else self
|
|
120
|
+
return super().add(years, months, weeks, days, **kwargs)
|
|
121
|
+
|
|
122
|
+
@store_calendar
|
|
123
|
+
def subtract(self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0, **kwargs) -> Self:
|
|
124
|
+
"""Subtract wrapper
|
|
125
|
+
If not business use Pendulum
|
|
126
|
+
If business assume only days (for now) and use local logic
|
|
127
|
+
"""
|
|
128
|
+
_business = self._business
|
|
129
|
+
self._business = False
|
|
130
|
+
if _business:
|
|
131
|
+
if days == 0:
|
|
132
|
+
return self._business_or_previous()
|
|
133
|
+
if days < 0:
|
|
134
|
+
return self.business().add(days=abs(days))
|
|
135
|
+
target = self._business_or_previous() if self._is_out_of_range() else self
|
|
136
|
+
result = target._add_business_days(-days)
|
|
137
|
+
return result if result is not None else self
|
|
138
|
+
kwargs = {k: -1*v for k, v in kwargs.items()}
|
|
139
|
+
return super().add(-years, -months, -weeks, -days, **kwargs)
|
|
140
|
+
|
|
141
|
+
@store_calendar
|
|
142
|
+
def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
|
|
143
|
+
"""Returns an instance set to the first occurrence
|
|
144
|
+
of a given day of the week in the current unit.
|
|
145
|
+
"""
|
|
146
|
+
_business = self._business
|
|
147
|
+
self._business = False
|
|
148
|
+
self = super().first_of(unit, day_of_week)
|
|
149
|
+
if _business:
|
|
150
|
+
self = self._business_or_next()
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
@store_calendar
|
|
154
|
+
def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
|
|
155
|
+
"""Returns an instance set to the last occurrence
|
|
156
|
+
of a given day of the week in the current unit.
|
|
157
|
+
"""
|
|
158
|
+
_business = self._business
|
|
159
|
+
self._business = False
|
|
160
|
+
self = super().last_of(unit, day_of_week)
|
|
161
|
+
if _business:
|
|
162
|
+
self = self._business_or_previous()
|
|
163
|
+
return self
|
|
164
|
+
|
|
165
|
+
@store_calendar
|
|
166
|
+
def start_of(self, unit: str) -> Self:
|
|
167
|
+
"""Returns a copy of the instance with the time reset
|
|
168
|
+
"""
|
|
169
|
+
_business = self._business
|
|
170
|
+
self._business = False
|
|
171
|
+
self = super().start_of(unit)
|
|
172
|
+
if _business:
|
|
173
|
+
self = self._business_or_next()
|
|
174
|
+
return self
|
|
175
|
+
|
|
176
|
+
@store_calendar
|
|
177
|
+
def end_of(self, unit: str) -> Self:
|
|
178
|
+
"""Returns a copy of the instance with the time reset
|
|
179
|
+
"""
|
|
180
|
+
_business = self._business
|
|
181
|
+
self._business = False
|
|
182
|
+
self = super().end_of(unit)
|
|
183
|
+
if _business:
|
|
184
|
+
self = self._business_or_previous()
|
|
185
|
+
return self
|
|
186
|
+
|
|
187
|
+
@store_calendar
|
|
188
|
+
def previous(self, day_of_week: WeekDay | None = None) -> Self:
|
|
189
|
+
"""Modify to the previous occurrence of a given day of the week.
|
|
190
|
+
|
|
191
|
+
In business mode, snaps BACKWARD to maintain 'previous' semantics.
|
|
192
|
+
"""
|
|
193
|
+
_business = self._business
|
|
194
|
+
self._business = False
|
|
195
|
+
self = super().previous(day_of_week)
|
|
196
|
+
if _business:
|
|
197
|
+
self = self._business_or_previous()
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
@store_calendar
|
|
201
|
+
def next(self, day_of_week: WeekDay | None = None) -> Self:
|
|
202
|
+
"""Modify to the next occurrence of a given day of the week.
|
|
203
|
+
|
|
204
|
+
In business mode, snaps FORWARD to maintain 'next' semantics.
|
|
205
|
+
"""
|
|
206
|
+
_business = self._business
|
|
207
|
+
self._business = False
|
|
208
|
+
self = super().next(day_of_week)
|
|
209
|
+
if _business:
|
|
210
|
+
self = self._business_or_next()
|
|
211
|
+
return self
|
|
212
|
+
|
|
213
|
+
@expect_date
|
|
214
|
+
def is_business_day(self) -> bool:
|
|
215
|
+
"""Check if the date is a business day according to the calendar.
|
|
216
|
+
|
|
217
|
+
Returns False for dates outside valid calendar range (1900-2100).
|
|
218
|
+
"""
|
|
219
|
+
if self._is_out_of_range():
|
|
220
|
+
return False
|
|
221
|
+
cal = self._active_calendar._get_calendar(self)
|
|
222
|
+
if cal is None:
|
|
223
|
+
return False
|
|
224
|
+
return cal.is_business_day(self.toordinal())
|
|
225
|
+
|
|
226
|
+
# Alias for backwards compatibility
|
|
227
|
+
business_open = is_business_day
|
|
228
|
+
|
|
229
|
+
@expect_date
|
|
230
|
+
def business_hours(self) -> tuple[DateTime, DateTime]:
|
|
231
|
+
"""Get market open and close times for this date.
|
|
232
|
+
|
|
233
|
+
Returns (None, None) if not a business day.
|
|
234
|
+
"""
|
|
235
|
+
return self._active_calendar.business_hours(self, self)\
|
|
236
|
+
.get(self, (None, None))
|
|
237
|
+
|
|
238
|
+
def _add_business_days(self, days: int) -> Self | None:
|
|
239
|
+
"""Add business days using Rust calendar.
|
|
240
|
+
|
|
241
|
+
Returns self unchanged for dates outside valid range (1900-2100).
|
|
242
|
+
"""
|
|
243
|
+
if self._is_out_of_range():
|
|
244
|
+
return self
|
|
245
|
+
cal = self._active_calendar._get_calendar(self)
|
|
246
|
+
if cal is None:
|
|
247
|
+
return None
|
|
248
|
+
start_ord = self.toordinal()
|
|
249
|
+
forward = days > 0
|
|
250
|
+
offset = 1 if forward else -1
|
|
251
|
+
first_bd = (cal.next_business_day if forward else cal.prev_business_day)(start_ord + offset)
|
|
252
|
+
if first_bd is None:
|
|
253
|
+
return None
|
|
254
|
+
result_ord = cal.add_business_days(first_bd, (abs(days) - 1) * offset)
|
|
255
|
+
if result_ord is None:
|
|
256
|
+
return None
|
|
257
|
+
return super().add(days=result_ord - start_ord)
|
|
258
|
+
|
|
259
|
+
@store_calendar
|
|
260
|
+
def _snap_to_business_day(self, forward: bool = True) -> Self:
|
|
261
|
+
"""Snap to nearest business day if not already on one.
|
|
262
|
+
|
|
263
|
+
For dates outside valid range (1900-2100):
|
|
264
|
+
- If forward=False and year > 2100: snap to last business day of 2100
|
|
265
|
+
- If forward=True and year < 1900: snap to first business day of 1900
|
|
266
|
+
- Otherwise return self unchanged
|
|
267
|
+
"""
|
|
268
|
+
self._business = False
|
|
269
|
+
if self._is_out_of_range():
|
|
270
|
+
from opendate.date_ import Date
|
|
271
|
+
if self.year > MAX_YEAR and not forward:
|
|
272
|
+
boundary = Date(MAX_YEAR, 12, 31)
|
|
273
|
+
boundary._calendar = self._calendar
|
|
274
|
+
return boundary._snap_to_business_day(forward=False)
|
|
275
|
+
elif self.year < MIN_YEAR and forward:
|
|
276
|
+
boundary = Date(MIN_YEAR, 1, 1)
|
|
277
|
+
boundary._calendar = self._calendar
|
|
278
|
+
return boundary._snap_to_business_day(forward=True)
|
|
279
|
+
return self
|
|
280
|
+
cal = self._active_calendar._get_calendar(self)
|
|
281
|
+
if cal is None:
|
|
282
|
+
return self
|
|
283
|
+
if self.is_business_day():
|
|
284
|
+
return self
|
|
285
|
+
ordinal = self.toordinal()
|
|
286
|
+
target = cal.next_business_day(ordinal) if forward else cal.prev_business_day(ordinal)
|
|
287
|
+
if target is None:
|
|
288
|
+
return self
|
|
289
|
+
return super().add(days=target - ordinal)
|
|
290
|
+
|
|
291
|
+
def _business_or_next(self) -> Self:
|
|
292
|
+
return self._snap_to_business_day(forward=True)
|
|
293
|
+
|
|
294
|
+
def _business_or_previous(self) -> Self:
|
|
295
|
+
return self._snap_to_business_day(forward=False)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from opendate.constants import WEEKDAY_SHORTNAME, WeekDay
|
|
7
|
+
from opendate.decorators import store_calendar
|
|
8
|
+
|
|
9
|
+
if sys.version_info >= (3, 11):
|
|
10
|
+
from typing import Self
|
|
11
|
+
else:
|
|
12
|
+
from typing_extensions import Self
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DateExtrasMixin:
|
|
19
|
+
"""Extended date functionality not provided by Pendulum.
|
|
20
|
+
|
|
21
|
+
.. note::
|
|
22
|
+
This mixin exists primarily for legacy backward compatibility.
|
|
23
|
+
New code should prefer using built-in methods where possible.
|
|
24
|
+
|
|
25
|
+
This mixin provides additional date utilities primarily focused on:
|
|
26
|
+
- Financial date calculations (nearest month start/end)
|
|
27
|
+
- Weekday-oriented date navigation
|
|
28
|
+
- Relative date lookups
|
|
29
|
+
|
|
30
|
+
These methods extend OpenDate functionality with features commonly
|
|
31
|
+
needed in financial applications and reporting scenarios.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@store_calendar
|
|
35
|
+
def nearest_start_of_month(self) -> Self:
|
|
36
|
+
"""Get the nearest start of month.
|
|
37
|
+
|
|
38
|
+
If day <= 15, returns start of current month.
|
|
39
|
+
If day > 15, returns start of next month.
|
|
40
|
+
In business mode, snaps to next business day if needed.
|
|
41
|
+
"""
|
|
42
|
+
_business = self._business
|
|
43
|
+
self._business = False
|
|
44
|
+
if self.day > 15:
|
|
45
|
+
d = self.end_of('month').add(days=1) # First of next month
|
|
46
|
+
else:
|
|
47
|
+
d = self.start_of('month') # First of current month
|
|
48
|
+
if _business:
|
|
49
|
+
d = d._business_or_next()
|
|
50
|
+
return d
|
|
51
|
+
|
|
52
|
+
@store_calendar
|
|
53
|
+
def nearest_end_of_month(self) -> Self:
|
|
54
|
+
"""Get the nearest end of month.
|
|
55
|
+
|
|
56
|
+
If day <= 15, returns end of previous month.
|
|
57
|
+
If day > 15, returns end of current month.
|
|
58
|
+
In business mode, snaps to previous business day if needed.
|
|
59
|
+
"""
|
|
60
|
+
_business = self._business
|
|
61
|
+
self._business = False
|
|
62
|
+
if self.day <= 15:
|
|
63
|
+
d = self.start_of('month').subtract(days=1) # End of previous month
|
|
64
|
+
else:
|
|
65
|
+
d = self.end_of('month') # End of current month
|
|
66
|
+
if _business:
|
|
67
|
+
d = d._business_or_previous()
|
|
68
|
+
return d
|
|
69
|
+
|
|
70
|
+
def next_relative_date_of_week_by_day(self, day='MO') -> Self:
|
|
71
|
+
"""Get next occurrence of the specified weekday (or current date if already that day).
|
|
72
|
+
"""
|
|
73
|
+
if self.weekday() == WEEKDAY_SHORTNAME.get(day):
|
|
74
|
+
return self
|
|
75
|
+
return self.next(WEEKDAY_SHORTNAME.get(day))
|
|
76
|
+
|
|
77
|
+
def weekday_or_previous_friday(self) -> Self:
|
|
78
|
+
"""Return the date if it is a weekday, otherwise return the previous Friday.
|
|
79
|
+
"""
|
|
80
|
+
if self.weekday() in {WeekDay.SATURDAY, WeekDay.SUNDAY}:
|
|
81
|
+
return self.previous(WeekDay.FRIDAY)
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def third_wednesday(cls, year, month) -> Self:
|
|
86
|
+
"""Calculate the date of the third Wednesday in a given month/year.
|
|
87
|
+
|
|
88
|
+
.. deprecated::
|
|
89
|
+
Use Date(year, month, 1).nth_of('month', 3, WeekDay.WEDNESDAY) instead.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
year: The year to use
|
|
93
|
+
month: The month to use (1-12)
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
A Date object representing the third Wednesday of the specified month
|
|
97
|
+
"""
|
|
98
|
+
return cls(year, month, 1).nth_of('month', 3, WeekDay.WEDNESDAY)
|
opendate/time_.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime as _datetime
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
import zoneinfo as _zoneinfo
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import pendulum as _pendulum
|
|
11
|
+
|
|
12
|
+
from opendate.constants import UTC
|
|
13
|
+
from opendate.decorators import prefer_utc_timezone
|
|
14
|
+
from opendate.helpers import _rust_parse_time
|
|
15
|
+
|
|
16
|
+
if sys.version_info >= (3, 11):
|
|
17
|
+
from typing import Self
|
|
18
|
+
else:
|
|
19
|
+
from typing_extensions import Self
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Time(_pendulum.Time):
|
|
23
|
+
"""Time class extending pendulum.Time with additional functionality.
|
|
24
|
+
|
|
25
|
+
This class inherits all pendulum.Time functionality while adding:
|
|
26
|
+
- Enhanced parsing for various time formats
|
|
27
|
+
- Default UTC timezone when created
|
|
28
|
+
- Simple timezone conversion utilities
|
|
29
|
+
|
|
30
|
+
Unlike pendulum.Time, this class has more lenient parsing capabilities
|
|
31
|
+
and different timezone defaults.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
@prefer_utc_timezone
|
|
36
|
+
def parse(cls, s: str | None, fmt: str | None = None, raise_err: bool = False) -> Self | None:
|
|
37
|
+
"""Parse time string in various formats.
|
|
38
|
+
|
|
39
|
+
Supported formats:
|
|
40
|
+
- hh:mm or hh.mm
|
|
41
|
+
- hh:mm:ss or hh.mm.ss
|
|
42
|
+
- hh:mm:ss.microseconds
|
|
43
|
+
- Any of above with AM/PM
|
|
44
|
+
- Compact: hhmmss or hhmmss.microseconds
|
|
45
|
+
|
|
46
|
+
Returns Time with UTC timezone by default.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
s: String to parse or None
|
|
50
|
+
fmt: Optional strftime format string for custom parsing
|
|
51
|
+
raise_err: If True, raises ValueError on parse failure instead of returning None
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
Time instance with UTC timezone or None if parsing fails and raise_err is False
|
|
55
|
+
|
|
56
|
+
Examples
|
|
57
|
+
Basic time formats:
|
|
58
|
+
Time.parse('14:30') → Time(14, 30, 0, 0, tzinfo=UTC)
|
|
59
|
+
Time.parse('14.30') → Time(14, 30, 0, 0, tzinfo=UTC)
|
|
60
|
+
Time.parse('14:30:45') → Time(14, 30, 45, 0, tzinfo=UTC)
|
|
61
|
+
|
|
62
|
+
With microseconds:
|
|
63
|
+
Time.parse('14:30:45.123456') → Time(14, 30, 45, 123456000, tzinfo=UTC)
|
|
64
|
+
Time.parse('14:30:45,500000') → Time(14, 30, 45, 500000000, tzinfo=UTC)
|
|
65
|
+
|
|
66
|
+
AM/PM formats:
|
|
67
|
+
Time.parse('2:30 PM') → Time(14, 30, 0, 0, tzinfo=UTC)
|
|
68
|
+
Time.parse('11:30 AM') → Time(11, 30, 0, 0, tzinfo=UTC)
|
|
69
|
+
Time.parse('12:30 PM') → Time(12, 30, 0, 0, tzinfo=UTC)
|
|
70
|
+
|
|
71
|
+
Compact formats:
|
|
72
|
+
Time.parse('143045') → Time(14, 30, 45, 0, tzinfo=UTC)
|
|
73
|
+
Time.parse('1430') → Time(14, 30, 0, 0, tzinfo=UTC)
|
|
74
|
+
|
|
75
|
+
Custom format:
|
|
76
|
+
Time.parse('14-30-45', fmt='%H-%M-%S') → Time(14, 30, 45, 0, tzinfo=UTC)
|
|
77
|
+
"""
|
|
78
|
+
if not s:
|
|
79
|
+
if raise_err:
|
|
80
|
+
raise ValueError('Empty value')
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
if not isinstance(s, str):
|
|
84
|
+
raise TypeError(f'Invalid type for time parse: {s.__class__}')
|
|
85
|
+
|
|
86
|
+
if fmt:
|
|
87
|
+
try:
|
|
88
|
+
return cls(*time.strptime(s, fmt)[3:6])
|
|
89
|
+
except (ValueError, TypeError):
|
|
90
|
+
if raise_err:
|
|
91
|
+
raise ValueError(f'Unable to parse {s} using fmt {fmt}')
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
result = _rust_parse_time(s)
|
|
95
|
+
if result is not None:
|
|
96
|
+
hour, minute, second, microsecond = result
|
|
97
|
+
return cls(hour, minute, second, microsecond)
|
|
98
|
+
|
|
99
|
+
if raise_err:
|
|
100
|
+
raise ValueError('Failed to parse time: %s', s)
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def instance(
|
|
104
|
+
cls,
|
|
105
|
+
obj: _datetime.time
|
|
106
|
+
| _datetime.datetime
|
|
107
|
+
| pd.Timestamp
|
|
108
|
+
| np.datetime64
|
|
109
|
+
| Self
|
|
110
|
+
| None,
|
|
111
|
+
tz: str | _zoneinfo.ZoneInfo | _datetime.tzinfo | None = None,
|
|
112
|
+
raise_err: bool = False,
|
|
113
|
+
) -> Self | None:
|
|
114
|
+
"""Create Time instance from time-like object.
|
|
115
|
+
|
|
116
|
+
Adds UTC timezone by default unless obj is already a Time instance.
|
|
117
|
+
"""
|
|
118
|
+
if pd.isna(obj):
|
|
119
|
+
if raise_err:
|
|
120
|
+
raise ValueError('Empty value')
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
if type(obj) is cls and not tz:
|
|
124
|
+
return obj
|
|
125
|
+
|
|
126
|
+
tz = tz or obj.tzinfo or UTC
|
|
127
|
+
|
|
128
|
+
return cls(obj.hour, obj.minute, obj.second, obj.microsecond, tzinfo=tz)
|
|
129
|
+
|
|
130
|
+
def in_timezone(self, tz: str | _zoneinfo.ZoneInfo | _datetime.tzinfo) -> Self:
|
|
131
|
+
"""Convert time to a different timezone.
|
|
132
|
+
"""
|
|
133
|
+
from opendate.date_ import Date
|
|
134
|
+
from opendate.datetime_ import DateTime
|
|
135
|
+
|
|
136
|
+
_dt = DateTime.combine(Date.today(), self, tzinfo=self.tzinfo or UTC)
|
|
137
|
+
return _dt.in_timezone(tz).time()
|
|
138
|
+
|
|
139
|
+
in_tz = in_timezone
|