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
dfdatetime/interface.py
ADDED
|
@@ -0,0 +1,1071 @@
|
|
|
1
|
+
"""Date and time interfaces."""
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import decimal
|
|
5
|
+
|
|
6
|
+
from dfdatetime import definitions
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DateTimeEpoch:
|
|
10
|
+
"""Date and time epoch interface.
|
|
11
|
+
|
|
12
|
+
This is the super class of different epoch representations.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
year (int): year that is the start of the epoch e.g. 1970.
|
|
16
|
+
month (int): month that is the start of the epoch, where 1 represents
|
|
17
|
+
January.
|
|
18
|
+
day_of_month (int): day of the month that is the start of the epoch,
|
|
19
|
+
where 1 represents the first day.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, year, month, day_of_month):
|
|
23
|
+
"""Initializes a date time epoch.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
year (int): year that is the start of the epoch e.g. 1970.
|
|
27
|
+
month (int): month that is the start of the epoch, where 1 represents
|
|
28
|
+
January.
|
|
29
|
+
day_of_month (int): day of the month that is the start of the epoch,
|
|
30
|
+
where 1 represents the first day.
|
|
31
|
+
"""
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.day_of_month = day_of_month
|
|
34
|
+
self.month = month
|
|
35
|
+
self.year = year
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class NormalizedTimeEpoch(DateTimeEpoch):
|
|
39
|
+
"""dfDateTime normalized time epoch."""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
"""Initializes a dfDateTime normalized time epoch."""
|
|
43
|
+
super().__init__(1970, 1, 1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DateTimeValues:
|
|
47
|
+
"""Date and time values interface.
|
|
48
|
+
|
|
49
|
+
This is the super class of different date and time representations.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
53
|
+
time_zone_hint (str): time zone hint, such as "Europe/Amsterdam", "CET" or
|
|
54
|
+
"UTC+1", or None if not set.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# pylint: disable=redundant-returns-doc
|
|
58
|
+
|
|
59
|
+
_EPOCH_NORMALIZED_TIME = NormalizedTimeEpoch()
|
|
60
|
+
|
|
61
|
+
_100_MILLISECONDS_PER_SECOND = 10
|
|
62
|
+
_10_MILLISECONDS_PER_SECOND = 100
|
|
63
|
+
_1_MILLISECOND_PER_SECOND = 1000
|
|
64
|
+
_100_MICROSECONDS_PER_SECOND = 10000
|
|
65
|
+
_10_MICROSECONDS_PER_SECOND = 100000
|
|
66
|
+
_1_MICROSECOND_PER_SECOND = 1000000
|
|
67
|
+
_100_NANOSECONDS_PER_SECOND = 10000000
|
|
68
|
+
_10_NANOSECONDS_PER_SECOND = 100000000
|
|
69
|
+
_1_NANOSECOND_PER_SECOND = definitions.NANOSECONDS_PER_SECOND
|
|
70
|
+
|
|
71
|
+
_100_NANOSECONDS_PER_MICROSECOND = 10
|
|
72
|
+
|
|
73
|
+
_INT64_MIN = -(1 << 63)
|
|
74
|
+
_INT64_MAX = (1 << 63) - 1
|
|
75
|
+
|
|
76
|
+
_UINT32_MAX = (1 << 32) - 1
|
|
77
|
+
_UINT60_MAX = (1 << 60) - 1
|
|
78
|
+
_UINT64_MAX = (1 << 64) - 1
|
|
79
|
+
|
|
80
|
+
_REMAINDER_MULTIPLIER = {
|
|
81
|
+
definitions.PRECISION_1_MILLISECOND: _1_MILLISECOND_PER_SECOND,
|
|
82
|
+
definitions.PRECISION_10_MILLISECONDS: _10_MILLISECONDS_PER_SECOND,
|
|
83
|
+
definitions.PRECISION_100_MILLISECONDS: _100_MILLISECONDS_PER_SECOND,
|
|
84
|
+
definitions.PRECISION_1_MICROSECOND: _1_MICROSECOND_PER_SECOND,
|
|
85
|
+
definitions.PRECISION_10_MICROSECONDS: _10_MICROSECONDS_PER_SECOND,
|
|
86
|
+
definitions.PRECISION_100_MICROSECONDS: _100_MICROSECONDS_PER_SECOND,
|
|
87
|
+
definitions.PRECISION_1_NANOSECOND: _1_NANOSECOND_PER_SECOND,
|
|
88
|
+
definitions.PRECISION_10_NANOSECONDS: _10_NANOSECONDS_PER_SECOND,
|
|
89
|
+
definitions.PRECISION_100_NANOSECONDS: _100_NANOSECONDS_PER_SECOND,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
def __init__(self, is_delta=False, precision=None, time_zone_offset=None):
|
|
93
|
+
"""Initializes date time values.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
is_delta (Optional[bool]): True if the date and time value is relative to
|
|
97
|
+
another date and time value.
|
|
98
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
99
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
100
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
101
|
+
from UTC or None if not set.
|
|
102
|
+
"""
|
|
103
|
+
super().__init__()
|
|
104
|
+
self._cached_date_time_values = None
|
|
105
|
+
self._is_delta = is_delta
|
|
106
|
+
self._normalized_timestamp = None
|
|
107
|
+
self._precision = precision
|
|
108
|
+
self._time_zone_offset = time_zone_offset
|
|
109
|
+
|
|
110
|
+
self.is_local_time = False
|
|
111
|
+
self.time_zone_hint = None
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def is_delta(self):
|
|
115
|
+
"""Is delta (bool): True if the date and time is relative to another."""
|
|
116
|
+
return self._is_delta
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def precision(self):
|
|
120
|
+
"""Precision (str): precision of the date and time value."""
|
|
121
|
+
return self._precision
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def time_zone_offset(self):
|
|
125
|
+
"""Time zone offset (int): time zone offset in minutes from UTC."""
|
|
126
|
+
return self._time_zone_offset
|
|
127
|
+
|
|
128
|
+
@time_zone_offset.setter
|
|
129
|
+
def time_zone_offset(self, time_zone_offset):
|
|
130
|
+
"""Sets the time zone offset.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
time_zone_offset (int): time zone offset in number of minutes from UTC or
|
|
134
|
+
None if not set.
|
|
135
|
+
"""
|
|
136
|
+
self._normalized_timestamp = None
|
|
137
|
+
self._time_zone_offset = time_zone_offset
|
|
138
|
+
|
|
139
|
+
def __eq__(self, other):
|
|
140
|
+
"""Determines if the date time values are equal to other.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
other (DateTimeValues): date time values to compare against.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
bool: True if the date time values are equal to other.
|
|
147
|
+
"""
|
|
148
|
+
if not isinstance(other, DateTimeValues):
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
152
|
+
other_normalized_timestamp = (
|
|
153
|
+
other._GetNormalizedTimestamp()
|
|
154
|
+
) # pylint: disable=protected-access
|
|
155
|
+
|
|
156
|
+
if normalized_timestamp is None and other_normalized_timestamp is not None:
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
if normalized_timestamp is not None and other_normalized_timestamp is None:
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
return normalized_timestamp == other_normalized_timestamp
|
|
163
|
+
|
|
164
|
+
def __ge__(self, other):
|
|
165
|
+
"""Determines if the date time values are greater than or equal to other.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
other (DateTimeValues): date time values to compare against.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
bool: True if the date time values are greater than or equal to other.
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
ValueError: if other is not an instance of DateTimeValues.
|
|
175
|
+
"""
|
|
176
|
+
if not isinstance(other, DateTimeValues):
|
|
177
|
+
raise ValueError("Other not an instance of DateTimeValues")
|
|
178
|
+
|
|
179
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
180
|
+
other_normalized_timestamp = (
|
|
181
|
+
other._GetNormalizedTimestamp()
|
|
182
|
+
) # pylint: disable=protected-access
|
|
183
|
+
|
|
184
|
+
if normalized_timestamp is None:
|
|
185
|
+
return other_normalized_timestamp is None
|
|
186
|
+
|
|
187
|
+
if other_normalized_timestamp is None:
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
return normalized_timestamp >= other_normalized_timestamp
|
|
191
|
+
|
|
192
|
+
def __gt__(self, other):
|
|
193
|
+
"""Determines if the date time values are greater than other.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
other (DateTimeValues): date time values to compare against.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
bool: True if the date time values are greater than other.
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
ValueError: if other is not an instance of DateTimeValues.
|
|
203
|
+
"""
|
|
204
|
+
if not isinstance(other, DateTimeValues):
|
|
205
|
+
raise ValueError("Other not an instance of DateTimeValues")
|
|
206
|
+
|
|
207
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
208
|
+
other_normalized_timestamp = (
|
|
209
|
+
other._GetNormalizedTimestamp()
|
|
210
|
+
) # pylint: disable=protected-access
|
|
211
|
+
|
|
212
|
+
if normalized_timestamp is None:
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
if other_normalized_timestamp is None:
|
|
216
|
+
return True
|
|
217
|
+
|
|
218
|
+
return normalized_timestamp > other_normalized_timestamp
|
|
219
|
+
|
|
220
|
+
def __le__(self, other):
|
|
221
|
+
"""Determines if the date time values are greater than or equal to other.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
other (DateTimeValues): date time values to compare against.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
bool: True if the date time values are greater than or equal to other.
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
ValueError: if other is not an instance of DateTimeValues.
|
|
231
|
+
"""
|
|
232
|
+
if not isinstance(other, DateTimeValues):
|
|
233
|
+
raise ValueError("Other not an instance of DateTimeValues")
|
|
234
|
+
|
|
235
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
236
|
+
other_normalized_timestamp = (
|
|
237
|
+
other._GetNormalizedTimestamp()
|
|
238
|
+
) # pylint: disable=protected-access
|
|
239
|
+
|
|
240
|
+
if normalized_timestamp is None:
|
|
241
|
+
return True
|
|
242
|
+
|
|
243
|
+
if other_normalized_timestamp is None:
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
return normalized_timestamp <= other_normalized_timestamp
|
|
247
|
+
|
|
248
|
+
def __lt__(self, other):
|
|
249
|
+
"""Determines if the date time values are less than other.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
other (DateTimeValues): date time values to compare against.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
bool: True if the date time values are less than other.
|
|
256
|
+
|
|
257
|
+
Raises:
|
|
258
|
+
ValueError: if other is not an instance of DateTimeValues.
|
|
259
|
+
"""
|
|
260
|
+
if not isinstance(other, DateTimeValues):
|
|
261
|
+
raise ValueError("Other not an instance of DateTimeValues")
|
|
262
|
+
|
|
263
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
264
|
+
other_normalized_timestamp = (
|
|
265
|
+
other._GetNormalizedTimestamp()
|
|
266
|
+
) # pylint: disable=protected-access
|
|
267
|
+
|
|
268
|
+
if normalized_timestamp is None:
|
|
269
|
+
return other_normalized_timestamp is not None
|
|
270
|
+
|
|
271
|
+
if other_normalized_timestamp is None:
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
return normalized_timestamp < other_normalized_timestamp
|
|
275
|
+
|
|
276
|
+
def __ne__(self, other):
|
|
277
|
+
"""Determines if the date time values are not equal to other.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
other (DateTimeValues): date time values to compare against.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
bool: True if the date time values are not equal to other.
|
|
284
|
+
"""
|
|
285
|
+
if not isinstance(other, DateTimeValues):
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
289
|
+
other_normalized_timestamp = (
|
|
290
|
+
other._GetNormalizedTimestamp()
|
|
291
|
+
) # pylint: disable=protected-access
|
|
292
|
+
|
|
293
|
+
if normalized_timestamp is None and other_normalized_timestamp is not None:
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
if normalized_timestamp is not None and other_normalized_timestamp is None:
|
|
297
|
+
return True
|
|
298
|
+
|
|
299
|
+
return normalized_timestamp != other_normalized_timestamp
|
|
300
|
+
|
|
301
|
+
def _CopyDateFromString(self, date_string):
|
|
302
|
+
"""Copies a date from a string.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
date_string (str): date value formatted as: YYYY-MM-DD
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
tuple[int, int, int]: year, month, day of month.
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
ValueError: if the date string is invalid or not supported.
|
|
312
|
+
"""
|
|
313
|
+
date_string_length = len(date_string)
|
|
314
|
+
|
|
315
|
+
# The date string should at least contain 'YYYY-MM-DD'.
|
|
316
|
+
if date_string_length < 10:
|
|
317
|
+
raise ValueError("Date string too short.")
|
|
318
|
+
|
|
319
|
+
if date_string[4] != "-" or date_string[7] != "-":
|
|
320
|
+
raise ValueError("Invalid date string.")
|
|
321
|
+
|
|
322
|
+
try:
|
|
323
|
+
year = int(date_string[0:4], 10)
|
|
324
|
+
except ValueError:
|
|
325
|
+
raise ValueError("Unable to parse year.")
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
month = int(date_string[5:7], 10)
|
|
329
|
+
except ValueError:
|
|
330
|
+
raise ValueError("Unable to parse month.")
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
day_of_month = int(date_string[8:10], 10)
|
|
334
|
+
except ValueError:
|
|
335
|
+
raise ValueError("Unable to parse day of month.")
|
|
336
|
+
|
|
337
|
+
days_per_month = self._GetDaysPerMonth(year, month)
|
|
338
|
+
if day_of_month < 1 or day_of_month > days_per_month:
|
|
339
|
+
raise ValueError("Day of month value out of bounds.")
|
|
340
|
+
|
|
341
|
+
return year, month, day_of_month
|
|
342
|
+
|
|
343
|
+
def _CopyDateTimeFromString(self, time_string):
|
|
344
|
+
"""Copies a date and time from a string.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
time_string (str): date and time value formatted as:
|
|
348
|
+
YYYY-MM-DD hh:mm:ss.######[+-]##:##
|
|
349
|
+
|
|
350
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
351
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
352
|
+
fraction and time zone offset are optional. The default time zone
|
|
353
|
+
is UTC.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
dict[str, int]: date and time values, such as year, month, day of month,
|
|
357
|
+
hours, minutes, seconds, nanoseconds, time zone offset in minutes.
|
|
358
|
+
|
|
359
|
+
Raises:
|
|
360
|
+
ValueError: if the time string is invalid or not supported.
|
|
361
|
+
"""
|
|
362
|
+
if not time_string:
|
|
363
|
+
raise ValueError("Invalid time string.")
|
|
364
|
+
|
|
365
|
+
time_string_length = len(time_string)
|
|
366
|
+
|
|
367
|
+
year, month, day_of_month = self._CopyDateFromString(time_string)
|
|
368
|
+
|
|
369
|
+
if time_string_length <= 10:
|
|
370
|
+
return {"year": year, "month": month, "day_of_month": day_of_month}
|
|
371
|
+
|
|
372
|
+
# If a time of day is specified the time string it should at least
|
|
373
|
+
# contain 'YYYY-MM-DD hh:mm:ss'.
|
|
374
|
+
if time_string[10] != " ":
|
|
375
|
+
raise ValueError(
|
|
376
|
+
"Invalid time string - space missing as date and time separator."
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
hours, minutes, seconds, nanoseconds, time_zone_offset = (
|
|
380
|
+
self._CopyTimeFromString(time_string[11:])
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
date_time_values = {
|
|
384
|
+
"year": year,
|
|
385
|
+
"month": month,
|
|
386
|
+
"day_of_month": day_of_month,
|
|
387
|
+
"hours": hours,
|
|
388
|
+
"minutes": minutes,
|
|
389
|
+
"seconds": seconds,
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if nanoseconds is not None:
|
|
393
|
+
date_time_values["nanoseconds"] = nanoseconds
|
|
394
|
+
if time_zone_offset is not None:
|
|
395
|
+
date_time_values["time_zone_offset"] = time_zone_offset
|
|
396
|
+
|
|
397
|
+
return date_time_values
|
|
398
|
+
|
|
399
|
+
def _CopyTimeFromString(self, time_string):
|
|
400
|
+
"""Copies a time from a string.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
time_string (str): time value formatted as:
|
|
404
|
+
hh:mm:ss.######[+-]##:##
|
|
405
|
+
|
|
406
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
407
|
+
fraction can be either 3, 6 or 9 digits. The seconds fraction and
|
|
408
|
+
time zone offset are optional.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
tuple[int, int, int, int, int]: hours, minutes, seconds, nanoseconds,
|
|
412
|
+
time zone offset in minutes.
|
|
413
|
+
|
|
414
|
+
Raises:
|
|
415
|
+
ValueError: if the time string is invalid or not supported.
|
|
416
|
+
"""
|
|
417
|
+
time_string_length = len(time_string)
|
|
418
|
+
|
|
419
|
+
# The time string should at least contain 'hh:mm:ss'.
|
|
420
|
+
if time_string_length < 8:
|
|
421
|
+
raise ValueError("Time string too short.")
|
|
422
|
+
|
|
423
|
+
if time_string[2] != ":" or time_string[5] != ":":
|
|
424
|
+
raise ValueError("Invalid time string.")
|
|
425
|
+
|
|
426
|
+
try:
|
|
427
|
+
hours = int(time_string[0:2], 10)
|
|
428
|
+
except ValueError:
|
|
429
|
+
raise ValueError("Unable to parse hours.")
|
|
430
|
+
|
|
431
|
+
if hours not in range(0, 24):
|
|
432
|
+
raise ValueError(f"Hours value: {hours:d} out of bounds.")
|
|
433
|
+
|
|
434
|
+
try:
|
|
435
|
+
minutes = int(time_string[3:5], 10)
|
|
436
|
+
except ValueError:
|
|
437
|
+
raise ValueError("Unable to parse minutes.")
|
|
438
|
+
|
|
439
|
+
if minutes not in range(0, 60):
|
|
440
|
+
raise ValueError(f"Minutes value: {minutes:d} out of bounds.")
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
seconds = int(time_string[6:8], 10)
|
|
444
|
+
except ValueError:
|
|
445
|
+
raise ValueError("Unable to parse day of seconds.")
|
|
446
|
+
|
|
447
|
+
# TODO: support a leap second?
|
|
448
|
+
if seconds not in range(0, 60):
|
|
449
|
+
raise ValueError(f"Seconds value: {seconds:d} out of bounds.")
|
|
450
|
+
|
|
451
|
+
nanoseconds = None
|
|
452
|
+
time_zone_offset = None
|
|
453
|
+
|
|
454
|
+
time_zone_string_index = 8
|
|
455
|
+
while time_zone_string_index < time_string_length:
|
|
456
|
+
if time_string[time_zone_string_index] in ("+", "-"):
|
|
457
|
+
break
|
|
458
|
+
|
|
459
|
+
time_zone_string_index += 1
|
|
460
|
+
|
|
461
|
+
# The calculations that follow rely on the time zone string index
|
|
462
|
+
# to point beyond the string in case no time zone offset was defined.
|
|
463
|
+
if time_zone_string_index == time_string_length - 1:
|
|
464
|
+
time_zone_string_index += 1
|
|
465
|
+
|
|
466
|
+
if time_string_length > 8 and time_string[8] == ".":
|
|
467
|
+
time_fraction_length = time_zone_string_index - 9
|
|
468
|
+
if time_fraction_length not in (3, 6, 9):
|
|
469
|
+
raise ValueError("Invalid time string.")
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
time_fraction = time_string[9:time_zone_string_index]
|
|
473
|
+
time_fraction = int(time_fraction, 10)
|
|
474
|
+
except ValueError:
|
|
475
|
+
raise ValueError("Unable to parse time fraction.")
|
|
476
|
+
|
|
477
|
+
if time_fraction_length == 3:
|
|
478
|
+
time_fraction *= 1000000
|
|
479
|
+
elif time_fraction_length == 6:
|
|
480
|
+
time_fraction *= 1000
|
|
481
|
+
|
|
482
|
+
nanoseconds = time_fraction
|
|
483
|
+
|
|
484
|
+
if time_zone_string_index < time_string_length:
|
|
485
|
+
if (
|
|
486
|
+
time_string_length - time_zone_string_index != 6
|
|
487
|
+
or time_string[time_zone_string_index + 3] != ":"
|
|
488
|
+
):
|
|
489
|
+
raise ValueError("Invalid time string.")
|
|
490
|
+
|
|
491
|
+
try:
|
|
492
|
+
hours_from_utc = int(
|
|
493
|
+
time_string[time_zone_string_index + 1 : time_zone_string_index + 3]
|
|
494
|
+
)
|
|
495
|
+
except ValueError:
|
|
496
|
+
raise ValueError("Unable to parse time zone hours offset.")
|
|
497
|
+
|
|
498
|
+
if hours_from_utc not in range(0, 15):
|
|
499
|
+
raise ValueError("Time zone hours offset value out of bounds.")
|
|
500
|
+
|
|
501
|
+
try:
|
|
502
|
+
minutes_from_utc = int(
|
|
503
|
+
time_string[time_zone_string_index + 4 : time_zone_string_index + 6]
|
|
504
|
+
)
|
|
505
|
+
except ValueError:
|
|
506
|
+
raise ValueError("Unable to parse time zone minutes offset.")
|
|
507
|
+
|
|
508
|
+
if minutes_from_utc not in range(0, 60):
|
|
509
|
+
raise ValueError("Time zone minutes offset value out of bounds.")
|
|
510
|
+
|
|
511
|
+
# pylint: disable=invalid-unary-operand-type
|
|
512
|
+
time_zone_offset = (hours_from_utc * 60) + minutes_from_utc
|
|
513
|
+
|
|
514
|
+
if time_string[time_zone_string_index] == "-":
|
|
515
|
+
time_zone_offset = -time_zone_offset
|
|
516
|
+
|
|
517
|
+
return hours, minutes, seconds, nanoseconds, time_zone_offset
|
|
518
|
+
|
|
519
|
+
def _CreateSerializableDict(self):
|
|
520
|
+
"""Creates a serializable dictionary.
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
dict[str, object]: serializable dictionary.
|
|
524
|
+
"""
|
|
525
|
+
serializable_dict = {
|
|
526
|
+
"__class_name__": type(self).__name__,
|
|
527
|
+
"__type__": "DateTimeValues",
|
|
528
|
+
}
|
|
529
|
+
if self.is_local_time:
|
|
530
|
+
serializable_dict["is_local_time"] = True
|
|
531
|
+
if self._time_zone_offset:
|
|
532
|
+
serializable_dict["time_zone_offset"] = self._time_zone_offset
|
|
533
|
+
if self.time_zone_hint:
|
|
534
|
+
serializable_dict["time_zone_hint"] = self.time_zone_hint
|
|
535
|
+
|
|
536
|
+
return serializable_dict
|
|
537
|
+
|
|
538
|
+
def _GetDateValues(
|
|
539
|
+
self, number_of_days, epoch_year, epoch_month, epoch_day_of_month
|
|
540
|
+
):
|
|
541
|
+
"""Determines date values.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
number_of_days (int): number of days since epoch.
|
|
545
|
+
epoch_year (int): year that is the start of the epoch e.g. 1970.
|
|
546
|
+
epoch_month (int): month that is the start of the epoch, where
|
|
547
|
+
1 represents January.
|
|
548
|
+
epoch_day_of_month (int): day of month that is the start of the epoch,
|
|
549
|
+
where 1 represents the first day.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
tuple[int, int, int]: year, month, day of month.
|
|
553
|
+
|
|
554
|
+
Raises:
|
|
555
|
+
ValueError: if the epoch year, month or day of month values are out
|
|
556
|
+
of bounds.
|
|
557
|
+
"""
|
|
558
|
+
if epoch_year < 0:
|
|
559
|
+
raise ValueError(f"Epoch year value: {epoch_year:d} out of bounds.")
|
|
560
|
+
|
|
561
|
+
if epoch_month not in range(1, 13):
|
|
562
|
+
raise ValueError(f"Epoch month value: {epoch_month:d} out of bounds.")
|
|
563
|
+
|
|
564
|
+
epoch_days_per_month = self._GetDaysPerMonth(epoch_year, epoch_month)
|
|
565
|
+
if epoch_day_of_month < 1 or epoch_day_of_month > epoch_days_per_month:
|
|
566
|
+
raise ValueError(
|
|
567
|
+
f"Epoch day of month value: {epoch_day_of_month:d} out of bounds."
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
before_epoch = number_of_days < 0
|
|
571
|
+
|
|
572
|
+
year = epoch_year
|
|
573
|
+
month = epoch_month
|
|
574
|
+
if before_epoch:
|
|
575
|
+
month -= 1
|
|
576
|
+
if month <= 0:
|
|
577
|
+
month = 12
|
|
578
|
+
year -= 1
|
|
579
|
+
|
|
580
|
+
number_of_days += epoch_day_of_month
|
|
581
|
+
if before_epoch:
|
|
582
|
+
number_of_days *= -1
|
|
583
|
+
|
|
584
|
+
# Align with the start of the year.
|
|
585
|
+
while month > 1:
|
|
586
|
+
days_per_month = self._GetDaysPerMonth(year, month)
|
|
587
|
+
if number_of_days < days_per_month:
|
|
588
|
+
break
|
|
589
|
+
|
|
590
|
+
if before_epoch:
|
|
591
|
+
month -= 1
|
|
592
|
+
else:
|
|
593
|
+
month += 1
|
|
594
|
+
|
|
595
|
+
if month > 12:
|
|
596
|
+
month = 1
|
|
597
|
+
year += 1
|
|
598
|
+
|
|
599
|
+
number_of_days -= days_per_month
|
|
600
|
+
|
|
601
|
+
# Align with the start of the next century.
|
|
602
|
+
_, remainder = divmod(year, 100)
|
|
603
|
+
for _ in range(remainder, 100):
|
|
604
|
+
days_in_year = self._GetNumberOfDaysInYear(year)
|
|
605
|
+
if number_of_days < days_in_year:
|
|
606
|
+
break
|
|
607
|
+
|
|
608
|
+
if before_epoch:
|
|
609
|
+
year -= 1
|
|
610
|
+
else:
|
|
611
|
+
year += 1
|
|
612
|
+
|
|
613
|
+
number_of_days -= days_in_year
|
|
614
|
+
|
|
615
|
+
days_in_century = self._GetNumberOfDaysInCentury(year)
|
|
616
|
+
while number_of_days > days_in_century:
|
|
617
|
+
if before_epoch:
|
|
618
|
+
year -= 100
|
|
619
|
+
else:
|
|
620
|
+
year += 100
|
|
621
|
+
|
|
622
|
+
number_of_days -= days_in_century
|
|
623
|
+
days_in_century = self._GetNumberOfDaysInCentury(year)
|
|
624
|
+
|
|
625
|
+
days_in_year = self._GetNumberOfDaysInYear(year)
|
|
626
|
+
while number_of_days > days_in_year:
|
|
627
|
+
if before_epoch:
|
|
628
|
+
year -= 1
|
|
629
|
+
else:
|
|
630
|
+
year += 1
|
|
631
|
+
|
|
632
|
+
number_of_days -= days_in_year
|
|
633
|
+
days_in_year = self._GetNumberOfDaysInYear(year)
|
|
634
|
+
|
|
635
|
+
days_per_month = self._GetDaysPerMonth(year, month)
|
|
636
|
+
while number_of_days > days_per_month:
|
|
637
|
+
if before_epoch:
|
|
638
|
+
month -= 1
|
|
639
|
+
else:
|
|
640
|
+
month += 1
|
|
641
|
+
|
|
642
|
+
if month <= 0:
|
|
643
|
+
month = 12
|
|
644
|
+
year -= 1
|
|
645
|
+
elif month > 12:
|
|
646
|
+
month = 1
|
|
647
|
+
year += 1
|
|
648
|
+
|
|
649
|
+
number_of_days -= days_per_month
|
|
650
|
+
days_per_month = self._GetDaysPerMonth(year, month)
|
|
651
|
+
|
|
652
|
+
if before_epoch:
|
|
653
|
+
days_per_month = self._GetDaysPerMonth(year, month)
|
|
654
|
+
number_of_days = days_per_month - number_of_days
|
|
655
|
+
|
|
656
|
+
elif number_of_days == 0:
|
|
657
|
+
number_of_days = 31
|
|
658
|
+
month = 12
|
|
659
|
+
year -= 1
|
|
660
|
+
|
|
661
|
+
return year, month, number_of_days
|
|
662
|
+
|
|
663
|
+
def _GetDateValuesWithEpoch(self, number_of_days, date_time_epoch):
|
|
664
|
+
"""Determines date values.
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
number_of_days (int): number of days since epoch.
|
|
668
|
+
date_time_epoch (DateTimeEpoch): date and time of the epoch.
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
tuple[int, int, int]: year, month, day of month.
|
|
672
|
+
"""
|
|
673
|
+
return self._GetDateValues(
|
|
674
|
+
number_of_days,
|
|
675
|
+
date_time_epoch.year,
|
|
676
|
+
date_time_epoch.month,
|
|
677
|
+
date_time_epoch.day_of_month,
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
def _GetDateWithTimeOfDay(self):
|
|
681
|
+
"""Retrieves the date with time of day.
|
|
682
|
+
|
|
683
|
+
Note that the date and time are adjusted to UTC.
|
|
684
|
+
|
|
685
|
+
Returns:
|
|
686
|
+
tuple[int, int, int, int, int, int]: year, month, day of month, hours,
|
|
687
|
+
minutes, seconds or (None, None, None, None, None, None)
|
|
688
|
+
if the date and time values do not represent a date or time of day.
|
|
689
|
+
"""
|
|
690
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
691
|
+
if normalized_timestamp is None:
|
|
692
|
+
return None, None, None, None, None, None
|
|
693
|
+
|
|
694
|
+
if (
|
|
695
|
+
not self._cached_date_time_values
|
|
696
|
+
or self._cached_date_time_values[0] != normalized_timestamp
|
|
697
|
+
):
|
|
698
|
+
number_of_days, hours, minutes, seconds = self._GetTimeValues(
|
|
699
|
+
normalized_timestamp
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
try:
|
|
703
|
+
year, month, day_of_month = self._GetDateValuesWithEpoch(
|
|
704
|
+
number_of_days, self._EPOCH_NORMALIZED_TIME
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
except ValueError:
|
|
708
|
+
return None, None, None, None, None, None
|
|
709
|
+
|
|
710
|
+
self._cached_date_time_values = (
|
|
711
|
+
normalized_timestamp,
|
|
712
|
+
year,
|
|
713
|
+
month,
|
|
714
|
+
day_of_month,
|
|
715
|
+
hours,
|
|
716
|
+
minutes,
|
|
717
|
+
seconds,
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
return self._cached_date_time_values[1:]
|
|
721
|
+
|
|
722
|
+
def _GetDayOfYear(self, year, month, day_of_month):
|
|
723
|
+
"""Retrieves the day of the year for a specific day of a month in a year.
|
|
724
|
+
|
|
725
|
+
Args:
|
|
726
|
+
year (int): year e.g. 1970.
|
|
727
|
+
month (int): month, where 1 represents January.
|
|
728
|
+
day_of_month (int): day of the month, where 1 represents the first day.
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
int: day of year.
|
|
732
|
+
|
|
733
|
+
Raises:
|
|
734
|
+
ValueError: if the month or day of month value is out of bounds.
|
|
735
|
+
"""
|
|
736
|
+
if month not in range(1, 13):
|
|
737
|
+
raise ValueError("Month value out of bounds.")
|
|
738
|
+
|
|
739
|
+
days_per_month = self._GetDaysPerMonth(year, month)
|
|
740
|
+
if day_of_month < 1 or day_of_month > days_per_month:
|
|
741
|
+
raise ValueError("Day of month value out of bounds.")
|
|
742
|
+
|
|
743
|
+
day_of_year = day_of_month
|
|
744
|
+
for past_month in range(1, month):
|
|
745
|
+
day_of_year += self._GetDaysPerMonth(year, past_month)
|
|
746
|
+
|
|
747
|
+
return day_of_year
|
|
748
|
+
|
|
749
|
+
def _GetDaysPerMonth(self, year, month):
|
|
750
|
+
"""Retrieves the number of days in a month of a specific year.
|
|
751
|
+
|
|
752
|
+
Args:
|
|
753
|
+
year (int): year e.g. 1970.
|
|
754
|
+
month (int): month, where 1 represents January.
|
|
755
|
+
|
|
756
|
+
Returns:
|
|
757
|
+
int: number of days in the month.
|
|
758
|
+
|
|
759
|
+
Raises:
|
|
760
|
+
ValueError: if the month value is out of bounds.
|
|
761
|
+
"""
|
|
762
|
+
if month not in range(1, 13):
|
|
763
|
+
raise ValueError("Month value out of bounds.")
|
|
764
|
+
|
|
765
|
+
days_per_month = definitions.DAYS_PER_MONTH[month - 1]
|
|
766
|
+
if month == 2 and (self._is_delta or self._IsLeapYear(year)):
|
|
767
|
+
days_per_month += 1
|
|
768
|
+
|
|
769
|
+
return days_per_month
|
|
770
|
+
|
|
771
|
+
@abc.abstractmethod
|
|
772
|
+
def _GetNormalizedTimestamp(self):
|
|
773
|
+
"""Retrieves the normalized timestamp.
|
|
774
|
+
|
|
775
|
+
Returns:
|
|
776
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
777
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
778
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
779
|
+
determined.
|
|
780
|
+
"""
|
|
781
|
+
|
|
782
|
+
def _GetNumberOfDaysInCentury(self, year):
|
|
783
|
+
"""Retrieves the number of days in a century.
|
|
784
|
+
|
|
785
|
+
Args:
|
|
786
|
+
year (int): year in the century e.g. 1970.
|
|
787
|
+
|
|
788
|
+
Returns:
|
|
789
|
+
int: number of (remaining) days in the century.
|
|
790
|
+
|
|
791
|
+
Raises:
|
|
792
|
+
ValueError: if the year value is out of bounds.
|
|
793
|
+
"""
|
|
794
|
+
if year < 0:
|
|
795
|
+
raise ValueError("Year value out of bounds.")
|
|
796
|
+
|
|
797
|
+
year, _ = divmod(year, 100)
|
|
798
|
+
year *= 100
|
|
799
|
+
|
|
800
|
+
number_of_days = definitions.DAYS_PER_CENTURY.get(year)
|
|
801
|
+
if number_of_days is not None:
|
|
802
|
+
return number_of_days
|
|
803
|
+
|
|
804
|
+
if self._IsLeapYear(year):
|
|
805
|
+
return 36525
|
|
806
|
+
return 36524
|
|
807
|
+
|
|
808
|
+
def _GetNumberOfDaysInYear(self, year):
|
|
809
|
+
"""Retrieves the number of days in a specific year.
|
|
810
|
+
|
|
811
|
+
Args:
|
|
812
|
+
year (int): year e.g. 1970.
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
int: number of days in the year.
|
|
816
|
+
"""
|
|
817
|
+
number_of_days = definitions.DAYS_PER_YEAR.get(year)
|
|
818
|
+
if number_of_days is not None:
|
|
819
|
+
return number_of_days
|
|
820
|
+
|
|
821
|
+
if self._IsLeapYear(year):
|
|
822
|
+
return 366
|
|
823
|
+
return 365
|
|
824
|
+
|
|
825
|
+
def _GetNumberOfSecondsFromElements(
|
|
826
|
+
self, year, month, day_of_month, hours, minutes, seconds
|
|
827
|
+
):
|
|
828
|
+
"""Retrieves the number of seconds from the date and time elements.
|
|
829
|
+
|
|
830
|
+
Args:
|
|
831
|
+
year (int): year e.g. 1970.
|
|
832
|
+
month (int): month, where 1 represents January.
|
|
833
|
+
day_of_month (int): day of the month, where 1 represents the first day.
|
|
834
|
+
hours (int): hours.
|
|
835
|
+
minutes (int): minutes.
|
|
836
|
+
seconds (int): seconds.
|
|
837
|
+
|
|
838
|
+
Returns:
|
|
839
|
+
int: number of seconds since January 1, 1970 00:00:00 or None if year,
|
|
840
|
+
month or day of month are not set.
|
|
841
|
+
|
|
842
|
+
Raises:
|
|
843
|
+
ValueError: if the time elements are invalid.
|
|
844
|
+
"""
|
|
845
|
+
if not month or not day_of_month:
|
|
846
|
+
return None
|
|
847
|
+
|
|
848
|
+
if hours is None:
|
|
849
|
+
hours = 0
|
|
850
|
+
elif hours not in range(0, 24):
|
|
851
|
+
raise ValueError(f"Hours value: {hours!s} out of bounds.")
|
|
852
|
+
|
|
853
|
+
if minutes is None:
|
|
854
|
+
minutes = 0
|
|
855
|
+
elif minutes not in range(0, 60):
|
|
856
|
+
raise ValueError(f"Minutes value: {minutes!s} out of bounds.")
|
|
857
|
+
|
|
858
|
+
# TODO: support a leap second?
|
|
859
|
+
if seconds is None:
|
|
860
|
+
seconds = 0
|
|
861
|
+
elif seconds not in range(0, 60):
|
|
862
|
+
raise ValueError(f"Seconds value: {seconds!s} out of bounds.")
|
|
863
|
+
|
|
864
|
+
number_of_days = definitions.DAYS_PER_YEAR_IN_POSIX_EPOCH.get(year)
|
|
865
|
+
if number_of_days is None:
|
|
866
|
+
raise ValueError(f"Year value: {year!s} out of bounds.")
|
|
867
|
+
|
|
868
|
+
number_of_days += sum(
|
|
869
|
+
definitions.DAYS_PER_MONTH[index] for index in range(month - 1)
|
|
870
|
+
)
|
|
871
|
+
if month > 2 and self._IsLeapYear(year):
|
|
872
|
+
number_of_days += 1
|
|
873
|
+
|
|
874
|
+
days_per_month = self._GetDaysPerMonth(year, month)
|
|
875
|
+
if day_of_month < 1 or day_of_month > days_per_month:
|
|
876
|
+
raise ValueError(f"Day of month value: {day_of_month:d} out of bounds.")
|
|
877
|
+
|
|
878
|
+
number_of_days += day_of_month - 1
|
|
879
|
+
number_of_hours = (number_of_days * 24) + hours
|
|
880
|
+
number_of_minutes = (number_of_hours * 60) + minutes
|
|
881
|
+
number_of_seconds = (number_of_minutes * 60) + seconds
|
|
882
|
+
|
|
883
|
+
return number_of_seconds
|
|
884
|
+
|
|
885
|
+
def _GetTimeValues(self, number_of_seconds):
|
|
886
|
+
"""Determines time values.
|
|
887
|
+
|
|
888
|
+
Args:
|
|
889
|
+
number_of_seconds (int|decimal.Decimal): number of seconds.
|
|
890
|
+
|
|
891
|
+
Returns:
|
|
892
|
+
tuple[int, int, int, int]: days, hours, minutes, seconds.
|
|
893
|
+
"""
|
|
894
|
+
number_of_seconds = int(number_of_seconds)
|
|
895
|
+
number_of_minutes, seconds = divmod(number_of_seconds, 60)
|
|
896
|
+
number_of_hours, minutes = divmod(number_of_minutes, 60)
|
|
897
|
+
number_of_days, hours = divmod(number_of_hours, 24)
|
|
898
|
+
return number_of_days, hours, minutes, seconds
|
|
899
|
+
|
|
900
|
+
def _IsLeapYear(self, year):
|
|
901
|
+
"""Determines if a year is a leap year.
|
|
902
|
+
|
|
903
|
+
Args:
|
|
904
|
+
year (int): year e.g. 1970.
|
|
905
|
+
|
|
906
|
+
Returns:
|
|
907
|
+
bool: True if the year is a leap year.
|
|
908
|
+
"""
|
|
909
|
+
# pylint: disable=consider-using-ternary
|
|
910
|
+
return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
|
|
911
|
+
|
|
912
|
+
@abc.abstractmethod
|
|
913
|
+
def CopyFromDateTimeString(self, time_string):
|
|
914
|
+
"""Copies a date time value from a date and time string.
|
|
915
|
+
|
|
916
|
+
Args:
|
|
917
|
+
time_string (str): date and time value formatted as:
|
|
918
|
+
YYYY-MM-DD hh:mm:ss.######[+-]##:##
|
|
919
|
+
|
|
920
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
921
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
922
|
+
fraction and time zone offset are optional. The default time zone
|
|
923
|
+
is UTC.
|
|
924
|
+
|
|
925
|
+
Raises:
|
|
926
|
+
ValueError: if the time string is invalid or not supported.
|
|
927
|
+
"""
|
|
928
|
+
|
|
929
|
+
def CopyToPosixTimestamp(self):
|
|
930
|
+
"""Copies the date time value to a POSIX timestamp.
|
|
931
|
+
|
|
932
|
+
Returns:
|
|
933
|
+
int: a POSIX timestamp in seconds or None if no timestamp is available.
|
|
934
|
+
"""
|
|
935
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
936
|
+
if normalized_timestamp is None:
|
|
937
|
+
return None
|
|
938
|
+
|
|
939
|
+
return int(normalized_timestamp)
|
|
940
|
+
|
|
941
|
+
def CopyToPosixTimestampWithFractionOfSecond(self):
|
|
942
|
+
"""Copies the date time value to a POSIX timestamp with fraction of second.
|
|
943
|
+
|
|
944
|
+
Returns:
|
|
945
|
+
tuple[int, int]: a POSIX timestamp in seconds with fraction of second or
|
|
946
|
+
None, None if no timestamp is available.
|
|
947
|
+
"""
|
|
948
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
949
|
+
if normalized_timestamp is None:
|
|
950
|
+
return None, None
|
|
951
|
+
|
|
952
|
+
remainder_multiplier = self._REMAINDER_MULTIPLIER.get(self._precision)
|
|
953
|
+
if not remainder_multiplier:
|
|
954
|
+
remainder = None
|
|
955
|
+
elif normalized_timestamp >= 0:
|
|
956
|
+
remainder = int((normalized_timestamp % 1) * remainder_multiplier)
|
|
957
|
+
else:
|
|
958
|
+
remainder = int((normalized_timestamp % 1) * -remainder_multiplier)
|
|
959
|
+
|
|
960
|
+
return int(normalized_timestamp), remainder
|
|
961
|
+
|
|
962
|
+
@abc.abstractmethod
|
|
963
|
+
def CopyToDateTimeString(self):
|
|
964
|
+
"""Copies the date time value to a date and time string.
|
|
965
|
+
|
|
966
|
+
Returns:
|
|
967
|
+
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
|
|
968
|
+
None if the timestamp cannot be copied to a date and time string.
|
|
969
|
+
"""
|
|
970
|
+
|
|
971
|
+
def CopyToDateTimeStringISO8601(self):
|
|
972
|
+
"""Copies the date time value to an ISO 8601 date and time string.
|
|
973
|
+
|
|
974
|
+
Returns:
|
|
975
|
+
str: date and time value formatted as an ISO 8601 date and time string or
|
|
976
|
+
None if the timestamp cannot be copied to a date and time string.
|
|
977
|
+
"""
|
|
978
|
+
date_time_string = self.CopyToDateTimeString()
|
|
979
|
+
if date_time_string:
|
|
980
|
+
date_time_string = date_time_string.replace(" ", "T")
|
|
981
|
+
|
|
982
|
+
if self._time_zone_offset is not None or not self.is_local_time:
|
|
983
|
+
time_zone_offset_hours, time_zone_offset_minutes = divmod(
|
|
984
|
+
self._time_zone_offset or 0, 60
|
|
985
|
+
)
|
|
986
|
+
if time_zone_offset_hours >= 0:
|
|
987
|
+
time_zone_offset_sign = "+"
|
|
988
|
+
else:
|
|
989
|
+
time_zone_offset_sign = "-"
|
|
990
|
+
time_zone_offset_hours *= -1
|
|
991
|
+
|
|
992
|
+
time_zone_string = (
|
|
993
|
+
f"{time_zone_offset_hours:02d}:{time_zone_offset_minutes:02d}"
|
|
994
|
+
)
|
|
995
|
+
date_time_string = time_zone_offset_sign.join(
|
|
996
|
+
[date_time_string, time_zone_string]
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
return date_time_string
|
|
1000
|
+
|
|
1001
|
+
@abc.abstractmethod
|
|
1002
|
+
def CopyToSerializableDict(self):
|
|
1003
|
+
"""Copies the date time value to a serializable dictionary.
|
|
1004
|
+
|
|
1005
|
+
Returns:
|
|
1006
|
+
dict[str, object]: serializable dictionary.
|
|
1007
|
+
"""
|
|
1008
|
+
|
|
1009
|
+
def GetDate(self):
|
|
1010
|
+
"""Retrieves the date represented by the date and time values.
|
|
1011
|
+
|
|
1012
|
+
Note that the date is adjusted to UTC.
|
|
1013
|
+
|
|
1014
|
+
Returns:
|
|
1015
|
+
tuple[int, int, int]: year, month, day of month or (None, None, None)
|
|
1016
|
+
if the date and time values do not represent a date.
|
|
1017
|
+
"""
|
|
1018
|
+
year, month, day_of_month, _, _, _ = self._GetDateWithTimeOfDay()
|
|
1019
|
+
return year, month, day_of_month
|
|
1020
|
+
|
|
1021
|
+
def GetDateWithTimeOfDay(self):
|
|
1022
|
+
"""Retrieves the date with time of day.
|
|
1023
|
+
|
|
1024
|
+
Note that the date and time are adjusted to UTC.
|
|
1025
|
+
|
|
1026
|
+
Returns:
|
|
1027
|
+
tuple[int, int, int, int, int, int]: year, month, day of month, hours,
|
|
1028
|
+
minutes, seconds or (None, None, None, None, None, None)
|
|
1029
|
+
if the date and time values do not represent a date or time of day.
|
|
1030
|
+
"""
|
|
1031
|
+
return self._GetDateWithTimeOfDay()
|
|
1032
|
+
|
|
1033
|
+
# TODO: remove this method when there is no more need for it in Plaso.
|
|
1034
|
+
def GetPlasoTimestamp(self):
|
|
1035
|
+
"""Retrieves a timestamp that is compatible with Plaso.
|
|
1036
|
+
|
|
1037
|
+
Returns:
|
|
1038
|
+
int: a POSIX timestamp in microseconds or None if no timestamp is
|
|
1039
|
+
available.
|
|
1040
|
+
|
|
1041
|
+
Raises:
|
|
1042
|
+
ValueError: if the timestamp cannot be determined.
|
|
1043
|
+
"""
|
|
1044
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
1045
|
+
if normalized_timestamp is None:
|
|
1046
|
+
return None
|
|
1047
|
+
|
|
1048
|
+
normalized_timestamp *= definitions.MICROSECONDS_PER_SECOND
|
|
1049
|
+
|
|
1050
|
+
try:
|
|
1051
|
+
normalized_timestamp = normalized_timestamp.quantize(
|
|
1052
|
+
1, rounding=decimal.ROUND_HALF_UP
|
|
1053
|
+
)
|
|
1054
|
+
except decimal.InvalidOperation as exception:
|
|
1055
|
+
raise ValueError(
|
|
1056
|
+
f"Unable to round normalized timestamp with error: {exception!s}"
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
return int(normalized_timestamp)
|
|
1060
|
+
|
|
1061
|
+
def GetTimeOfDay(self):
|
|
1062
|
+
"""Retrieves the time of day represented by the date and time values.
|
|
1063
|
+
|
|
1064
|
+
Note that the time is adjusted to UTC.
|
|
1065
|
+
|
|
1066
|
+
Returns:
|
|
1067
|
+
tuple[int, int, int]: hours, minutes, seconds or (None, None, None)
|
|
1068
|
+
if the date and time values do not represent a time of day.
|
|
1069
|
+
"""
|
|
1070
|
+
_, _, _, hours, minutes, seconds = self._GetDateWithTimeOfDay()
|
|
1071
|
+
return hours, minutes, seconds
|