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
opendate/date_.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import datetime as _datetime
|
|
5
|
+
import sys
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import pendulum as _pendulum
|
|
11
|
+
|
|
12
|
+
from opendate.constants import _IS_WINDOWS, DATEMATCH, LCL, UTC
|
|
13
|
+
from opendate.helpers import _rust_parse_datetime
|
|
14
|
+
from opendate.metaclass import DATE_METHODS_RETURNING_DATE, DateContextMeta
|
|
15
|
+
from opendate.mixins import DateBusinessMixin, DateExtrasMixin
|
|
16
|
+
|
|
17
|
+
if sys.version_info >= (3, 11):
|
|
18
|
+
from typing import Self
|
|
19
|
+
else:
|
|
20
|
+
from typing_extensions import Self
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from opendate.calendars import Calendar
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Date(
|
|
27
|
+
DateExtrasMixin,
|
|
28
|
+
DateBusinessMixin,
|
|
29
|
+
_pendulum.Date,
|
|
30
|
+
metaclass=DateContextMeta,
|
|
31
|
+
methods_to_wrap=DATE_METHODS_RETURNING_DATE
|
|
32
|
+
):
|
|
33
|
+
"""Date class extending pendulum.Date with business day and additional functionality.
|
|
34
|
+
|
|
35
|
+
This class inherits all pendulum.Date functionality while adding:
|
|
36
|
+
- Business day calculations with NYSE calendar integration
|
|
37
|
+
- Additional date navigation methods
|
|
38
|
+
- Enhanced parsing capabilities
|
|
39
|
+
- Custom financial date utilities
|
|
40
|
+
|
|
41
|
+
Unlike pendulum.Date, methods that create new instances return Date objects
|
|
42
|
+
that preserve business status and entity association when chained.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def to_string(self, fmt: str) -> str:
|
|
46
|
+
"""Format date to string, handling platform-specific format codes.
|
|
47
|
+
|
|
48
|
+
Automatically converts '%-' format codes to '%#' on Windows.
|
|
49
|
+
"""
|
|
50
|
+
return self.strftime(fmt.replace('%-', '%#') if _IS_WINDOWS else fmt)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def fromordinal(cls, *args, **kwargs) -> Self:
|
|
54
|
+
"""Create a Date from an ordinal.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
n: The ordinal value
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
Date instance
|
|
61
|
+
"""
|
|
62
|
+
result = _pendulum.Date.fromordinal(*args, **kwargs)
|
|
63
|
+
return cls.instance(result)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def fromtimestamp(cls, timestamp, tz=None) -> Self:
|
|
67
|
+
"""Create a Date from a timestamp.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
timestamp: Unix timestamp
|
|
71
|
+
tz: Optional timezone (defaults to UTC)
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
Date instance
|
|
75
|
+
"""
|
|
76
|
+
tz = tz or UTC
|
|
77
|
+
dt = _datetime.datetime.fromtimestamp(timestamp, tz=tz)
|
|
78
|
+
return cls(dt.year, dt.month, dt.day)
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def parse(
|
|
82
|
+
cls,
|
|
83
|
+
s: str | None,
|
|
84
|
+
calendar: str | Calendar = 'NYSE',
|
|
85
|
+
raise_err: bool = False,
|
|
86
|
+
) -> Self | None:
|
|
87
|
+
"""Convert a string to a date handling many different formats.
|
|
88
|
+
|
|
89
|
+
Supports various date formats including:
|
|
90
|
+
- Standard formats: YYYY-MM-DD, MM/DD/YYYY, MM/DD/YY, YYYYMMDD
|
|
91
|
+
- Named months: DD-MON-YYYY, MON-DD-YYYY, Month DD, YYYY
|
|
92
|
+
- Special codes: T (today), Y (yesterday), P (previous business day)
|
|
93
|
+
- Business day offsets: T-3b, P+2b (add/subtract business days)
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
s: String to parse or None
|
|
97
|
+
calendar: Calendar name or instance for business day calculations (default 'NYSE')
|
|
98
|
+
raise_err: If True, raises ValueError on parse failure instead of returning None
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
Date instance or None if parsing fails and raise_err is False
|
|
102
|
+
|
|
103
|
+
Examples
|
|
104
|
+
Standard numeric formats:
|
|
105
|
+
Date.parse('2020-01-15') → Date(2020, 1, 15)
|
|
106
|
+
Date.parse('01/15/2020') → Date(2020, 1, 15)
|
|
107
|
+
Date.parse('01/15/20') → Date(2020, 1, 15)
|
|
108
|
+
Date.parse('20200115') → Date(2020, 1, 15)
|
|
109
|
+
|
|
110
|
+
Named month formats:
|
|
111
|
+
Date.parse('15-Jan-2020') → Date(2020, 1, 15)
|
|
112
|
+
Date.parse('Jan 15, 2020') → Date(2020, 1, 15)
|
|
113
|
+
Date.parse('15JAN2020') → Date(2020, 1, 15)
|
|
114
|
+
|
|
115
|
+
Special codes:
|
|
116
|
+
Date.parse('T') → today's date
|
|
117
|
+
Date.parse('Y') → yesterday's date
|
|
118
|
+
Date.parse('P') → previous business day
|
|
119
|
+
Date.parse('M') → last day of previous month
|
|
120
|
+
|
|
121
|
+
Business day offsets:
|
|
122
|
+
Date.parse('T-3b') → 3 business days ago
|
|
123
|
+
Date.parse('P+2b') → 2 business days after previous business day
|
|
124
|
+
Date.parse('T+5') → 5 calendar days from today
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def date_for_symbol(s):
|
|
128
|
+
if s == 'N':
|
|
129
|
+
return cls.today()
|
|
130
|
+
if s == 'T':
|
|
131
|
+
return cls.today()
|
|
132
|
+
if s == 'Y':
|
|
133
|
+
return cls.today().subtract(days=1)
|
|
134
|
+
if s == 'P':
|
|
135
|
+
return cls.today().calendar(calendar).business().subtract(days=1)
|
|
136
|
+
if s == 'M':
|
|
137
|
+
return cls.today().start_of('month').subtract(days=1)
|
|
138
|
+
|
|
139
|
+
if not s:
|
|
140
|
+
if raise_err:
|
|
141
|
+
raise ValueError('Empty value')
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
if not isinstance(s, str):
|
|
145
|
+
raise TypeError(f'Invalid type for date parse: {s.__class__}')
|
|
146
|
+
|
|
147
|
+
with contextlib.suppress(ValueError):
|
|
148
|
+
if float(s) and len(s) != 8: # 20000101
|
|
149
|
+
if raise_err:
|
|
150
|
+
raise ValueError('Invalid date: %s', s)
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
# special shortcode symbolic values: T, Y-2, P-1b
|
|
154
|
+
if m := DATEMATCH.match(s):
|
|
155
|
+
d = date_for_symbol(m.groupdict().get('d'))
|
|
156
|
+
n = m.groupdict().get('n')
|
|
157
|
+
if not n:
|
|
158
|
+
return d
|
|
159
|
+
n = int(n)
|
|
160
|
+
b = m.groupdict().get('b')
|
|
161
|
+
if b:
|
|
162
|
+
if b != 'b':
|
|
163
|
+
raise ValueError(f"Expected 'b' for business day modifier, got '{b}'")
|
|
164
|
+
d = d.calendar(calendar).business().add(days=n)
|
|
165
|
+
else:
|
|
166
|
+
d = d.add(days=n)
|
|
167
|
+
return d
|
|
168
|
+
if 'today' in s.lower():
|
|
169
|
+
return cls.today()
|
|
170
|
+
if 'yester' in s.lower():
|
|
171
|
+
return cls.today().subtract(days=1)
|
|
172
|
+
|
|
173
|
+
parsed = _rust_parse_datetime(s)
|
|
174
|
+
if parsed is not None:
|
|
175
|
+
return cls.instance(parsed)
|
|
176
|
+
|
|
177
|
+
if raise_err:
|
|
178
|
+
raise ValueError('Failed to parse date: %s', s)
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def instance(
|
|
182
|
+
cls,
|
|
183
|
+
obj: _datetime.date
|
|
184
|
+
| _datetime.datetime
|
|
185
|
+
| _datetime.time
|
|
186
|
+
| pd.Timestamp
|
|
187
|
+
| np.datetime64
|
|
188
|
+
| Self
|
|
189
|
+
| None,
|
|
190
|
+
raise_err: bool = False,
|
|
191
|
+
) -> Self | None:
|
|
192
|
+
"""Create a Date instance from various date-like objects.
|
|
193
|
+
|
|
194
|
+
Converts datetime.date, datetime.datetime, pandas Timestamp,
|
|
195
|
+
numpy datetime64, and other date-like objects to Date instances.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
obj: Date-like object to convert
|
|
199
|
+
raise_err: If True, raises ValueError for None/NA values instead of returning None
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
Date instance or None if obj is None/NA and raise_err is False
|
|
203
|
+
"""
|
|
204
|
+
if pd.isna(obj):
|
|
205
|
+
if raise_err:
|
|
206
|
+
raise ValueError('Empty value')
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
if type(obj) is cls:
|
|
210
|
+
return obj
|
|
211
|
+
|
|
212
|
+
if isinstance(obj, pd.Timestamp):
|
|
213
|
+
obj = obj.to_pydatetime()
|
|
214
|
+
return cls(obj.year, obj.month, obj.day)
|
|
215
|
+
|
|
216
|
+
if isinstance(obj, np.datetime64):
|
|
217
|
+
obj = np.datetime64(obj, 'us').astype(_datetime.datetime)
|
|
218
|
+
return cls(obj.year, obj.month, obj.day)
|
|
219
|
+
|
|
220
|
+
return cls(obj.year, obj.month, obj.day)
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def today(cls) -> Self:
|
|
224
|
+
d = _datetime.datetime.now(LCL)
|
|
225
|
+
return cls(d.year, d.month, d.day)
|
|
226
|
+
|
|
227
|
+
def isoweek(self) -> int | None:
|
|
228
|
+
"""Get ISO week number (1-52/53) following ISO week-numbering standard.
|
|
229
|
+
"""
|
|
230
|
+
with contextlib.suppress(Exception):
|
|
231
|
+
return self.isocalendar()[1]
|
|
232
|
+
|
|
233
|
+
def lookback(self, unit='last') -> Self:
|
|
234
|
+
"""Get date in the past based on lookback unit.
|
|
235
|
+
|
|
236
|
+
Supported units: 'last'/'day' (1 day), 'week', 'month', 'quarter', 'year'.
|
|
237
|
+
Respects business day mode if enabled.
|
|
238
|
+
"""
|
|
239
|
+
def _lookback(years=0, months=0, weeks=0, days=0):
|
|
240
|
+
_business = self._business
|
|
241
|
+
self._business = False
|
|
242
|
+
d = self\
|
|
243
|
+
.subtract(years=years, months=months, weeks=weeks, days=days)
|
|
244
|
+
if _business:
|
|
245
|
+
return d._business_or_previous()
|
|
246
|
+
return d
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
'day': _lookback(days=1),
|
|
250
|
+
'last': _lookback(days=1),
|
|
251
|
+
'week': _lookback(weeks=1),
|
|
252
|
+
'month': _lookback(months=1),
|
|
253
|
+
'quarter': _lookback(months=3),
|
|
254
|
+
'year': _lookback(years=1),
|
|
255
|
+
}.get(unit)
|
opendate/datetime_.py
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime as _datetime
|
|
4
|
+
import sys
|
|
5
|
+
import zoneinfo as _zoneinfo
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
import pendulum as _pendulum
|
|
11
|
+
|
|
12
|
+
from opendate.constants import LCL, UTC, Timezone
|
|
13
|
+
from opendate.helpers import _rust_parse_datetime
|
|
14
|
+
from opendate.metaclass import DATETIME_METHODS_RETURNING_DATETIME, DateContextMeta
|
|
15
|
+
from opendate.mixins import DateBusinessMixin
|
|
16
|
+
|
|
17
|
+
if sys.version_info >= (3, 11):
|
|
18
|
+
from typing import Self
|
|
19
|
+
else:
|
|
20
|
+
from typing_extensions import Self
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from opendate.calendars import Calendar
|
|
24
|
+
from opendate.date_ import Date
|
|
25
|
+
from opendate.time_ import Time
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DateTime(
|
|
29
|
+
DateBusinessMixin,
|
|
30
|
+
_pendulum.DateTime,
|
|
31
|
+
metaclass=DateContextMeta,
|
|
32
|
+
methods_to_wrap=DATETIME_METHODS_RETURNING_DATETIME
|
|
33
|
+
):
|
|
34
|
+
"""DateTime class extending pendulum.DateTime with business day and additional functionality.
|
|
35
|
+
|
|
36
|
+
This class inherits all pendulum.DateTime functionality while adding:
|
|
37
|
+
- Business day calculations with NYSE calendar integration
|
|
38
|
+
- Enhanced timezone handling
|
|
39
|
+
- Extended parsing capabilities
|
|
40
|
+
- Custom utility methods for financial applications
|
|
41
|
+
|
|
42
|
+
Unlike pendulum.DateTime:
|
|
43
|
+
- today() returns start of day rather than current time
|
|
44
|
+
- Methods preserve business status and entity when chaining
|
|
45
|
+
- Has timezone handling helpers not present in pendulum
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def epoch(self) -> float:
|
|
49
|
+
"""Translate a datetime object into unix seconds since epoch
|
|
50
|
+
"""
|
|
51
|
+
return self.timestamp()
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def fromordinal(cls, *args, **kwargs) -> Self:
|
|
55
|
+
"""Create a DateTime from an ordinal.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
n: The ordinal value
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
DateTime instance
|
|
62
|
+
"""
|
|
63
|
+
result = _pendulum.DateTime.fromordinal(*args, **kwargs)
|
|
64
|
+
return cls.instance(result)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def fromtimestamp(cls, timestamp, tz=None) -> Self:
|
|
68
|
+
"""Create a DateTime from a timestamp.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
timestamp: Unix timestamp
|
|
72
|
+
tz: Optional timezone
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
DateTime instance
|
|
76
|
+
"""
|
|
77
|
+
tz = tz or UTC
|
|
78
|
+
result = _pendulum.DateTime.fromtimestamp(timestamp, tz)
|
|
79
|
+
return cls.instance(result)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def strptime(cls, time_str, fmt) -> Self:
|
|
83
|
+
"""Parse a string into a DateTime according to a format.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
time_str: String to parse
|
|
87
|
+
fmt: Format string
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
DateTime instance
|
|
91
|
+
"""
|
|
92
|
+
result = _pendulum.DateTime.strptime(time_str, fmt)
|
|
93
|
+
return cls.instance(result)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def utcfromtimestamp(cls, timestamp) -> Self:
|
|
97
|
+
"""Create a UTC DateTime from a timestamp.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
timestamp: Unix timestamp
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
DateTime instance
|
|
104
|
+
"""
|
|
105
|
+
result = _pendulum.DateTime.utcfromtimestamp(timestamp)
|
|
106
|
+
return cls.instance(result)
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def utcnow(cls) -> Self:
|
|
110
|
+
"""Create a DateTime representing current UTC time.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
DateTime instance
|
|
114
|
+
"""
|
|
115
|
+
result = _pendulum.DateTime.utcnow()
|
|
116
|
+
return cls.instance(result)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def now(cls, tz: str | _zoneinfo.ZoneInfo | _datetime.tzinfo | None = None) -> Self:
|
|
120
|
+
"""Get a DateTime instance for the current date and time.
|
|
121
|
+
"""
|
|
122
|
+
if tz is None or tz == 'local':
|
|
123
|
+
d = _datetime.datetime.now(LCL)
|
|
124
|
+
elif tz is UTC or tz == 'UTC':
|
|
125
|
+
d = _datetime.datetime.now(UTC)
|
|
126
|
+
else:
|
|
127
|
+
d = _datetime.datetime.now(UTC)
|
|
128
|
+
tz = _pendulum._safe_timezone(tz)
|
|
129
|
+
d = d.astimezone(tz)
|
|
130
|
+
return cls(d.year, d.month, d.day, d.hour, d.minute, d.second,
|
|
131
|
+
d.microsecond, tzinfo=d.tzinfo, fold=d.fold)
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def today(cls, tz: str | _zoneinfo.ZoneInfo | None = None) -> Self:
|
|
135
|
+
"""Create a DateTime object representing today at the start of day.
|
|
136
|
+
|
|
137
|
+
Unlike pendulum.today() which returns current time, this method
|
|
138
|
+
returns a DateTime object at 00:00:00 of the current day.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
tz: Optional timezone (defaults to local timezone)
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
DateTime instance representing start of current day
|
|
145
|
+
"""
|
|
146
|
+
return DateTime.now(tz).start_of('day')
|
|
147
|
+
|
|
148
|
+
def date(self) -> Date:
|
|
149
|
+
from opendate.date_ import Date
|
|
150
|
+
return Date(self.year, self.month, self.day)
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def combine(
|
|
154
|
+
cls,
|
|
155
|
+
date: _datetime.date,
|
|
156
|
+
time: _datetime.time,
|
|
157
|
+
tzinfo: _zoneinfo.ZoneInfo | None = None,
|
|
158
|
+
) -> Self:
|
|
159
|
+
"""Combine date and time (*behaves differently from Pendulum `combine`*).
|
|
160
|
+
"""
|
|
161
|
+
_tzinfo = tzinfo or time.tzinfo
|
|
162
|
+
return DateTime.instance(_datetime.datetime.combine(date, time, tzinfo=_tzinfo))
|
|
163
|
+
|
|
164
|
+
def rfc3339(self) -> str:
|
|
165
|
+
"""Return RFC 3339 formatted string (same as isoformat()).
|
|
166
|
+
"""
|
|
167
|
+
return self.isoformat()
|
|
168
|
+
|
|
169
|
+
def time(self) -> Time:
|
|
170
|
+
"""Extract time component from datetime (preserving timezone).
|
|
171
|
+
"""
|
|
172
|
+
from opendate.time_ import Time
|
|
173
|
+
return Time.instance(self)
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def parse(
|
|
177
|
+
cls, s: str | int | None,
|
|
178
|
+
calendar: str | Calendar = 'NYSE',
|
|
179
|
+
raise_err: bool = False
|
|
180
|
+
) -> Self | None:
|
|
181
|
+
"""Convert a string or timestamp to a DateTime with extended format support.
|
|
182
|
+
|
|
183
|
+
Unlike pendulum's parse, this method supports:
|
|
184
|
+
- Unix timestamps (int/float, handles milliseconds automatically)
|
|
185
|
+
- Special codes: T (today), Y (yesterday), P (previous business day)
|
|
186
|
+
- Business day offsets: T-3b, P+2b (add/subtract business days)
|
|
187
|
+
- Multiple date-time formats beyond ISO 8601
|
|
188
|
+
- Combined date and time strings with various separators
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
s: String or timestamp to parse
|
|
192
|
+
calendar: Calendar name or instance for business day calculations (default 'NYSE')
|
|
193
|
+
raise_err: If True, raises ValueError on parse failure instead of returning None
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
DateTime instance or None if parsing fails and raise_err is False
|
|
197
|
+
|
|
198
|
+
Examples
|
|
199
|
+
Unix timestamps:
|
|
200
|
+
DateTime.parse(1609459200) → DateTime(2021, 1, 1, 0, 0, 0, tzinfo=LCL)
|
|
201
|
+
DateTime.parse(1609459200000) → DateTime(2021, 1, 1, 0, 0, 0, tzinfo=LCL)
|
|
202
|
+
|
|
203
|
+
ISO 8601 format:
|
|
204
|
+
DateTime.parse('2020-01-15T14:30:00') → DateTime(2020, 1, 15, 14, 30, 0)
|
|
205
|
+
|
|
206
|
+
Date and time separated:
|
|
207
|
+
DateTime.parse('2020-01-15 14:30:00') → DateTime(2020, 1, 15, 14, 30, 0, tzinfo=LCL)
|
|
208
|
+
DateTime.parse('01/15/2020:14:30:00') → DateTime(2020, 1, 15, 14, 30, 0, tzinfo=LCL)
|
|
209
|
+
|
|
210
|
+
Date only (time defaults to 00:00:00):
|
|
211
|
+
DateTime.parse('2020-01-15') → DateTime(2020, 1, 15, 0, 0, 0)
|
|
212
|
+
DateTime.parse('01/15/2020') → DateTime(2020, 1, 15, 0, 0, 0)
|
|
213
|
+
|
|
214
|
+
Time only (uses today's date):
|
|
215
|
+
DateTime.parse('14:30:00') → DateTime(today's year, month, day, 14, 30, 0, tzinfo=LCL)
|
|
216
|
+
|
|
217
|
+
Special codes:
|
|
218
|
+
DateTime.parse('T') → today at 00:00:00
|
|
219
|
+
DateTime.parse('Y') → yesterday at 00:00:00
|
|
220
|
+
DateTime.parse('P') → previous business day at 00:00:00
|
|
221
|
+
"""
|
|
222
|
+
from opendate.date_ import Date
|
|
223
|
+
from opendate.time_ import Time
|
|
224
|
+
|
|
225
|
+
if not s:
|
|
226
|
+
if raise_err:
|
|
227
|
+
raise ValueError('Empty value')
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
if not isinstance(s, (str, int, float)):
|
|
231
|
+
raise TypeError(f'Invalid type for datetime parse: {s.__class__}')
|
|
232
|
+
|
|
233
|
+
if isinstance(s, (int, float)):
|
|
234
|
+
if len(str(int(s))) == 13:
|
|
235
|
+
s /= 1000 # Convert from milliseconds to seconds
|
|
236
|
+
dt = _datetime.datetime.fromtimestamp(s)
|
|
237
|
+
return cls(dt.year, dt.month, dt.day, dt.hour, dt.minute,
|
|
238
|
+
dt.second, dt.microsecond, tzinfo=LCL)
|
|
239
|
+
|
|
240
|
+
parsed = _rust_parse_datetime(s)
|
|
241
|
+
if parsed is not None:
|
|
242
|
+
return cls.instance(parsed)
|
|
243
|
+
|
|
244
|
+
for delim in (' ', ':'):
|
|
245
|
+
bits = s.split(delim, 1)
|
|
246
|
+
if len(bits) == 2:
|
|
247
|
+
d = Date.parse(bits[0])
|
|
248
|
+
t = Time.parse(bits[1])
|
|
249
|
+
if d is not None and t is not None:
|
|
250
|
+
return DateTime.combine(d, t, LCL)
|
|
251
|
+
|
|
252
|
+
d = Date.parse(s, calendar=calendar)
|
|
253
|
+
if d is not None:
|
|
254
|
+
return cls(d.year, d.month, d.day, 0, 0, 0)
|
|
255
|
+
|
|
256
|
+
current = Date.today()
|
|
257
|
+
t = Time.parse(s)
|
|
258
|
+
if t is not None:
|
|
259
|
+
return cls.combine(current, t, LCL)
|
|
260
|
+
|
|
261
|
+
if raise_err:
|
|
262
|
+
raise ValueError('Invalid date-time format: %s', s)
|
|
263
|
+
|
|
264
|
+
@classmethod
|
|
265
|
+
def instance(
|
|
266
|
+
cls,
|
|
267
|
+
obj: _datetime.date
|
|
268
|
+
| _datetime.time
|
|
269
|
+
| pd.Timestamp
|
|
270
|
+
| np.datetime64
|
|
271
|
+
| Self
|
|
272
|
+
| None,
|
|
273
|
+
tz: str | _zoneinfo.ZoneInfo | _datetime.tzinfo | None = None,
|
|
274
|
+
raise_err: bool = False,
|
|
275
|
+
) -> Self | None:
|
|
276
|
+
"""Create a DateTime instance from various datetime-like objects.
|
|
277
|
+
|
|
278
|
+
Provides unified interface for converting different date/time types
|
|
279
|
+
including pandas and numpy datetime objects into DateTime instances.
|
|
280
|
+
|
|
281
|
+
Unlike pendulum, this method:
|
|
282
|
+
- Handles pandas Timestamp and numpy datetime64 objects
|
|
283
|
+
- Adds timezone (UTC by default) when none is specified
|
|
284
|
+
- Has special handling for Time objects (combines with current date)
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
obj: Date, datetime, time, or compatible object to convert
|
|
288
|
+
tz: Optional timezone to apply (if None, uses obj's timezone or UTC)
|
|
289
|
+
raise_err: If True, raises ValueError for None/NA values instead of returning None
|
|
290
|
+
|
|
291
|
+
Returns
|
|
292
|
+
DateTime instance or None if obj is None/NA and raise_err is False
|
|
293
|
+
"""
|
|
294
|
+
from opendate.date_ import Date
|
|
295
|
+
from opendate.time_ import Time
|
|
296
|
+
|
|
297
|
+
if pd.isna(obj):
|
|
298
|
+
if raise_err:
|
|
299
|
+
raise ValueError('Empty value')
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
if type(obj) is cls and not tz:
|
|
303
|
+
return obj
|
|
304
|
+
|
|
305
|
+
if isinstance(obj, pd.Timestamp):
|
|
306
|
+
obj = obj.to_pydatetime()
|
|
307
|
+
tz = tz or obj.tzinfo or UTC
|
|
308
|
+
if tz is _datetime.timezone.utc:
|
|
309
|
+
tz = UTC
|
|
310
|
+
elif hasattr(tz, 'zone'):
|
|
311
|
+
tz = Timezone(tz.zone)
|
|
312
|
+
elif isinstance(tz, str):
|
|
313
|
+
tz = Timezone(tz)
|
|
314
|
+
return cls(obj.year, obj.month, obj.day, obj.hour, obj.minute,
|
|
315
|
+
obj.second, obj.microsecond, tzinfo=tz)
|
|
316
|
+
|
|
317
|
+
if isinstance(obj, np.datetime64):
|
|
318
|
+
obj = np.datetime64(obj, 'us').astype(_datetime.datetime)
|
|
319
|
+
tz = tz or UTC
|
|
320
|
+
return cls(obj.year, obj.month, obj.day, obj.hour, obj.minute,
|
|
321
|
+
obj.second, obj.microsecond, tzinfo=tz)
|
|
322
|
+
|
|
323
|
+
if type(obj) is Date:
|
|
324
|
+
return cls(obj.year, obj.month, obj.day, tzinfo=tz or UTC)
|
|
325
|
+
|
|
326
|
+
if isinstance(obj, _datetime.date) and not isinstance(obj, _datetime.datetime):
|
|
327
|
+
return cls(obj.year, obj.month, obj.day, tzinfo=tz or UTC)
|
|
328
|
+
|
|
329
|
+
tz = tz or obj.tzinfo or UTC
|
|
330
|
+
|
|
331
|
+
if type(obj) is Time:
|
|
332
|
+
return cls.combine(Date.today(), obj, tzinfo=tz)
|
|
333
|
+
|
|
334
|
+
if isinstance(obj, _datetime.time):
|
|
335
|
+
from opendate.date_ import Date
|
|
336
|
+
return cls.combine(Date.today(), obj, tzinfo=tz)
|
|
337
|
+
|
|
338
|
+
return cls(obj.year, obj.month, obj.day, obj.hour, obj.minute,
|
|
339
|
+
obj.second, obj.microsecond, tzinfo=tz)
|