dfdatetime 20260730__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.
- dfdatetime/__init__.py +26 -0
- dfdatetime/apfs_time.py +86 -0
- dfdatetime/cocoa_time.py +149 -0
- dfdatetime/decorators.py +23 -0
- dfdatetime/definitions.py +97 -0
- dfdatetime/delphi_date_time.py +166 -0
- dfdatetime/dotnet_datetime.py +157 -0
- dfdatetime/factory.py +59 -0
- dfdatetime/fake_time.py +139 -0
- dfdatetime/fat_date_time.py +337 -0
- dfdatetime/filetime.py +164 -0
- dfdatetime/golang_time.py +234 -0
- dfdatetime/hfs_time.py +150 -0
- dfdatetime/interface.py +1071 -0
- dfdatetime/java_time.py +66 -0
- dfdatetime/ole_automation_date.py +158 -0
- dfdatetime/posix_time.py +501 -0
- dfdatetime/precisions.py +425 -0
- dfdatetime/rfc2579_date_time.py +289 -0
- dfdatetime/semantic_time.py +329 -0
- dfdatetime/serializer.py +154 -0
- dfdatetime/systemtime.py +253 -0
- dfdatetime/time_elements.py +1732 -0
- dfdatetime/uuid_time.py +170 -0
- dfdatetime/webkit_time.py +159 -0
- dfdatetime-20260730.dist-info/METADATA +25 -0
- dfdatetime-20260730.dist-info/RECORD +32 -0
- dfdatetime-20260730.dist-info/WHEEL +5 -0
- dfdatetime-20260730.dist-info/licenses/ACKNOWLEDGEMENTS +16 -0
- dfdatetime-20260730.dist-info/licenses/AUTHORS +14 -0
- dfdatetime-20260730.dist-info/licenses/LICENSE +202 -0
- dfdatetime-20260730.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1732 @@
|
|
|
1
|
+
"""Time elements implementation."""
|
|
2
|
+
|
|
3
|
+
import decimal
|
|
4
|
+
|
|
5
|
+
from dfdatetime import definitions
|
|
6
|
+
from dfdatetime import factory
|
|
7
|
+
from dfdatetime import interface
|
|
8
|
+
from dfdatetime import precisions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TimeElements(interface.DateTimeValues):
|
|
12
|
+
"""Time elements.
|
|
13
|
+
|
|
14
|
+
Time elements contain separate values for year, month, day of month,
|
|
15
|
+
hours, minutes and seconds.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Maps the RFC 822, RFC 1123 and RFC 2822 definitions to their corresponding
|
|
22
|
+
# integer values.
|
|
23
|
+
_RFC_MONTH_MAPPINGS = {
|
|
24
|
+
"Jan": 1,
|
|
25
|
+
"Feb": 2,
|
|
26
|
+
"Mar": 3,
|
|
27
|
+
"Apr": 4,
|
|
28
|
+
"May": 5,
|
|
29
|
+
"Jun": 6,
|
|
30
|
+
"Jul": 7,
|
|
31
|
+
"Aug": 8,
|
|
32
|
+
"Sep": 9,
|
|
33
|
+
"Oct": 10,
|
|
34
|
+
"Nov": 11,
|
|
35
|
+
"Dec": 12,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_RFC_TIME_ZONE_MAPPINGS = {
|
|
39
|
+
"UT": 0,
|
|
40
|
+
"GMT": 0,
|
|
41
|
+
"EST": -5,
|
|
42
|
+
"EDT": -4,
|
|
43
|
+
"CST": -6,
|
|
44
|
+
"CDT": -5,
|
|
45
|
+
"MST": -7,
|
|
46
|
+
"MDT": -6,
|
|
47
|
+
"PST": -8,
|
|
48
|
+
"PDT": -7,
|
|
49
|
+
"A": -1,
|
|
50
|
+
"B": -2,
|
|
51
|
+
"C": -3,
|
|
52
|
+
"D": -4,
|
|
53
|
+
"E": -5,
|
|
54
|
+
"F": -6,
|
|
55
|
+
"G": -7,
|
|
56
|
+
"H": -8,
|
|
57
|
+
"I": -9,
|
|
58
|
+
"K": -10,
|
|
59
|
+
"L": -11,
|
|
60
|
+
"M": -12,
|
|
61
|
+
"N": 1,
|
|
62
|
+
"O": 2,
|
|
63
|
+
"P": 3,
|
|
64
|
+
"Q": 4,
|
|
65
|
+
"R": 5,
|
|
66
|
+
"S": 6,
|
|
67
|
+
"T": 7,
|
|
68
|
+
"U": 8,
|
|
69
|
+
"V": 9,
|
|
70
|
+
"W": 10,
|
|
71
|
+
"X": 11,
|
|
72
|
+
"Y": 12,
|
|
73
|
+
"Z": 0,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_RFC_WEEKDAYS = frozenset(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
is_delta=False,
|
|
81
|
+
precision=None,
|
|
82
|
+
time_elements_tuple=None,
|
|
83
|
+
time_zone_offset=None,
|
|
84
|
+
):
|
|
85
|
+
"""Initializes time elements.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
is_delta (Optional[bool]): True if the date and time value is relative to
|
|
89
|
+
another date and time value.
|
|
90
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
91
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
92
|
+
time_elements_tuple (Optional[tuple[int, int, int, int, int, int]]):
|
|
93
|
+
time elements, contains year, month, day of month, hours, minutes and
|
|
94
|
+
seconds.
|
|
95
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
96
|
+
from UTC or None if not set.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
ValueError: if the time elements tuple is invalid.
|
|
100
|
+
"""
|
|
101
|
+
super().__init__(
|
|
102
|
+
is_delta=is_delta,
|
|
103
|
+
precision=precision or definitions.PRECISION_1_SECOND,
|
|
104
|
+
time_zone_offset=time_zone_offset,
|
|
105
|
+
)
|
|
106
|
+
self._number_of_seconds = None
|
|
107
|
+
self._time_elements_tuple = time_elements_tuple
|
|
108
|
+
|
|
109
|
+
if time_elements_tuple:
|
|
110
|
+
number_of_elements = len(time_elements_tuple)
|
|
111
|
+
if number_of_elements < 6:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
(
|
|
114
|
+
f"Invalid time elements tuple at least 6 elements required,"
|
|
115
|
+
f"got: {number_of_elements:d}"
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self._number_of_seconds = self._GetNumberOfSecondsFromElements(
|
|
120
|
+
time_elements_tuple[0],
|
|
121
|
+
time_elements_tuple[1],
|
|
122
|
+
time_elements_tuple[2],
|
|
123
|
+
time_elements_tuple[3],
|
|
124
|
+
time_elements_tuple[4],
|
|
125
|
+
time_elements_tuple[5],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def _GetNormalizedTimestamp(self):
|
|
129
|
+
"""Retrieves the normalized timestamp.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
133
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
134
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
135
|
+
determined.
|
|
136
|
+
"""
|
|
137
|
+
if self._normalized_timestamp is None:
|
|
138
|
+
if self._number_of_seconds is not None:
|
|
139
|
+
self._normalized_timestamp = decimal.Decimal(self._number_of_seconds)
|
|
140
|
+
|
|
141
|
+
if self._time_zone_offset:
|
|
142
|
+
self._normalized_timestamp -= self._time_zone_offset * 60
|
|
143
|
+
|
|
144
|
+
return self._normalized_timestamp
|
|
145
|
+
|
|
146
|
+
def _CopyDateTimeFromStringISO8601(self, time_string):
|
|
147
|
+
"""Copies a date and time from an ISO 8601 date and time string.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
time_string (str): time value formatted as:
|
|
151
|
+
hh:mm:ss.######[+-]##:##
|
|
152
|
+
|
|
153
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
154
|
+
fraction can be either 3, 6 or 9 digits. The fraction of second and
|
|
155
|
+
time zone offset are optional.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
dict[str, int]: date and time values, such as year, month, day of month,
|
|
159
|
+
hours, minutes, seconds, nanoseconds, time zone offset in minutes.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
ValueError: if the time string is invalid or not supported.
|
|
163
|
+
"""
|
|
164
|
+
if not time_string:
|
|
165
|
+
raise ValueError("Invalid time string.")
|
|
166
|
+
|
|
167
|
+
time_string_length = len(time_string)
|
|
168
|
+
|
|
169
|
+
year, month, day_of_month = self._CopyDateFromString(time_string)
|
|
170
|
+
|
|
171
|
+
if time_string_length <= 10:
|
|
172
|
+
return {"year": year, "month": month, "day_of_month": day_of_month}
|
|
173
|
+
|
|
174
|
+
# If a time of day is specified the time string it should at least
|
|
175
|
+
# contain 'YYYY-MM-DDThh'.
|
|
176
|
+
if time_string[10] != "T":
|
|
177
|
+
raise ValueError("Invalid time string - missing date and time separator.")
|
|
178
|
+
|
|
179
|
+
hours, minutes, seconds, nanoseconds, time_zone_offset = (
|
|
180
|
+
self._CopyTimeFromStringISO8601(time_string[11:])
|
|
181
|
+
)
|
|
182
|
+
date_time_values = {
|
|
183
|
+
"year": year,
|
|
184
|
+
"month": month,
|
|
185
|
+
"day_of_month": day_of_month,
|
|
186
|
+
"hours": hours,
|
|
187
|
+
"minutes": minutes,
|
|
188
|
+
"seconds": seconds,
|
|
189
|
+
}
|
|
190
|
+
if nanoseconds is not None:
|
|
191
|
+
date_time_values["nanoseconds"] = nanoseconds
|
|
192
|
+
if time_zone_offset is not None:
|
|
193
|
+
date_time_values["time_zone_offset"] = time_zone_offset
|
|
194
|
+
|
|
195
|
+
return date_time_values
|
|
196
|
+
|
|
197
|
+
def _CopyDateTimeFromStringRFC822(self, time_string):
|
|
198
|
+
"""Copies a date and time from a RFC 822 date and time string.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
time_string (str): date and time value formatted as:
|
|
202
|
+
DAY, D MONTH YY hh:mm:ss ZONE
|
|
203
|
+
|
|
204
|
+
Where weekday (DAY) and seconds (ss) are optional and day of
|
|
205
|
+
month (D) can consist of 1 or 2 digits.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
dict[str, int]: date and time values, such as year, month, day of month,
|
|
209
|
+
hours, minutes, seconds, time zone offset in minutes.
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
ValueError: if the time string is invalid or not supported.
|
|
213
|
+
"""
|
|
214
|
+
if not time_string:
|
|
215
|
+
raise ValueError("Invalid time string.")
|
|
216
|
+
|
|
217
|
+
string_segments = time_string.split(" ")
|
|
218
|
+
|
|
219
|
+
if len(string_segments) not in (5, 6):
|
|
220
|
+
raise ValueError("Unsupported number of time string segments.")
|
|
221
|
+
|
|
222
|
+
weekday_string = string_segments[0]
|
|
223
|
+
if weekday_string.endswith(","):
|
|
224
|
+
weekday_string = weekday_string[:-1]
|
|
225
|
+
if weekday_string not in self._RFC_WEEKDAYS:
|
|
226
|
+
raise ValueError(f"Invalid weekday: {weekday_string:s}.")
|
|
227
|
+
|
|
228
|
+
string_segments.pop(0)
|
|
229
|
+
|
|
230
|
+
day_of_month_string = string_segments[0]
|
|
231
|
+
|
|
232
|
+
day_of_month = 0
|
|
233
|
+
if len(day_of_month_string) in (1, 2):
|
|
234
|
+
try:
|
|
235
|
+
day_of_month = int(day_of_month_string, 10)
|
|
236
|
+
except ValueError:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
if day_of_month == 0:
|
|
240
|
+
raise ValueError(f"Invalid day of month: {day_of_month_string:s}.")
|
|
241
|
+
|
|
242
|
+
month_string = string_segments[1]
|
|
243
|
+
|
|
244
|
+
month = self._RFC_MONTH_MAPPINGS.get(month_string)
|
|
245
|
+
if not month:
|
|
246
|
+
raise ValueError(f"Invalid month: {month_string:s}.")
|
|
247
|
+
|
|
248
|
+
year_string = string_segments[2]
|
|
249
|
+
|
|
250
|
+
year = None
|
|
251
|
+
if len(year_string) == 2:
|
|
252
|
+
try:
|
|
253
|
+
year = int(year_string, 10)
|
|
254
|
+
except ValueError:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
if year is None:
|
|
258
|
+
raise ValueError(f"Invalid year: {0:s}.")
|
|
259
|
+
|
|
260
|
+
year += 1900
|
|
261
|
+
|
|
262
|
+
hours, minutes, seconds, time_zone_offset = self._CopyTimeFromStringRFC(
|
|
263
|
+
string_segments[3], string_segments[4]
|
|
264
|
+
)
|
|
265
|
+
date_time_values = {
|
|
266
|
+
"year": year,
|
|
267
|
+
"month": month,
|
|
268
|
+
"day_of_month": day_of_month,
|
|
269
|
+
"hours": hours,
|
|
270
|
+
"minutes": minutes,
|
|
271
|
+
"time_zone_offset": time_zone_offset,
|
|
272
|
+
}
|
|
273
|
+
if seconds is not None:
|
|
274
|
+
date_time_values["seconds"] = seconds
|
|
275
|
+
|
|
276
|
+
return date_time_values
|
|
277
|
+
|
|
278
|
+
def _CopyDateTimeFromStringRFC1123(self, time_string):
|
|
279
|
+
"""Copies a date and time from a RFC 1123 date and time string.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
time_string (str): date and time value formatted as:
|
|
283
|
+
DAY, D MONTH YYYY hh:mm:ss ZONE
|
|
284
|
+
|
|
285
|
+
Where weekday (DAY) and seconds (ss) are optional and day of
|
|
286
|
+
month (D) can consist of 1 or 2 digits.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
dict[str, int]: date and time values, such as year, month, day of month,
|
|
290
|
+
hours, minutes, seconds, time zone offset in minutes.
|
|
291
|
+
|
|
292
|
+
Raises:
|
|
293
|
+
ValueError: if the time string is invalid or not supported.
|
|
294
|
+
"""
|
|
295
|
+
if not time_string:
|
|
296
|
+
raise ValueError("Invalid time string.")
|
|
297
|
+
|
|
298
|
+
string_segments = time_string.split(" ")
|
|
299
|
+
|
|
300
|
+
if len(string_segments) not in (5, 6):
|
|
301
|
+
raise ValueError("Unsupported number of time string segments.")
|
|
302
|
+
|
|
303
|
+
weekday_string = string_segments[0]
|
|
304
|
+
if weekday_string.endswith(","):
|
|
305
|
+
weekday_string = weekday_string[:-1]
|
|
306
|
+
if weekday_string not in self._RFC_WEEKDAYS:
|
|
307
|
+
raise ValueError(f"Invalid weekday: {weekday_string:s}.")
|
|
308
|
+
|
|
309
|
+
string_segments.pop(0)
|
|
310
|
+
|
|
311
|
+
day_of_month_string = string_segments[0]
|
|
312
|
+
|
|
313
|
+
day_of_month = 0
|
|
314
|
+
if len(day_of_month_string) in (1, 2):
|
|
315
|
+
try:
|
|
316
|
+
day_of_month = int(day_of_month_string, 10)
|
|
317
|
+
except ValueError:
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
if day_of_month == 0:
|
|
321
|
+
raise ValueError(f"Invalid day of month: {day_of_month_string:s}.")
|
|
322
|
+
|
|
323
|
+
month_string = string_segments[1]
|
|
324
|
+
|
|
325
|
+
month = self._RFC_MONTH_MAPPINGS.get(month_string)
|
|
326
|
+
if not month:
|
|
327
|
+
raise ValueError(f"Invalid month: {month_string:s}.")
|
|
328
|
+
|
|
329
|
+
year_string = string_segments[2]
|
|
330
|
+
|
|
331
|
+
year = None
|
|
332
|
+
if len(year_string) == 4:
|
|
333
|
+
try:
|
|
334
|
+
year = int(year_string, 10)
|
|
335
|
+
except ValueError:
|
|
336
|
+
pass
|
|
337
|
+
|
|
338
|
+
if year is None:
|
|
339
|
+
raise ValueError(f"Invalid year: {year_string:s}.")
|
|
340
|
+
|
|
341
|
+
hours, minutes, seconds, time_zone_offset = self._CopyTimeFromStringRFC(
|
|
342
|
+
string_segments[3], string_segments[4]
|
|
343
|
+
)
|
|
344
|
+
date_time_values = {
|
|
345
|
+
"year": year,
|
|
346
|
+
"month": month,
|
|
347
|
+
"day_of_month": day_of_month,
|
|
348
|
+
"hours": hours,
|
|
349
|
+
"minutes": minutes,
|
|
350
|
+
"time_zone_offset": time_zone_offset,
|
|
351
|
+
}
|
|
352
|
+
if seconds is not None:
|
|
353
|
+
date_time_values["seconds"] = seconds
|
|
354
|
+
|
|
355
|
+
return date_time_values
|
|
356
|
+
|
|
357
|
+
def _CopyFromDateTimeValues(self, date_time_values):
|
|
358
|
+
"""Copies time elements from date and time values.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
date_time_values (dict[str, int]): date and time values, such as year,
|
|
362
|
+
month, day of month, hours, minutes, seconds, nanoseconds, time zone
|
|
363
|
+
offset in minutes.
|
|
364
|
+
"""
|
|
365
|
+
year = date_time_values.get("year", 0)
|
|
366
|
+
month = date_time_values.get("month", 0)
|
|
367
|
+
day_of_month = date_time_values.get("day_of_month", 0)
|
|
368
|
+
hours = date_time_values.get("hours", 0)
|
|
369
|
+
minutes = date_time_values.get("minutes", 0)
|
|
370
|
+
seconds = date_time_values.get("seconds", 0)
|
|
371
|
+
time_zone_offset = date_time_values.get("time_zone_offset")
|
|
372
|
+
|
|
373
|
+
self._normalized_timestamp = None
|
|
374
|
+
self._number_of_seconds = self._GetNumberOfSecondsFromElements(
|
|
375
|
+
year, month, day_of_month, hours, minutes, seconds
|
|
376
|
+
)
|
|
377
|
+
self._time_elements_tuple = (year, month, day_of_month, hours, minutes, seconds)
|
|
378
|
+
self._time_zone_offset = time_zone_offset
|
|
379
|
+
|
|
380
|
+
def _CopyTimeFromStringISO8601(self, time_string):
|
|
381
|
+
"""Copies a time from an ISO 8601 time string.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
time_string (str): time value formatted as:
|
|
385
|
+
hh:mm:ss.######[+-]##:##
|
|
386
|
+
|
|
387
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
388
|
+
fraction can be either 3, 6 or 9 digits. The fraction of second and
|
|
389
|
+
time zone offset are optional.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
tuple[int, int, int, int, int]: hours, minutes, seconds, nanoseconds,
|
|
393
|
+
time zone offset in minutes.
|
|
394
|
+
|
|
395
|
+
Raises:
|
|
396
|
+
ValueError: if the time string is invalid or not supported.
|
|
397
|
+
"""
|
|
398
|
+
if time_string.endswith("Z"):
|
|
399
|
+
time_string = "".join([time_string[:-1], "+00:00"])
|
|
400
|
+
|
|
401
|
+
time_string_length = len(time_string)
|
|
402
|
+
|
|
403
|
+
# The time string should at least contain 'hh'.
|
|
404
|
+
if time_string_length < 2:
|
|
405
|
+
raise ValueError("Time string too short.")
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
hours = int(time_string[0:2], 10)
|
|
409
|
+
except ValueError:
|
|
410
|
+
raise ValueError("Unable to parse hours.")
|
|
411
|
+
|
|
412
|
+
if hours not in range(0, 24):
|
|
413
|
+
raise ValueError(f"Hours value: {hours:d} out of bounds.")
|
|
414
|
+
|
|
415
|
+
minutes = None
|
|
416
|
+
seconds = None
|
|
417
|
+
nanoseconds = None
|
|
418
|
+
time_zone_offset = None
|
|
419
|
+
|
|
420
|
+
time_string_index = 2
|
|
421
|
+
|
|
422
|
+
# Minutes are either specified as 'hhmm', 'hh:mm' or as a fractional part
|
|
423
|
+
# 'hh[.,]###'.
|
|
424
|
+
if time_string_index + 1 < time_string_length and time_string[
|
|
425
|
+
time_string_index
|
|
426
|
+
] not in (".", ","):
|
|
427
|
+
if time_string[time_string_index] == ":":
|
|
428
|
+
time_string_index += 1
|
|
429
|
+
|
|
430
|
+
if time_string_index + 2 > time_string_length:
|
|
431
|
+
raise ValueError("Time string too short.")
|
|
432
|
+
|
|
433
|
+
try:
|
|
434
|
+
minutes = time_string[time_string_index : time_string_index + 2]
|
|
435
|
+
minutes = int(minutes, 10)
|
|
436
|
+
except ValueError:
|
|
437
|
+
raise ValueError("Unable to parse minutes.")
|
|
438
|
+
|
|
439
|
+
time_string_index += 2
|
|
440
|
+
|
|
441
|
+
# Seconds are either specified as 'hhmmss', 'hh:mm:ss' or as a fractional
|
|
442
|
+
# part 'hh:mm[.,]###' or 'hhmm[.,]###'.
|
|
443
|
+
if time_string_index + 1 < time_string_length and time_string[
|
|
444
|
+
time_string_index
|
|
445
|
+
] not in (".", ","):
|
|
446
|
+
if time_string[time_string_index] == ":":
|
|
447
|
+
time_string_index += 1
|
|
448
|
+
|
|
449
|
+
if time_string_index + 2 > time_string_length:
|
|
450
|
+
raise ValueError("Time string too short.")
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
seconds = time_string[time_string_index : time_string_index + 2]
|
|
454
|
+
seconds = int(seconds, 10)
|
|
455
|
+
except ValueError:
|
|
456
|
+
raise ValueError("Unable to parse day of seconds.")
|
|
457
|
+
|
|
458
|
+
time_string_index += 2
|
|
459
|
+
|
|
460
|
+
time_zone_string_index = time_string_index
|
|
461
|
+
while time_zone_string_index < time_string_length:
|
|
462
|
+
if time_string[time_zone_string_index] in ("+", "-"):
|
|
463
|
+
break
|
|
464
|
+
|
|
465
|
+
time_zone_string_index += 1
|
|
466
|
+
|
|
467
|
+
# The calculations that follow rely on the time zone string index
|
|
468
|
+
# to point beyond the string in case no time zone offset was defined.
|
|
469
|
+
if time_zone_string_index == time_string_length - 1:
|
|
470
|
+
time_zone_string_index += 1
|
|
471
|
+
|
|
472
|
+
if time_string_length > time_string_index and time_string[
|
|
473
|
+
time_string_index
|
|
474
|
+
] in (".", ","):
|
|
475
|
+
time_string_index += 1
|
|
476
|
+
time_fraction_length = time_zone_string_index - time_string_index
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
time_fraction = time_string[time_string_index:time_zone_string_index]
|
|
480
|
+
time_fraction = int(time_fraction, 10)
|
|
481
|
+
time_fraction = decimal.Decimal(time_fraction) / decimal.Decimal(
|
|
482
|
+
10**time_fraction_length
|
|
483
|
+
)
|
|
484
|
+
except ValueError:
|
|
485
|
+
raise ValueError("Unable to parse time fraction.")
|
|
486
|
+
|
|
487
|
+
if minutes is None:
|
|
488
|
+
time_fraction *= 60
|
|
489
|
+
minutes = int(time_fraction)
|
|
490
|
+
time_fraction -= minutes
|
|
491
|
+
|
|
492
|
+
if seconds is None:
|
|
493
|
+
time_fraction *= 60
|
|
494
|
+
seconds = int(time_fraction)
|
|
495
|
+
time_fraction -= seconds
|
|
496
|
+
|
|
497
|
+
time_fraction *= definitions.NANOSECONDS_PER_SECOND
|
|
498
|
+
nanoseconds = int(time_fraction)
|
|
499
|
+
|
|
500
|
+
if minutes is not None and minutes not in range(0, 60):
|
|
501
|
+
raise ValueError(f"Minutes value: {minutes:d} out of bounds.")
|
|
502
|
+
|
|
503
|
+
# TODO: support a leap second?
|
|
504
|
+
if seconds is not None and seconds not in range(0, 60):
|
|
505
|
+
raise ValueError(f"Seconds value: {seconds:d} out of bounds.")
|
|
506
|
+
|
|
507
|
+
if time_zone_string_index < time_string_length:
|
|
508
|
+
if (
|
|
509
|
+
time_string_length - time_zone_string_index != 6
|
|
510
|
+
or time_string[time_zone_string_index + 3] != ":"
|
|
511
|
+
):
|
|
512
|
+
raise ValueError("Invalid time string.")
|
|
513
|
+
|
|
514
|
+
try:
|
|
515
|
+
hours_from_utc = int(
|
|
516
|
+
time_string[time_zone_string_index + 1 : time_zone_string_index + 3]
|
|
517
|
+
)
|
|
518
|
+
except ValueError:
|
|
519
|
+
raise ValueError("Unable to parse time zone hours offset.")
|
|
520
|
+
|
|
521
|
+
if hours_from_utc not in range(0, 15):
|
|
522
|
+
raise ValueError("Time zone hours offset value out of bounds.")
|
|
523
|
+
|
|
524
|
+
try:
|
|
525
|
+
minutes_from_utc = int(
|
|
526
|
+
time_string[time_zone_string_index + 4 : time_zone_string_index + 6]
|
|
527
|
+
)
|
|
528
|
+
except ValueError:
|
|
529
|
+
raise ValueError("Unable to parse time zone minutes offset.")
|
|
530
|
+
|
|
531
|
+
if minutes_from_utc not in range(0, 60):
|
|
532
|
+
raise ValueError("Time zone minutes offset value out of bounds.")
|
|
533
|
+
|
|
534
|
+
# pylint: disable=invalid-unary-operand-type
|
|
535
|
+
time_zone_offset = (hours_from_utc * 60) + minutes_from_utc
|
|
536
|
+
|
|
537
|
+
if time_string[time_zone_string_index] == "-":
|
|
538
|
+
time_zone_offset = -time_zone_offset
|
|
539
|
+
|
|
540
|
+
return hours, minutes, seconds, nanoseconds, time_zone_offset
|
|
541
|
+
|
|
542
|
+
def _CopyTimeFromStringRFC(self, time_string, time_zone_string):
|
|
543
|
+
"""Copies a time from a RFC 822, RFC 1123 or RFC 2822 time string.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
time_string (str): time value formatted as: hh:mm[:ss], where seconds (ss)
|
|
547
|
+
are optional.
|
|
548
|
+
time_zone_string (str): time zone value formatted as predefined time zone
|
|
549
|
+
indicator or [+-]HHMM
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
tuple[int, int, int, int]: hours, minutes, seconds, time zone offset in
|
|
553
|
+
minutes.
|
|
554
|
+
|
|
555
|
+
Raises:
|
|
556
|
+
ValueError: if the time string is invalid or not supported.
|
|
557
|
+
"""
|
|
558
|
+
time_string_length = len(time_string)
|
|
559
|
+
|
|
560
|
+
# The time string should at least contain 'hh:mm'.
|
|
561
|
+
if time_string_length < 5:
|
|
562
|
+
raise ValueError("Time string too short.")
|
|
563
|
+
|
|
564
|
+
if time_string_length > 8:
|
|
565
|
+
raise ValueError("Time string too long.")
|
|
566
|
+
|
|
567
|
+
if time_string[2] != ":":
|
|
568
|
+
raise ValueError("Invalid hours and minutes separator.")
|
|
569
|
+
|
|
570
|
+
try:
|
|
571
|
+
hours = int(time_string[0:2], 10)
|
|
572
|
+
except ValueError:
|
|
573
|
+
raise ValueError("Unable to parse hours.")
|
|
574
|
+
|
|
575
|
+
if hours not in range(0, 24):
|
|
576
|
+
raise ValueError(f"Hours value: {hours:d} out of bounds.")
|
|
577
|
+
|
|
578
|
+
try:
|
|
579
|
+
minutes = int(time_string[3:5], 10)
|
|
580
|
+
except ValueError:
|
|
581
|
+
raise ValueError("Unable to parse minutes.")
|
|
582
|
+
|
|
583
|
+
if minutes not in range(0, 60):
|
|
584
|
+
raise ValueError(f"Minutes value: {minutes:d} out of bounds.")
|
|
585
|
+
|
|
586
|
+
seconds = None
|
|
587
|
+
|
|
588
|
+
if time_string_length > 5:
|
|
589
|
+
if time_string_length < 8:
|
|
590
|
+
raise ValueError("Time string too short.")
|
|
591
|
+
|
|
592
|
+
if time_string[5] != ":":
|
|
593
|
+
raise ValueError("Invalid minutes and seconds separator.")
|
|
594
|
+
|
|
595
|
+
try:
|
|
596
|
+
seconds = int(time_string[6:8], 10)
|
|
597
|
+
except ValueError:
|
|
598
|
+
raise ValueError("Unable to parse seconds.")
|
|
599
|
+
|
|
600
|
+
if seconds not in range(0, 60):
|
|
601
|
+
raise ValueError(f"Seconds value: {seconds:d} out of bounds.")
|
|
602
|
+
|
|
603
|
+
time_zone_string_length = len(time_zone_string)
|
|
604
|
+
if time_zone_string_length > 5:
|
|
605
|
+
raise ValueError("Time zone string too long.")
|
|
606
|
+
|
|
607
|
+
if time_zone_string_length < 5:
|
|
608
|
+
hours_from_utc = self._RFC_TIME_ZONE_MAPPINGS.get(time_zone_string)
|
|
609
|
+
minutes_from_utc = 0
|
|
610
|
+
if hours_from_utc is None:
|
|
611
|
+
raise ValueError(f"Invalid time zone: {time_zone_string:s}.")
|
|
612
|
+
|
|
613
|
+
else:
|
|
614
|
+
if time_zone_string[0] not in ("+", "-"):
|
|
615
|
+
raise ValueError(f"Invalid time zone: {time_zone_string:s}.")
|
|
616
|
+
|
|
617
|
+
try:
|
|
618
|
+
hours_from_utc = int(time_zone_string[1:3], 10)
|
|
619
|
+
except ValueError:
|
|
620
|
+
raise ValueError("Unable to parse time zone hours offset.")
|
|
621
|
+
|
|
622
|
+
if hours_from_utc not in range(0, 15):
|
|
623
|
+
raise ValueError("Time zone hours offset value out of bounds.")
|
|
624
|
+
|
|
625
|
+
try:
|
|
626
|
+
minutes_from_utc = int(time_zone_string[3:5], 10)
|
|
627
|
+
except ValueError:
|
|
628
|
+
raise ValueError("Unable to parse time zone minutes offset.")
|
|
629
|
+
|
|
630
|
+
if minutes_from_utc not in range(0, 60):
|
|
631
|
+
raise ValueError("Time zone minutes offset value out of bounds.")
|
|
632
|
+
|
|
633
|
+
time_zone_offset = (hours_from_utc * 60) + minutes_from_utc
|
|
634
|
+
if time_zone_string[0] == "-":
|
|
635
|
+
time_zone_offset = -time_zone_offset
|
|
636
|
+
|
|
637
|
+
return hours, minutes, seconds, time_zone_offset
|
|
638
|
+
|
|
639
|
+
@property
|
|
640
|
+
def day_of_month(self):
|
|
641
|
+
"""int: day of month or None if not set."""
|
|
642
|
+
if not self._time_elements_tuple:
|
|
643
|
+
return None
|
|
644
|
+
return self._time_elements_tuple[2]
|
|
645
|
+
|
|
646
|
+
@property
|
|
647
|
+
def hours(self):
|
|
648
|
+
"""int: number of hours or None if not set."""
|
|
649
|
+
if not self._time_elements_tuple:
|
|
650
|
+
return None
|
|
651
|
+
return self._time_elements_tuple[3]
|
|
652
|
+
|
|
653
|
+
@property
|
|
654
|
+
def minutes(self):
|
|
655
|
+
"""int: number of minutes or None if not set."""
|
|
656
|
+
if not self._time_elements_tuple:
|
|
657
|
+
return None
|
|
658
|
+
return self._time_elements_tuple[4]
|
|
659
|
+
|
|
660
|
+
@property
|
|
661
|
+
def month(self):
|
|
662
|
+
"""int: month or None if not set."""
|
|
663
|
+
if not self._time_elements_tuple:
|
|
664
|
+
return None
|
|
665
|
+
return self._time_elements_tuple[1]
|
|
666
|
+
|
|
667
|
+
@property
|
|
668
|
+
def seconds(self):
|
|
669
|
+
"""int: number of seconds or None if not set."""
|
|
670
|
+
if not self._time_elements_tuple:
|
|
671
|
+
return None
|
|
672
|
+
return self._time_elements_tuple[5]
|
|
673
|
+
|
|
674
|
+
@property
|
|
675
|
+
def year(self):
|
|
676
|
+
"""int: year or None if not set."""
|
|
677
|
+
if not self._time_elements_tuple:
|
|
678
|
+
return None
|
|
679
|
+
return self._time_elements_tuple[0]
|
|
680
|
+
|
|
681
|
+
def CopyFromDatetime(self, datetime_object):
|
|
682
|
+
"""Copies time elements from a Python datetime object.
|
|
683
|
+
|
|
684
|
+
A naive datetime object is considered in local time.
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
datetime_object (datetime.datetime): Python datetime object.
|
|
688
|
+
"""
|
|
689
|
+
year, month, day_of_month, hours, minutes, seconds, _, _, _ = (
|
|
690
|
+
datetime_object.utctimetuple()
|
|
691
|
+
)
|
|
692
|
+
date_time_values = {
|
|
693
|
+
"year": year,
|
|
694
|
+
"month": month,
|
|
695
|
+
"day_of_month": day_of_month,
|
|
696
|
+
"hours": hours,
|
|
697
|
+
"minutes": minutes,
|
|
698
|
+
"seconds": seconds,
|
|
699
|
+
}
|
|
700
|
+
self._CopyFromDateTimeValues(date_time_values)
|
|
701
|
+
|
|
702
|
+
self.is_local_time = bool(datetime_object.tzinfo is None)
|
|
703
|
+
|
|
704
|
+
def CopyFromDateTimeString(self, time_string):
|
|
705
|
+
"""Copies time elements from a date and time string.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
time_string (str): date and time value formatted as:
|
|
709
|
+
YYYY-MM-DD hh:mm:ss.######[+-]##:##
|
|
710
|
+
|
|
711
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
712
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
713
|
+
fraction and time zone offset are optional. The default time zone
|
|
714
|
+
is UTC.
|
|
715
|
+
"""
|
|
716
|
+
date_time_values = self._CopyDateTimeFromString(time_string)
|
|
717
|
+
|
|
718
|
+
self._CopyFromDateTimeValues(date_time_values)
|
|
719
|
+
|
|
720
|
+
def CopyFromStringISO8601(self, time_string):
|
|
721
|
+
"""Copies time elements from an ISO 8601 date and time string.
|
|
722
|
+
|
|
723
|
+
Currently not supported:
|
|
724
|
+
* Duration notation: "P..."
|
|
725
|
+
* Week notation "2016-W33"
|
|
726
|
+
* Date with week number notation "2016-W33-3"
|
|
727
|
+
* Date without year notation "--08-17"
|
|
728
|
+
* Ordinal date notation "2016-230"
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
time_string (str): date and time value formatted as:
|
|
732
|
+
YYYY-MM-DDThh:mm:ss.######[+-]##:##
|
|
733
|
+
|
|
734
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
735
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
736
|
+
fraction and time zone offset are optional. The default time zone
|
|
737
|
+
is UTC.
|
|
738
|
+
|
|
739
|
+
Raises:
|
|
740
|
+
ValueError: if the time string is invalid or not supported.
|
|
741
|
+
"""
|
|
742
|
+
date_time_values = self._CopyDateTimeFromStringISO8601(time_string)
|
|
743
|
+
|
|
744
|
+
self._CopyFromDateTimeValues(date_time_values)
|
|
745
|
+
|
|
746
|
+
def CopyFromStringRFC822(self, time_string):
|
|
747
|
+
"""Copies time elements from a RFC 822 date and time string.
|
|
748
|
+
|
|
749
|
+
Args:
|
|
750
|
+
time_string (str): date and time value formatted as:
|
|
751
|
+
DAY, D MONTH YY hh:mm:ss ZONE
|
|
752
|
+
|
|
753
|
+
Where weekday (DAY) and seconds (ss) are optional and day of
|
|
754
|
+
month (D) can consist of 1 or 2 digits.
|
|
755
|
+
|
|
756
|
+
Raises:
|
|
757
|
+
ValueError: if the time string is invalid or not supported.
|
|
758
|
+
"""
|
|
759
|
+
date_time_values = self._CopyDateTimeFromStringRFC822(time_string)
|
|
760
|
+
|
|
761
|
+
self._CopyFromDateTimeValues(date_time_values)
|
|
762
|
+
|
|
763
|
+
def CopyFromStringRFC1123(self, time_string):
|
|
764
|
+
"""Copies time elements from a RFC 1123 date and time string.
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
time_string (str): date and time value formatted as:
|
|
768
|
+
DAY, D MONTH YYYY hh:mm:ss ZONE
|
|
769
|
+
|
|
770
|
+
Where weekday (DAY) and seconds (ss) are optional and day of
|
|
771
|
+
month (D) can consist of 1 or 2 digits.
|
|
772
|
+
|
|
773
|
+
Raises:
|
|
774
|
+
ValueError: if the time string is invalid or not supported.
|
|
775
|
+
"""
|
|
776
|
+
date_time_values = self._CopyDateTimeFromStringRFC1123(time_string)
|
|
777
|
+
|
|
778
|
+
self._CopyFromDateTimeValues(date_time_values)
|
|
779
|
+
|
|
780
|
+
def CopyFromStringTuple(self, time_elements_tuple):
|
|
781
|
+
"""Copies time elements from string-based time elements tuple.
|
|
782
|
+
|
|
783
|
+
Args:
|
|
784
|
+
time_elements_tuple (Optional[tuple[str, str, str, str, str, str]]):
|
|
785
|
+
time elements, contains year, month, day of month, hours, minutes and
|
|
786
|
+
seconds.
|
|
787
|
+
|
|
788
|
+
Raises:
|
|
789
|
+
ValueError: if the time elements tuple is invalid.
|
|
790
|
+
"""
|
|
791
|
+
number_of_elements = len(time_elements_tuple)
|
|
792
|
+
if number_of_elements < 6:
|
|
793
|
+
raise ValueError(
|
|
794
|
+
(
|
|
795
|
+
f"Invalid time elements tuple at least 6 elements required,"
|
|
796
|
+
f"got: {number_of_elements:d}"
|
|
797
|
+
)
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
year_string = time_elements_tuple[0]
|
|
801
|
+
month_string = time_elements_tuple[1]
|
|
802
|
+
day_of_month_string = time_elements_tuple[2]
|
|
803
|
+
hours_string = time_elements_tuple[3]
|
|
804
|
+
minutes_string = time_elements_tuple[4]
|
|
805
|
+
seconds_string = time_elements_tuple[5]
|
|
806
|
+
|
|
807
|
+
try:
|
|
808
|
+
year = int(year_string, 10)
|
|
809
|
+
except (TypeError, ValueError):
|
|
810
|
+
raise ValueError(f"Invalid year value: {year_string!s}")
|
|
811
|
+
|
|
812
|
+
try:
|
|
813
|
+
month = int(month_string, 10)
|
|
814
|
+
except (TypeError, ValueError):
|
|
815
|
+
raise ValueError(f"Invalid month value: {month_string!s}")
|
|
816
|
+
|
|
817
|
+
try:
|
|
818
|
+
day_of_month = int(day_of_month_string, 10)
|
|
819
|
+
except (TypeError, ValueError):
|
|
820
|
+
raise ValueError(f"Invalid day of month value: {day_of_month_string!s}")
|
|
821
|
+
|
|
822
|
+
try:
|
|
823
|
+
hours = int(hours_string, 10)
|
|
824
|
+
except (TypeError, ValueError):
|
|
825
|
+
raise ValueError(f"Invalid hours value: {hours_string!s}")
|
|
826
|
+
|
|
827
|
+
try:
|
|
828
|
+
minutes = int(minutes_string, 10)
|
|
829
|
+
except (TypeError, ValueError):
|
|
830
|
+
raise ValueError(f"Invalid minutes value: {minutes_string!s}")
|
|
831
|
+
|
|
832
|
+
try:
|
|
833
|
+
seconds = int(seconds_string, 10)
|
|
834
|
+
except (TypeError, ValueError):
|
|
835
|
+
raise ValueError(f"Invalid seconds value: {seconds_string!s}")
|
|
836
|
+
|
|
837
|
+
self._normalized_timestamp = None
|
|
838
|
+
self._number_of_seconds = self._GetNumberOfSecondsFromElements(
|
|
839
|
+
year, month, day_of_month, hours, minutes, seconds
|
|
840
|
+
)
|
|
841
|
+
self._time_elements_tuple = (year, month, day_of_month, hours, minutes, seconds)
|
|
842
|
+
|
|
843
|
+
def CopyToDateTimeString(self):
|
|
844
|
+
"""Copies the time elements to a date and time string.
|
|
845
|
+
|
|
846
|
+
Returns:
|
|
847
|
+
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss" or None
|
|
848
|
+
if time elements are missing.
|
|
849
|
+
"""
|
|
850
|
+
if self._number_of_seconds is None:
|
|
851
|
+
return None
|
|
852
|
+
|
|
853
|
+
year, month, day_of_month, hours, minutes, seconds = self._time_elements_tuple
|
|
854
|
+
|
|
855
|
+
return (
|
|
856
|
+
f"{year:04d}-{month:02d}-{day_of_month:02d} "
|
|
857
|
+
f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
def CopyToSerializableDict(self):
|
|
861
|
+
"""Copies the date time value to a serializable dictionary.
|
|
862
|
+
|
|
863
|
+
Returns:
|
|
864
|
+
dict[str, object]: serializable dictionary.
|
|
865
|
+
"""
|
|
866
|
+
serializable_dict = self._CreateSerializableDict()
|
|
867
|
+
|
|
868
|
+
serializable_dict["time_elements_tuple"] = self._time_elements_tuple
|
|
869
|
+
|
|
870
|
+
if self._is_delta:
|
|
871
|
+
serializable_dict["is_delta"] = True
|
|
872
|
+
|
|
873
|
+
return serializable_dict
|
|
874
|
+
|
|
875
|
+
def NewFromDeltaAndDate(self, year, month, day_of_month):
|
|
876
|
+
"""Creates a new time elements instance from a date time delta and a date.
|
|
877
|
+
|
|
878
|
+
Args:
|
|
879
|
+
year (int): year.
|
|
880
|
+
month (int): month, where 1 represents January and 0 if not set.
|
|
881
|
+
day_of_month (int): day of month, where 1 represents the first day and 0
|
|
882
|
+
if not set.
|
|
883
|
+
|
|
884
|
+
Returns:
|
|
885
|
+
TimeElements: time elements or None if time elements are missing.
|
|
886
|
+
|
|
887
|
+
Raises:
|
|
888
|
+
ValueError: if the instance is not a date time delta.
|
|
889
|
+
"""
|
|
890
|
+
if not self._is_delta:
|
|
891
|
+
raise ValueError("Not a date time delta.")
|
|
892
|
+
|
|
893
|
+
if self._time_elements_tuple is None:
|
|
894
|
+
return None
|
|
895
|
+
|
|
896
|
+
delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = (
|
|
897
|
+
self._time_elements_tuple
|
|
898
|
+
)
|
|
899
|
+
time_elements_tuple = (
|
|
900
|
+
year + delta_year,
|
|
901
|
+
month + delta_month,
|
|
902
|
+
day_of_month + delta_day_of_month,
|
|
903
|
+
hours,
|
|
904
|
+
minutes,
|
|
905
|
+
seconds,
|
|
906
|
+
)
|
|
907
|
+
date_time = TimeElements(
|
|
908
|
+
precision=self._precision,
|
|
909
|
+
time_elements_tuple=time_elements_tuple,
|
|
910
|
+
time_zone_offset=self._time_zone_offset,
|
|
911
|
+
)
|
|
912
|
+
date_time.is_local_time = self.is_local_time
|
|
913
|
+
|
|
914
|
+
return date_time
|
|
915
|
+
|
|
916
|
+
def NewFromDeltaAndYear(self, year):
|
|
917
|
+
"""Creates a new time elements instance from a date time delta and a year.
|
|
918
|
+
|
|
919
|
+
Args:
|
|
920
|
+
year (int): year.
|
|
921
|
+
|
|
922
|
+
Returns:
|
|
923
|
+
TimeElements: time elements or None if time elements are missing.
|
|
924
|
+
|
|
925
|
+
Raises:
|
|
926
|
+
ValueError: if the instance is not a date time delta.
|
|
927
|
+
"""
|
|
928
|
+
return self.NewFromDeltaAndDate(year, 0, 0)
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
class TimeElementsWithFractionOfSecond(TimeElements):
|
|
932
|
+
"""Time elements with a fraction of second interface.
|
|
933
|
+
|
|
934
|
+
Attributes:
|
|
935
|
+
fraction_of_second (decimal.Decimal): fraction of second, which must be a
|
|
936
|
+
value between 0.0 and 1.0.
|
|
937
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
938
|
+
"""
|
|
939
|
+
|
|
940
|
+
def __init__(
|
|
941
|
+
self,
|
|
942
|
+
fraction_of_second=None,
|
|
943
|
+
is_delta=False,
|
|
944
|
+
precision=None,
|
|
945
|
+
time_elements_tuple=None,
|
|
946
|
+
time_zone_offset=None,
|
|
947
|
+
):
|
|
948
|
+
"""Initializes time elements.
|
|
949
|
+
|
|
950
|
+
Args:
|
|
951
|
+
fraction_of_second (Optional[decimal.Decimal]): fraction of second, which
|
|
952
|
+
must be a value between 0.0 and 1.0.
|
|
953
|
+
is_delta (Optional[bool]): True if the date and time value is relative to
|
|
954
|
+
another date and time value.
|
|
955
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
956
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
957
|
+
time_elements_tuple (Optional[tuple[int, int, int, int, int, int]]):
|
|
958
|
+
time elements, contains year, month, day of month, hours, minutes and
|
|
959
|
+
seconds.
|
|
960
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
961
|
+
from UTC or None if not set.
|
|
962
|
+
|
|
963
|
+
Raises:
|
|
964
|
+
ValueError: if the time elements tuple is invalid or fraction of second
|
|
965
|
+
value is out of bounds.
|
|
966
|
+
"""
|
|
967
|
+
if fraction_of_second is not None:
|
|
968
|
+
if fraction_of_second < 0.0 or fraction_of_second >= 1.0:
|
|
969
|
+
raise ValueError(
|
|
970
|
+
f"Fraction of second value: {fraction_of_second:f} out of bounds."
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
super().__init__(
|
|
974
|
+
is_delta=is_delta,
|
|
975
|
+
precision=precision or definitions.PRECISION_1_SECOND,
|
|
976
|
+
time_elements_tuple=time_elements_tuple,
|
|
977
|
+
time_zone_offset=time_zone_offset,
|
|
978
|
+
)
|
|
979
|
+
self.fraction_of_second = fraction_of_second
|
|
980
|
+
|
|
981
|
+
def _GetNormalizedTimestamp(self):
|
|
982
|
+
"""Retrieves the normalized timestamp.
|
|
983
|
+
|
|
984
|
+
Returns:
|
|
985
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
986
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
987
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
988
|
+
determined.
|
|
989
|
+
"""
|
|
990
|
+
if self._normalized_timestamp is None:
|
|
991
|
+
if (
|
|
992
|
+
self._number_of_seconds is not None
|
|
993
|
+
and self.fraction_of_second is not None
|
|
994
|
+
):
|
|
995
|
+
self._normalized_timestamp = (
|
|
996
|
+
decimal.Decimal(self._number_of_seconds) + self.fraction_of_second
|
|
997
|
+
)
|
|
998
|
+
if self._time_zone_offset:
|
|
999
|
+
self._normalized_timestamp -= self._time_zone_offset * 60
|
|
1000
|
+
|
|
1001
|
+
return self._normalized_timestamp
|
|
1002
|
+
|
|
1003
|
+
def _CopyFromDateTimeValues(self, date_time_values):
|
|
1004
|
+
"""Copies time elements from date and time values.
|
|
1005
|
+
|
|
1006
|
+
Args:
|
|
1007
|
+
date_time_values (dict[str, int]): date and time values, such as year,
|
|
1008
|
+
month, day of month, hours, minutes, seconds, nanoseconds, time zone
|
|
1009
|
+
offset in minutes.
|
|
1010
|
+
|
|
1011
|
+
Raises:
|
|
1012
|
+
ValueError: if no helper can be created for the current precision.
|
|
1013
|
+
"""
|
|
1014
|
+
year = date_time_values.get("year", 0)
|
|
1015
|
+
month = date_time_values.get("month", 0)
|
|
1016
|
+
day_of_month = date_time_values.get("day_of_month", 0)
|
|
1017
|
+
hours = date_time_values.get("hours", 0)
|
|
1018
|
+
minutes = date_time_values.get("minutes", 0)
|
|
1019
|
+
seconds = date_time_values.get("seconds", 0)
|
|
1020
|
+
nanoseconds = date_time_values.get("nanoseconds", 0)
|
|
1021
|
+
time_zone_offset = date_time_values.get("time_zone_offset")
|
|
1022
|
+
|
|
1023
|
+
precision_helper = precisions.PrecisionHelperFactory.CreatePrecisionHelper(
|
|
1024
|
+
self._precision
|
|
1025
|
+
)
|
|
1026
|
+
fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond(
|
|
1027
|
+
nanoseconds
|
|
1028
|
+
)
|
|
1029
|
+
self._normalized_timestamp = None
|
|
1030
|
+
self._number_of_seconds = self._GetNumberOfSecondsFromElements(
|
|
1031
|
+
year, month, day_of_month, hours, minutes, seconds
|
|
1032
|
+
)
|
|
1033
|
+
self._time_elements_tuple = (year, month, day_of_month, hours, minutes, seconds)
|
|
1034
|
+
self._time_zone_offset = time_zone_offset
|
|
1035
|
+
|
|
1036
|
+
self.fraction_of_second = fraction_of_second
|
|
1037
|
+
|
|
1038
|
+
def CopyFromDatetime(self, datetime_object):
|
|
1039
|
+
"""Copies time elements from a Python datetime object.
|
|
1040
|
+
|
|
1041
|
+
A naive datetime object is considered in local time.
|
|
1042
|
+
|
|
1043
|
+
Args:
|
|
1044
|
+
datetime_object (datetime.datetime): Python datetime object.
|
|
1045
|
+
"""
|
|
1046
|
+
super().CopyFromDatetime(datetime_object)
|
|
1047
|
+
|
|
1048
|
+
precision_helper = precisions.PrecisionHelperFactory.CreatePrecisionHelper(
|
|
1049
|
+
self._precision
|
|
1050
|
+
)
|
|
1051
|
+
fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond(
|
|
1052
|
+
datetime_object.microsecond * definitions.NANOSECONDS_PER_MICROSECOND
|
|
1053
|
+
)
|
|
1054
|
+
self.fraction_of_second = fraction_of_second
|
|
1055
|
+
|
|
1056
|
+
def CopyFromStringTuple(self, time_elements_tuple):
|
|
1057
|
+
"""Copies time elements from string-based time elements tuple.
|
|
1058
|
+
|
|
1059
|
+
Args:
|
|
1060
|
+
time_elements_tuple (Optional[tuple[str, str, str, str, str, str, str]]):
|
|
1061
|
+
time elements, contains year, month, day of month, hours, minutes,
|
|
1062
|
+
seconds and fraction of seconds.
|
|
1063
|
+
|
|
1064
|
+
Raises:
|
|
1065
|
+
ValueError: if the time elements tuple is invalid.
|
|
1066
|
+
"""
|
|
1067
|
+
number_of_elements = len(time_elements_tuple)
|
|
1068
|
+
if number_of_elements < 7:
|
|
1069
|
+
raise ValueError(
|
|
1070
|
+
f"Invalid time elements tuple at least 7 elements required,"
|
|
1071
|
+
f"got: {number_of_elements:d}"
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
super().CopyFromStringTuple(time_elements_tuple)
|
|
1075
|
+
|
|
1076
|
+
fraction_of_second_string = time_elements_tuple[6]
|
|
1077
|
+
|
|
1078
|
+
try:
|
|
1079
|
+
fraction_of_second = decimal.Decimal(fraction_of_second_string)
|
|
1080
|
+
except (TypeError, ValueError):
|
|
1081
|
+
raise ValueError(
|
|
1082
|
+
f"Invalid fraction of second value: {fraction_of_second_string!s}"
|
|
1083
|
+
)
|
|
1084
|
+
|
|
1085
|
+
if fraction_of_second < 0.0 or fraction_of_second >= 1.0:
|
|
1086
|
+
raise ValueError(
|
|
1087
|
+
f"Fraction of second value: {fraction_of_second:f} out of bounds."
|
|
1088
|
+
)
|
|
1089
|
+
|
|
1090
|
+
self.fraction_of_second = fraction_of_second
|
|
1091
|
+
|
|
1092
|
+
def CopyToDateTimeString(self):
|
|
1093
|
+
"""Copies the time elements to a date and time string.
|
|
1094
|
+
|
|
1095
|
+
Returns:
|
|
1096
|
+
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss" or
|
|
1097
|
+
"YYYY-MM-DD hh:mm:ss.######" or None if time elements are missing.
|
|
1098
|
+
|
|
1099
|
+
Raises:
|
|
1100
|
+
ValueError: if the precision value is unsupported.
|
|
1101
|
+
"""
|
|
1102
|
+
if self._number_of_seconds is None or self.fraction_of_second is None:
|
|
1103
|
+
return None
|
|
1104
|
+
|
|
1105
|
+
precision_helper = precisions.PrecisionHelperFactory.CreatePrecisionHelper(
|
|
1106
|
+
self._precision
|
|
1107
|
+
)
|
|
1108
|
+
return precision_helper.CopyToDateTimeString(
|
|
1109
|
+
self._time_elements_tuple, self.fraction_of_second
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
def NewFromDeltaAndDate(self, year, month, day_of_month):
|
|
1113
|
+
"""Creates a new time elements instance from a date time delta and a date.
|
|
1114
|
+
|
|
1115
|
+
Args:
|
|
1116
|
+
year (int): year.
|
|
1117
|
+
month (int): month, where 1 represents January and 0 if not set.
|
|
1118
|
+
day_of_month (int): day of month, where 1 represents the first day and 0
|
|
1119
|
+
if not set.
|
|
1120
|
+
|
|
1121
|
+
Returns:
|
|
1122
|
+
TimeElementsWithFractionOfSecond: time elements or None if time elements
|
|
1123
|
+
are missing.
|
|
1124
|
+
|
|
1125
|
+
Raises:
|
|
1126
|
+
ValueError: if the instance is not a date time delta.
|
|
1127
|
+
"""
|
|
1128
|
+
if not self._is_delta:
|
|
1129
|
+
raise ValueError("Not a date time delta.")
|
|
1130
|
+
|
|
1131
|
+
if self._time_elements_tuple is None:
|
|
1132
|
+
return None
|
|
1133
|
+
|
|
1134
|
+
delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = (
|
|
1135
|
+
self._time_elements_tuple
|
|
1136
|
+
)
|
|
1137
|
+
time_elements_tuple = (
|
|
1138
|
+
year + delta_year,
|
|
1139
|
+
month + delta_month,
|
|
1140
|
+
day_of_month + delta_day_of_month,
|
|
1141
|
+
hours,
|
|
1142
|
+
minutes,
|
|
1143
|
+
seconds,
|
|
1144
|
+
)
|
|
1145
|
+
return TimeElementsWithFractionOfSecond(
|
|
1146
|
+
fraction_of_second=self.fraction_of_second,
|
|
1147
|
+
precision=self._precision,
|
|
1148
|
+
time_elements_tuple=time_elements_tuple,
|
|
1149
|
+
time_zone_offset=self._time_zone_offset,
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
def NewFromDeltaAndYear(self, year):
|
|
1153
|
+
"""Creates a new time elements instance from a date time delta and a year.
|
|
1154
|
+
|
|
1155
|
+
Args:
|
|
1156
|
+
year (int): year.
|
|
1157
|
+
|
|
1158
|
+
Returns:
|
|
1159
|
+
TimeElementsWithFractionOfSecond: time elements or None if time elements
|
|
1160
|
+
are missing.
|
|
1161
|
+
|
|
1162
|
+
Raises:
|
|
1163
|
+
ValueError: if the instance is not a date time delta.
|
|
1164
|
+
"""
|
|
1165
|
+
return self.NewFromDeltaAndDate(year, 0, 0)
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
class TimeElementsInMilliseconds(TimeElementsWithFractionOfSecond):
|
|
1169
|
+
"""Time elements in milliseconds.
|
|
1170
|
+
|
|
1171
|
+
Attributes:
|
|
1172
|
+
fraction_of_second (decimal.Decimal): fraction of second, which must be a
|
|
1173
|
+
value between 0.0 and 1.0.
|
|
1174
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
1175
|
+
precision (str): precision of the date of the date and time value, that
|
|
1176
|
+
represents 1 millisecond (PRECISION_1_MILLISECOND).
|
|
1177
|
+
"""
|
|
1178
|
+
|
|
1179
|
+
def __init__(
|
|
1180
|
+
self,
|
|
1181
|
+
is_delta=False,
|
|
1182
|
+
precision=None,
|
|
1183
|
+
time_elements_tuple=None,
|
|
1184
|
+
time_zone_offset=None,
|
|
1185
|
+
):
|
|
1186
|
+
"""Initializes time elements.
|
|
1187
|
+
|
|
1188
|
+
Args:
|
|
1189
|
+
is_delta (Optional[bool]): True if the date and time value is relative to
|
|
1190
|
+
another date and time value.
|
|
1191
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
1192
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
1193
|
+
time_elements_tuple (Optional[tuple[int, int, int, int, int, int, int]]):
|
|
1194
|
+
time elements, contains year, month, day of month, hours, minutes,
|
|
1195
|
+
seconds and milliseconds.
|
|
1196
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
1197
|
+
from UTC or None if not set.
|
|
1198
|
+
|
|
1199
|
+
Raises:
|
|
1200
|
+
ValueError: if the time elements tuple is invalid.
|
|
1201
|
+
"""
|
|
1202
|
+
fraction_of_second = None
|
|
1203
|
+
if time_elements_tuple:
|
|
1204
|
+
number_of_elements = len(time_elements_tuple)
|
|
1205
|
+
if number_of_elements < 7:
|
|
1206
|
+
raise ValueError(
|
|
1207
|
+
(
|
|
1208
|
+
f"Invalid time elements tuple at least 7 elements required,"
|
|
1209
|
+
f"got: {number_of_elements:d}"
|
|
1210
|
+
)
|
|
1211
|
+
)
|
|
1212
|
+
|
|
1213
|
+
milliseconds = time_elements_tuple[6]
|
|
1214
|
+
time_elements_tuple = time_elements_tuple[:6]
|
|
1215
|
+
|
|
1216
|
+
if milliseconds < 0 or milliseconds >= definitions.MILLISECONDS_PER_SECOND:
|
|
1217
|
+
raise ValueError("Invalid number of milliseconds.")
|
|
1218
|
+
|
|
1219
|
+
fraction_of_second = (
|
|
1220
|
+
decimal.Decimal(milliseconds) / definitions.MILLISECONDS_PER_SECOND
|
|
1221
|
+
)
|
|
1222
|
+
|
|
1223
|
+
super().__init__(
|
|
1224
|
+
fraction_of_second=fraction_of_second,
|
|
1225
|
+
is_delta=is_delta,
|
|
1226
|
+
precision=precision or definitions.PRECISION_1_MILLISECOND,
|
|
1227
|
+
time_elements_tuple=time_elements_tuple,
|
|
1228
|
+
time_zone_offset=time_zone_offset,
|
|
1229
|
+
)
|
|
1230
|
+
|
|
1231
|
+
@property
|
|
1232
|
+
def milliseconds(self):
|
|
1233
|
+
"""int: number of milliseconds."""
|
|
1234
|
+
return int(self.fraction_of_second * definitions.MILLISECONDS_PER_SECOND)
|
|
1235
|
+
|
|
1236
|
+
def CopyFromStringTuple(self, time_elements_tuple):
|
|
1237
|
+
"""Copies time elements from string-based time elements tuple.
|
|
1238
|
+
|
|
1239
|
+
Args:
|
|
1240
|
+
time_elements_tuple (Optional[tuple[str, str, str, str, str, str, str]]):
|
|
1241
|
+
time elements, contains year, month, day of month, hours, minutes,
|
|
1242
|
+
seconds and milliseconds.
|
|
1243
|
+
|
|
1244
|
+
Raises:
|
|
1245
|
+
ValueError: if the time elements tuple is invalid.
|
|
1246
|
+
"""
|
|
1247
|
+
number_of_elements = len(time_elements_tuple)
|
|
1248
|
+
if len(time_elements_tuple) < 7:
|
|
1249
|
+
raise ValueError(
|
|
1250
|
+
(
|
|
1251
|
+
f"Invalid time elements tuple at least 7 elements required,"
|
|
1252
|
+
f"got: {number_of_elements:d}"
|
|
1253
|
+
)
|
|
1254
|
+
)
|
|
1255
|
+
|
|
1256
|
+
year, month, day_of_month, hours, minutes, seconds, milliseconds = (
|
|
1257
|
+
time_elements_tuple
|
|
1258
|
+
)
|
|
1259
|
+
try:
|
|
1260
|
+
milliseconds = int(milliseconds, 10)
|
|
1261
|
+
except (TypeError, ValueError):
|
|
1262
|
+
raise ValueError(f"Invalid millisecond value: {milliseconds!s}")
|
|
1263
|
+
|
|
1264
|
+
if milliseconds < 0 or milliseconds >= definitions.MILLISECONDS_PER_SECOND:
|
|
1265
|
+
raise ValueError("Invalid number of milliseconds.")
|
|
1266
|
+
|
|
1267
|
+
fraction_of_second = (
|
|
1268
|
+
decimal.Decimal(milliseconds) / definitions.MILLISECONDS_PER_SECOND
|
|
1269
|
+
)
|
|
1270
|
+
time_elements_tuple = (
|
|
1271
|
+
year,
|
|
1272
|
+
month,
|
|
1273
|
+
day_of_month,
|
|
1274
|
+
hours,
|
|
1275
|
+
minutes,
|
|
1276
|
+
seconds,
|
|
1277
|
+
str(fraction_of_second),
|
|
1278
|
+
)
|
|
1279
|
+
super().CopyFromStringTuple(time_elements_tuple)
|
|
1280
|
+
|
|
1281
|
+
def CopyToSerializableDict(self):
|
|
1282
|
+
"""Copies the date time value to a serializable dictionary.
|
|
1283
|
+
|
|
1284
|
+
Returns:
|
|
1285
|
+
dict[str, object]: serializable dictionary.
|
|
1286
|
+
"""
|
|
1287
|
+
serializable_dict = self._CreateSerializableDict()
|
|
1288
|
+
|
|
1289
|
+
serializable_dict["time_elements_tuple"] = (
|
|
1290
|
+
*self._time_elements_tuple,
|
|
1291
|
+
self.milliseconds,
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
if self._is_delta:
|
|
1295
|
+
serializable_dict["is_delta"] = True
|
|
1296
|
+
|
|
1297
|
+
return serializable_dict
|
|
1298
|
+
|
|
1299
|
+
def NewFromDeltaAndDate(self, year, month, day_of_month):
|
|
1300
|
+
"""Creates a new time elements instance from a date time delta and a date.
|
|
1301
|
+
|
|
1302
|
+
Args:
|
|
1303
|
+
year (int): year.
|
|
1304
|
+
month (int): month, where 1 represents January and 0 if not set.
|
|
1305
|
+
day_of_month (int): day of month, where 1 represents the first day and 0
|
|
1306
|
+
if not set.
|
|
1307
|
+
|
|
1308
|
+
Returns:
|
|
1309
|
+
TimeElementsInMilliseconds: time elements or None if time elements are
|
|
1310
|
+
missing.
|
|
1311
|
+
|
|
1312
|
+
Raises:
|
|
1313
|
+
ValueError: if the instance is not a date time delta.
|
|
1314
|
+
"""
|
|
1315
|
+
if not self._is_delta:
|
|
1316
|
+
raise ValueError("Not a date time delta.")
|
|
1317
|
+
|
|
1318
|
+
if self._time_elements_tuple is None:
|
|
1319
|
+
return None
|
|
1320
|
+
|
|
1321
|
+
delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = (
|
|
1322
|
+
self._time_elements_tuple
|
|
1323
|
+
)
|
|
1324
|
+
time_elements_tuple = (
|
|
1325
|
+
year + delta_year,
|
|
1326
|
+
month + delta_month,
|
|
1327
|
+
day_of_month + delta_day_of_month,
|
|
1328
|
+
hours,
|
|
1329
|
+
minutes,
|
|
1330
|
+
seconds,
|
|
1331
|
+
self.milliseconds,
|
|
1332
|
+
)
|
|
1333
|
+
return TimeElementsInMilliseconds(
|
|
1334
|
+
precision=self._precision,
|
|
1335
|
+
time_elements_tuple=time_elements_tuple,
|
|
1336
|
+
time_zone_offset=self._time_zone_offset,
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
def NewFromDeltaAndYear(self, year):
|
|
1340
|
+
"""Creates a new time elements instance from a date time delta and a year.
|
|
1341
|
+
|
|
1342
|
+
Args:
|
|
1343
|
+
year (int): year.
|
|
1344
|
+
|
|
1345
|
+
Returns:
|
|
1346
|
+
TimeElementsInMilliseconds: time elements or None if time elements are
|
|
1347
|
+
missing.
|
|
1348
|
+
|
|
1349
|
+
Raises:
|
|
1350
|
+
ValueError: if the instance is not a date time delta.
|
|
1351
|
+
"""
|
|
1352
|
+
return self.NewFromDeltaAndDate(year, 0, 0)
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
class TimeElementsInMicroseconds(TimeElementsWithFractionOfSecond):
|
|
1356
|
+
"""Time elements in microseconds.
|
|
1357
|
+
|
|
1358
|
+
Attributes:
|
|
1359
|
+
fraction_of_second (decimal.Decimal): fraction of second, which must be a
|
|
1360
|
+
value between 0.0 and 1.0.
|
|
1361
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
1362
|
+
precision (str): precision of the date of the date and time value, that
|
|
1363
|
+
represents 1 microsecond (PRECISION_1_MICROSECOND).
|
|
1364
|
+
"""
|
|
1365
|
+
|
|
1366
|
+
def __init__(
|
|
1367
|
+
self,
|
|
1368
|
+
is_delta=False,
|
|
1369
|
+
precision=None,
|
|
1370
|
+
time_elements_tuple=None,
|
|
1371
|
+
time_zone_offset=None,
|
|
1372
|
+
):
|
|
1373
|
+
"""Initializes time elements.
|
|
1374
|
+
|
|
1375
|
+
Args:
|
|
1376
|
+
is_delta (Optional[bool]): True if the date and time value is relative to
|
|
1377
|
+
another date and time value.
|
|
1378
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
1379
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
1380
|
+
time_elements_tuple (Optional[tuple[int, int, int, int, int, int, int]]):
|
|
1381
|
+
time elements, contains year, month, day of month, hours, minutes,
|
|
1382
|
+
seconds and microseconds.
|
|
1383
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
1384
|
+
from UTC or None if not set.
|
|
1385
|
+
|
|
1386
|
+
Raises:
|
|
1387
|
+
ValueError: if the time elements tuple is invalid.
|
|
1388
|
+
"""
|
|
1389
|
+
fraction_of_second = None
|
|
1390
|
+
if time_elements_tuple:
|
|
1391
|
+
number_of_elements = len(time_elements_tuple)
|
|
1392
|
+
if number_of_elements < 7:
|
|
1393
|
+
raise ValueError(
|
|
1394
|
+
(
|
|
1395
|
+
f"Invalid time elements tuple at least 7 elements required,"
|
|
1396
|
+
f"got: {number_of_elements:d}"
|
|
1397
|
+
)
|
|
1398
|
+
)
|
|
1399
|
+
|
|
1400
|
+
microseconds = time_elements_tuple[6]
|
|
1401
|
+
time_elements_tuple = time_elements_tuple[:6]
|
|
1402
|
+
|
|
1403
|
+
if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND:
|
|
1404
|
+
raise ValueError("Invalid number of microseconds.")
|
|
1405
|
+
|
|
1406
|
+
fraction_of_second = (
|
|
1407
|
+
decimal.Decimal(microseconds) / definitions.MICROSECONDS_PER_SECOND
|
|
1408
|
+
)
|
|
1409
|
+
|
|
1410
|
+
super().__init__(
|
|
1411
|
+
fraction_of_second=fraction_of_second,
|
|
1412
|
+
is_delta=is_delta,
|
|
1413
|
+
precision=precision or definitions.PRECISION_1_MICROSECOND,
|
|
1414
|
+
time_elements_tuple=time_elements_tuple,
|
|
1415
|
+
time_zone_offset=time_zone_offset,
|
|
1416
|
+
)
|
|
1417
|
+
|
|
1418
|
+
@property
|
|
1419
|
+
def microseconds(self):
|
|
1420
|
+
"""int: number of microseconds."""
|
|
1421
|
+
return int(self.fraction_of_second * definitions.MICROSECONDS_PER_SECOND)
|
|
1422
|
+
|
|
1423
|
+
def CopyFromStringTuple(self, time_elements_tuple):
|
|
1424
|
+
"""Copies time elements from string-based time elements tuple.
|
|
1425
|
+
|
|
1426
|
+
Args:
|
|
1427
|
+
time_elements_tuple (Optional[tuple[str, str, str, str, str, str, str]]):
|
|
1428
|
+
time elements, contains year, month, day of month, hours, minutes,
|
|
1429
|
+
seconds and microseconds.
|
|
1430
|
+
|
|
1431
|
+
Raises:
|
|
1432
|
+
ValueError: if the time elements tuple is invalid.
|
|
1433
|
+
"""
|
|
1434
|
+
number_of_elements = len(time_elements_tuple)
|
|
1435
|
+
if len(time_elements_tuple) < 7:
|
|
1436
|
+
raise ValueError(
|
|
1437
|
+
(
|
|
1438
|
+
f"Invalid time elements tuple at least 7 elements required,"
|
|
1439
|
+
f"got: {number_of_elements:d}"
|
|
1440
|
+
)
|
|
1441
|
+
)
|
|
1442
|
+
|
|
1443
|
+
year, month, day_of_month, hours, minutes, seconds, microseconds = (
|
|
1444
|
+
time_elements_tuple
|
|
1445
|
+
)
|
|
1446
|
+
try:
|
|
1447
|
+
microseconds = int(microseconds, 10)
|
|
1448
|
+
except (TypeError, ValueError):
|
|
1449
|
+
raise ValueError(f"Invalid microsecond value: {microseconds!s}")
|
|
1450
|
+
|
|
1451
|
+
if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND:
|
|
1452
|
+
raise ValueError("Invalid number of microseconds.")
|
|
1453
|
+
|
|
1454
|
+
fraction_of_second = (
|
|
1455
|
+
decimal.Decimal(microseconds) / definitions.MICROSECONDS_PER_SECOND
|
|
1456
|
+
)
|
|
1457
|
+
time_elements_tuple = (
|
|
1458
|
+
year,
|
|
1459
|
+
month,
|
|
1460
|
+
day_of_month,
|
|
1461
|
+
hours,
|
|
1462
|
+
minutes,
|
|
1463
|
+
seconds,
|
|
1464
|
+
str(fraction_of_second),
|
|
1465
|
+
)
|
|
1466
|
+
super().CopyFromStringTuple(time_elements_tuple)
|
|
1467
|
+
|
|
1468
|
+
def CopyToSerializableDict(self):
|
|
1469
|
+
"""Copies the date time value to a serializable dictionary.
|
|
1470
|
+
|
|
1471
|
+
Returns:
|
|
1472
|
+
dict[str, object]: serializable dictionary.
|
|
1473
|
+
"""
|
|
1474
|
+
serializable_dict = self._CreateSerializableDict()
|
|
1475
|
+
|
|
1476
|
+
serializable_dict["time_elements_tuple"] = (
|
|
1477
|
+
*self._time_elements_tuple,
|
|
1478
|
+
self.microseconds,
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
if self._is_delta:
|
|
1482
|
+
serializable_dict["is_delta"] = True
|
|
1483
|
+
|
|
1484
|
+
return serializable_dict
|
|
1485
|
+
|
|
1486
|
+
def NewFromDeltaAndDate(self, year, month, day_of_month):
|
|
1487
|
+
"""Creates a new time elements instance from a date time delta and a year.
|
|
1488
|
+
|
|
1489
|
+
Args:
|
|
1490
|
+
year (int): year.
|
|
1491
|
+
month (int): month, where 1 represents January and 0 if not set.
|
|
1492
|
+
day_of_month (int): day of month, where 1 represents the first day and 0
|
|
1493
|
+
if not set.
|
|
1494
|
+
|
|
1495
|
+
Returns:
|
|
1496
|
+
TimeElementsInMicroseconds: time elements or None if time elements are
|
|
1497
|
+
missing.
|
|
1498
|
+
|
|
1499
|
+
Raises:
|
|
1500
|
+
ValueError: if the instance is not a date time delta.
|
|
1501
|
+
"""
|
|
1502
|
+
if not self._is_delta:
|
|
1503
|
+
raise ValueError("Not a date time delta.")
|
|
1504
|
+
|
|
1505
|
+
if self._time_elements_tuple is None:
|
|
1506
|
+
return None
|
|
1507
|
+
|
|
1508
|
+
delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = (
|
|
1509
|
+
self._time_elements_tuple
|
|
1510
|
+
)
|
|
1511
|
+
time_elements_tuple = (
|
|
1512
|
+
year + delta_year,
|
|
1513
|
+
month + delta_month,
|
|
1514
|
+
day_of_month + delta_day_of_month,
|
|
1515
|
+
hours,
|
|
1516
|
+
minutes,
|
|
1517
|
+
seconds,
|
|
1518
|
+
self.microseconds,
|
|
1519
|
+
)
|
|
1520
|
+
return TimeElementsInMicroseconds(
|
|
1521
|
+
precision=self._precision,
|
|
1522
|
+
time_elements_tuple=time_elements_tuple,
|
|
1523
|
+
time_zone_offset=self._time_zone_offset,
|
|
1524
|
+
)
|
|
1525
|
+
|
|
1526
|
+
def NewFromDeltaAndYear(self, year):
|
|
1527
|
+
"""Creates a new time elements instance from a date time delta and a year.
|
|
1528
|
+
|
|
1529
|
+
Args:
|
|
1530
|
+
year (int): year.
|
|
1531
|
+
|
|
1532
|
+
Returns:
|
|
1533
|
+
TimeElementsInMicroseconds: time elements or None if time elements are
|
|
1534
|
+
missing.
|
|
1535
|
+
|
|
1536
|
+
Raises:
|
|
1537
|
+
ValueError: if the instance is not a date time delta.
|
|
1538
|
+
"""
|
|
1539
|
+
return self.NewFromDeltaAndDate(year, 0, 0)
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
class TimeElementsInNanoseconds(TimeElementsWithFractionOfSecond):
|
|
1543
|
+
"""Time elements in nanoseconds.
|
|
1544
|
+
|
|
1545
|
+
Attributes:
|
|
1546
|
+
fraction_of_second (decimal.Decimal): fraction of second, which must be a
|
|
1547
|
+
value between 0.0 and 1.0.
|
|
1548
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
1549
|
+
precision (str): precision of the date of the date and time value, that
|
|
1550
|
+
represents 1 nanosecond (PRECISION_1_NANOSECOND).
|
|
1551
|
+
"""
|
|
1552
|
+
|
|
1553
|
+
def __init__(
|
|
1554
|
+
self,
|
|
1555
|
+
is_delta=False,
|
|
1556
|
+
precision=None,
|
|
1557
|
+
time_elements_tuple=None,
|
|
1558
|
+
time_zone_offset=None,
|
|
1559
|
+
):
|
|
1560
|
+
"""Initializes time elements.
|
|
1561
|
+
|
|
1562
|
+
Args:
|
|
1563
|
+
is_delta (Optional[bool]): True if the date and time value is relative to
|
|
1564
|
+
another date and time value.
|
|
1565
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
1566
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
1567
|
+
time_elements_tuple (Optional[tuple[int, int, int, int, int, int, int]]):
|
|
1568
|
+
time elements, contains year, month, day of month, hours, minutes,
|
|
1569
|
+
seconds and nanoseconds.
|
|
1570
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
1571
|
+
from UTC or None if not set.
|
|
1572
|
+
|
|
1573
|
+
Raises:
|
|
1574
|
+
ValueError: if the time elements tuple is invalid.
|
|
1575
|
+
"""
|
|
1576
|
+
fraction_of_second = None
|
|
1577
|
+
if time_elements_tuple:
|
|
1578
|
+
number_of_elements = len(time_elements_tuple)
|
|
1579
|
+
if number_of_elements < 7:
|
|
1580
|
+
raise ValueError(
|
|
1581
|
+
(
|
|
1582
|
+
f"Invalid time elements tuple at least 7 elements required,"
|
|
1583
|
+
f"got: {number_of_elements:d}"
|
|
1584
|
+
)
|
|
1585
|
+
)
|
|
1586
|
+
|
|
1587
|
+
nanoseconds = time_elements_tuple[6]
|
|
1588
|
+
time_elements_tuple = time_elements_tuple[:6]
|
|
1589
|
+
|
|
1590
|
+
if nanoseconds < 0 or nanoseconds >= definitions.NANOSECONDS_PER_SECOND:
|
|
1591
|
+
raise ValueError("Invalid number of nanoseconds.")
|
|
1592
|
+
|
|
1593
|
+
fraction_of_second = (
|
|
1594
|
+
decimal.Decimal(nanoseconds) / definitions.NANOSECONDS_PER_SECOND
|
|
1595
|
+
)
|
|
1596
|
+
|
|
1597
|
+
super().__init__(
|
|
1598
|
+
fraction_of_second=fraction_of_second,
|
|
1599
|
+
is_delta=is_delta,
|
|
1600
|
+
precision=precision or definitions.PRECISION_1_NANOSECOND,
|
|
1601
|
+
time_elements_tuple=time_elements_tuple,
|
|
1602
|
+
time_zone_offset=time_zone_offset,
|
|
1603
|
+
)
|
|
1604
|
+
|
|
1605
|
+
@property
|
|
1606
|
+
def nanoseconds(self):
|
|
1607
|
+
"""int: number of nanoseconds."""
|
|
1608
|
+
return int(self.fraction_of_second * definitions.NANOSECONDS_PER_SECOND)
|
|
1609
|
+
|
|
1610
|
+
def CopyFromStringTuple(self, time_elements_tuple):
|
|
1611
|
+
"""Copies time elements from string-based time elements tuple.
|
|
1612
|
+
|
|
1613
|
+
Args:
|
|
1614
|
+
time_elements_tuple (Optional[tuple[str, str, str, str, str, str, str]]):
|
|
1615
|
+
time elements, contains year, month, day of month, hours, minutes,
|
|
1616
|
+
seconds and nanoseconds.
|
|
1617
|
+
|
|
1618
|
+
Raises:
|
|
1619
|
+
ValueError: if the time elements tuple is invalid.
|
|
1620
|
+
"""
|
|
1621
|
+
number_of_elements = len(time_elements_tuple)
|
|
1622
|
+
if len(time_elements_tuple) < 7:
|
|
1623
|
+
raise ValueError(
|
|
1624
|
+
(
|
|
1625
|
+
f"Invalid time elements tuple at least 7 elements required,"
|
|
1626
|
+
f"got: {number_of_elements:d}"
|
|
1627
|
+
)
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
year, month, day_of_month, hours, minutes, seconds, nanoseconds = (
|
|
1631
|
+
time_elements_tuple
|
|
1632
|
+
)
|
|
1633
|
+
try:
|
|
1634
|
+
nanoseconds = int(nanoseconds, 10)
|
|
1635
|
+
except (TypeError, ValueError):
|
|
1636
|
+
raise ValueError(f"Invalid nanosecond value: {nanoseconds!s}")
|
|
1637
|
+
|
|
1638
|
+
if nanoseconds < 0 or nanoseconds >= definitions.NANOSECONDS_PER_SECOND:
|
|
1639
|
+
raise ValueError("Invalid number of nanoseconds.")
|
|
1640
|
+
|
|
1641
|
+
fraction_of_second = (
|
|
1642
|
+
decimal.Decimal(nanoseconds) / definitions.NANOSECONDS_PER_SECOND
|
|
1643
|
+
)
|
|
1644
|
+
time_elements_tuple = (
|
|
1645
|
+
year,
|
|
1646
|
+
month,
|
|
1647
|
+
day_of_month,
|
|
1648
|
+
hours,
|
|
1649
|
+
minutes,
|
|
1650
|
+
seconds,
|
|
1651
|
+
str(fraction_of_second),
|
|
1652
|
+
)
|
|
1653
|
+
super().CopyFromStringTuple(time_elements_tuple)
|
|
1654
|
+
|
|
1655
|
+
def CopyToSerializableDict(self):
|
|
1656
|
+
"""Copies the date time value to a serializable dictionary.
|
|
1657
|
+
|
|
1658
|
+
Returns:
|
|
1659
|
+
dict[str, object]: serializable dictionary.
|
|
1660
|
+
"""
|
|
1661
|
+
serializable_dict = self._CreateSerializableDict()
|
|
1662
|
+
|
|
1663
|
+
serializable_dict["time_elements_tuple"] = (
|
|
1664
|
+
*self._time_elements_tuple,
|
|
1665
|
+
self.nanoseconds,
|
|
1666
|
+
)
|
|
1667
|
+
|
|
1668
|
+
if self._is_delta:
|
|
1669
|
+
serializable_dict["is_delta"] = True
|
|
1670
|
+
|
|
1671
|
+
return serializable_dict
|
|
1672
|
+
|
|
1673
|
+
def NewFromDeltaAndDate(self, year, month, day_of_month):
|
|
1674
|
+
"""Creates a new time elements instance from a date time delta and a year.
|
|
1675
|
+
|
|
1676
|
+
Args:
|
|
1677
|
+
year (int): year.
|
|
1678
|
+
month (int): month, where 1 represents January and 0 if not set.
|
|
1679
|
+
day_of_month (int): day of month, where 1 represents the first day and 0
|
|
1680
|
+
if not set.
|
|
1681
|
+
|
|
1682
|
+
Returns:
|
|
1683
|
+
TimeElementsInNanoseconds: time elements or None if time elements are
|
|
1684
|
+
missing.
|
|
1685
|
+
|
|
1686
|
+
Raises:
|
|
1687
|
+
ValueError: if the instance is not a date time delta.
|
|
1688
|
+
"""
|
|
1689
|
+
if not self._is_delta:
|
|
1690
|
+
raise ValueError("Not a date time delta.")
|
|
1691
|
+
|
|
1692
|
+
if self._time_elements_tuple is None:
|
|
1693
|
+
return None
|
|
1694
|
+
|
|
1695
|
+
delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = (
|
|
1696
|
+
self._time_elements_tuple
|
|
1697
|
+
)
|
|
1698
|
+
time_elements_tuple = (
|
|
1699
|
+
year + delta_year,
|
|
1700
|
+
month + delta_month,
|
|
1701
|
+
day_of_month + delta_day_of_month,
|
|
1702
|
+
hours,
|
|
1703
|
+
minutes,
|
|
1704
|
+
seconds,
|
|
1705
|
+
self.nanoseconds,
|
|
1706
|
+
)
|
|
1707
|
+
return TimeElementsInNanoseconds(
|
|
1708
|
+
precision=self._precision,
|
|
1709
|
+
time_elements_tuple=time_elements_tuple,
|
|
1710
|
+
time_zone_offset=self._time_zone_offset,
|
|
1711
|
+
)
|
|
1712
|
+
|
|
1713
|
+
def NewFromDeltaAndYear(self, year):
|
|
1714
|
+
"""Creates a new time elements instance from a date time delta and a year.
|
|
1715
|
+
|
|
1716
|
+
Args:
|
|
1717
|
+
year (int): year.
|
|
1718
|
+
|
|
1719
|
+
Returns:
|
|
1720
|
+
TimeElementsInNanoseconds: time elements or None if time elements are
|
|
1721
|
+
missing.
|
|
1722
|
+
|
|
1723
|
+
Raises:
|
|
1724
|
+
ValueError: if the instance is not a date time delta.
|
|
1725
|
+
"""
|
|
1726
|
+
return self.NewFromDeltaAndDate(year, 0, 0)
|
|
1727
|
+
|
|
1728
|
+
|
|
1729
|
+
factory.Factory.RegisterDateTimeValues(TimeElements)
|
|
1730
|
+
factory.Factory.RegisterDateTimeValues(TimeElementsInMilliseconds)
|
|
1731
|
+
factory.Factory.RegisterDateTimeValues(TimeElementsInMicroseconds)
|
|
1732
|
+
factory.Factory.RegisterDateTimeValues(TimeElementsInNanoseconds)
|