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/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Digital Forensics Date and Time (dfDateTime).
|
|
2
|
+
|
|
3
|
+
dfDateTime, or Digital Forensics date and time, provides date and time objects to
|
|
4
|
+
preserve accuracy and precision.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Imports for date time values factory.
|
|
8
|
+
from dfdatetime import apfs_time
|
|
9
|
+
from dfdatetime import cocoa_time
|
|
10
|
+
from dfdatetime import delphi_date_time
|
|
11
|
+
from dfdatetime import dotnet_datetime
|
|
12
|
+
from dfdatetime import fat_date_time
|
|
13
|
+
from dfdatetime import filetime
|
|
14
|
+
from dfdatetime import hfs_time
|
|
15
|
+
from dfdatetime import golang_time
|
|
16
|
+
from dfdatetime import java_time
|
|
17
|
+
from dfdatetime import ole_automation_date
|
|
18
|
+
from dfdatetime import posix_time
|
|
19
|
+
from dfdatetime import rfc2579_date_time
|
|
20
|
+
from dfdatetime import semantic_time
|
|
21
|
+
from dfdatetime import systemtime
|
|
22
|
+
from dfdatetime import time_elements
|
|
23
|
+
from dfdatetime import uuid_time
|
|
24
|
+
from dfdatetime import webkit_time
|
|
25
|
+
|
|
26
|
+
__version__ = "20260730"
|
dfdatetime/apfs_time.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Apple File System (APFS) time implementation."""
|
|
2
|
+
|
|
3
|
+
import decimal
|
|
4
|
+
|
|
5
|
+
from dfdatetime import definitions
|
|
6
|
+
from dfdatetime import factory
|
|
7
|
+
from dfdatetime import posix_time
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class APFSTime(posix_time.PosixTimeInNanoseconds):
|
|
11
|
+
"""Apple File System (APFS) timestamp.
|
|
12
|
+
|
|
13
|
+
The APFS timestamp is a signed 64-bit integer that contains the number of
|
|
14
|
+
nanoseconds since 1970-01-01 00:00:00.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def _GetNormalizedTimestamp(self):
|
|
21
|
+
"""Retrieves the normalized timestamp.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
25
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
26
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
27
|
+
determined.
|
|
28
|
+
"""
|
|
29
|
+
if self._normalized_timestamp is None:
|
|
30
|
+
if (
|
|
31
|
+
self._timestamp is not None
|
|
32
|
+
and self._timestamp >= self._INT64_MIN
|
|
33
|
+
and self._timestamp <= self._INT64_MAX
|
|
34
|
+
):
|
|
35
|
+
self._normalized_timestamp = (
|
|
36
|
+
decimal.Decimal(self._timestamp)
|
|
37
|
+
/ definitions.NANOSECONDS_PER_SECOND
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if self._time_zone_offset:
|
|
41
|
+
self._normalized_timestamp -= self._time_zone_offset * 60
|
|
42
|
+
|
|
43
|
+
return self._normalized_timestamp
|
|
44
|
+
|
|
45
|
+
def CopyFromDateTimeString(self, time_string):
|
|
46
|
+
"""Copies a APFS timestamp from a date and time string.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
time_string (str): date and time value formatted as:
|
|
50
|
+
YYYY-MM-DD hh:mm:ss.######[+-]##:##
|
|
51
|
+
|
|
52
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
53
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
54
|
+
fraction and time zone offset are optional. The default time zone
|
|
55
|
+
is UTC.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: if the date and time value is not supported.
|
|
59
|
+
"""
|
|
60
|
+
super().CopyFromDateTimeString(time_string)
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
self._timestamp is None
|
|
64
|
+
or self._timestamp < self._INT64_MIN
|
|
65
|
+
or self._timestamp > self._INT64_MAX
|
|
66
|
+
):
|
|
67
|
+
raise ValueError("Date time value not supported.")
|
|
68
|
+
|
|
69
|
+
def CopyToDateTimeString(self):
|
|
70
|
+
"""Copies the APFS timestamp to a date and time string.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.#########" or
|
|
74
|
+
None if the timestamp is missing or invalid.
|
|
75
|
+
"""
|
|
76
|
+
if (
|
|
77
|
+
self._timestamp is None
|
|
78
|
+
or self._timestamp < self._INT64_MIN
|
|
79
|
+
or self._timestamp > self._INT64_MAX
|
|
80
|
+
):
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
return super().CopyToDateTimeString()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
factory.Factory.RegisterDateTimeValues(APFSTime)
|
dfdatetime/cocoa_time.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Cocoa timestamp implementation."""
|
|
2
|
+
|
|
3
|
+
import decimal
|
|
4
|
+
|
|
5
|
+
from dfdatetime import definitions
|
|
6
|
+
from dfdatetime import factory
|
|
7
|
+
from dfdatetime import interface
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CocoaTimeEpoch(interface.DateTimeEpoch):
|
|
11
|
+
"""Cocoa time epoch."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initializes a Cocoa time epoch."""
|
|
15
|
+
super().__init__(2001, 1, 1)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CocoaTime(interface.DateTimeValues):
|
|
19
|
+
"""Cocoa timestamp.
|
|
20
|
+
|
|
21
|
+
The Cocoa timestamp is a floating point value that contains the number of
|
|
22
|
+
seconds since 2001-01-01 00:00:00 (also known as the Cocoa epoch).
|
|
23
|
+
Negative values represent date and times predating the Cocoa epoch.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# The difference between January 1, 2001 and January 1, 1970 in seconds.
|
|
30
|
+
_COCOA_TO_POSIX_BASE = -978307200
|
|
31
|
+
|
|
32
|
+
_EPOCH = CocoaTimeEpoch()
|
|
33
|
+
|
|
34
|
+
def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
|
|
35
|
+
"""Initializes a Cocoa timestamp.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
39
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
40
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
41
|
+
from UTC or None if not set.
|
|
42
|
+
timestamp (Optional[float]): Cocoa timestamp.
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(
|
|
45
|
+
precision=precision or definitions.PRECISION_1_SECOND,
|
|
46
|
+
time_zone_offset=time_zone_offset,
|
|
47
|
+
)
|
|
48
|
+
self._timestamp = timestamp
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def timestamp(self):
|
|
52
|
+
"""float: Cocoa timestamp or None if not set."""
|
|
53
|
+
return self._timestamp
|
|
54
|
+
|
|
55
|
+
def _GetNormalizedTimestamp(self):
|
|
56
|
+
"""Retrieves the normalized timestamp.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
60
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
61
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
62
|
+
determined.
|
|
63
|
+
"""
|
|
64
|
+
if self._normalized_timestamp is None:
|
|
65
|
+
if self._timestamp is not None:
|
|
66
|
+
self._normalized_timestamp = (
|
|
67
|
+
decimal.Decimal(self._timestamp) - self._COCOA_TO_POSIX_BASE
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if self._time_zone_offset:
|
|
71
|
+
self._normalized_timestamp -= self._time_zone_offset * 60
|
|
72
|
+
|
|
73
|
+
return self._normalized_timestamp
|
|
74
|
+
|
|
75
|
+
def CopyFromDateTimeString(self, time_string):
|
|
76
|
+
"""Copies a Cocoa timestamp from a date and time string.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
time_string (str): date and time value formatted as:
|
|
80
|
+
YYYY-MM-DD hh:mm:ss.######[+-]##:##
|
|
81
|
+
|
|
82
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
83
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
84
|
+
fraction and time zone offset are optional. The default time zone
|
|
85
|
+
is UTC.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: if the time string is invalid or not supported.
|
|
89
|
+
"""
|
|
90
|
+
date_time_values = self._CopyDateTimeFromString(time_string)
|
|
91
|
+
|
|
92
|
+
year = date_time_values.get("year", 0)
|
|
93
|
+
month = date_time_values.get("month", 0)
|
|
94
|
+
day_of_month = date_time_values.get("day_of_month", 0)
|
|
95
|
+
hours = date_time_values.get("hours", 0)
|
|
96
|
+
minutes = date_time_values.get("minutes", 0)
|
|
97
|
+
seconds = date_time_values.get("seconds", 0)
|
|
98
|
+
nanoseconds = date_time_values.get("nanoseconds", 0)
|
|
99
|
+
time_zone_offset = date_time_values.get("time_zone_offset")
|
|
100
|
+
|
|
101
|
+
timestamp = self._GetNumberOfSecondsFromElements(
|
|
102
|
+
year, month, day_of_month, hours, minutes, seconds
|
|
103
|
+
)
|
|
104
|
+
timestamp += self._COCOA_TO_POSIX_BASE
|
|
105
|
+
|
|
106
|
+
timestamp = float(timestamp)
|
|
107
|
+
timestamp += float(nanoseconds) / definitions.NANOSECONDS_PER_SECOND
|
|
108
|
+
|
|
109
|
+
self._normalized_timestamp = None
|
|
110
|
+
self._timestamp = timestamp
|
|
111
|
+
self._time_zone_offset = time_zone_offset
|
|
112
|
+
|
|
113
|
+
def CopyToDateTimeString(self):
|
|
114
|
+
"""Copies the Cocoa timestamp to a date and time string.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
str: date and time value formatted as: YYYY-MM-DD hh:mm:ss.###### or
|
|
118
|
+
None if the timestamp cannot be copied to a date and time string.
|
|
119
|
+
"""
|
|
120
|
+
if self._timestamp is None:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
number_of_days, hours, minutes, seconds = self._GetTimeValues(
|
|
124
|
+
int(self._timestamp)
|
|
125
|
+
)
|
|
126
|
+
year, month, day_of_month = self._GetDateValuesWithEpoch(
|
|
127
|
+
number_of_days, self._EPOCH
|
|
128
|
+
)
|
|
129
|
+
microseconds = int((self._timestamp % 1) * definitions.MICROSECONDS_PER_SECOND)
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
f"{year:04d}-{month:02d}-{day_of_month:02d} "
|
|
133
|
+
f"{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def CopyToSerializableDict(self):
|
|
137
|
+
"""Copies the date time value to a serializable dictionary.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
dict[str, object]: serializable dictionary.
|
|
141
|
+
"""
|
|
142
|
+
serializable_dict = self._CreateSerializableDict()
|
|
143
|
+
|
|
144
|
+
serializable_dict["timestamp"] = self._timestamp
|
|
145
|
+
|
|
146
|
+
return serializable_dict
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
factory.Factory.RegisterDateTimeValues(CocoaTime)
|
dfdatetime/decorators.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Function decorators."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def deprecated(function): # pylint: disable=invalid-name
|
|
7
|
+
"""Decorator to mark functions or methods as deprecated."""
|
|
8
|
+
|
|
9
|
+
def IssueDeprecationWarning(*args, **kwargs):
|
|
10
|
+
"""Issue a deprecation warning."""
|
|
11
|
+
warnings.simplefilter("default", DeprecationWarning)
|
|
12
|
+
warnings.warn(
|
|
13
|
+
f"Call to deprecated function: {function.__name__:s}.",
|
|
14
|
+
category=DeprecationWarning,
|
|
15
|
+
stacklevel=2,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
return function(*args, **kwargs)
|
|
19
|
+
|
|
20
|
+
IssueDeprecationWarning.__name__ = function.__name__
|
|
21
|
+
IssueDeprecationWarning.__doc__ = function.__doc__
|
|
22
|
+
IssueDeprecationWarning.__dict__.update(function.__dict__)
|
|
23
|
+
return IssueDeprecationWarning
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""The date and time definitions.
|
|
2
|
+
|
|
3
|
+
Also see:
|
|
4
|
+
https://en.wikipedia.org/wiki/Day
|
|
5
|
+
https://en.wikipedia.org/wiki/Hour
|
|
6
|
+
https://en.wikipedia.org/wiki/Minute
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
DAYS_PER_MONTH = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
|
|
10
|
+
|
|
11
|
+
SECONDS_PER_DAY = 86400
|
|
12
|
+
|
|
13
|
+
DECISECONDS_PER_SECOND = 10
|
|
14
|
+
|
|
15
|
+
CENTISECONDS_PER_SECOND = 100
|
|
16
|
+
|
|
17
|
+
MILLISECONDS_PER_SECOND = 1000
|
|
18
|
+
|
|
19
|
+
DECIMICROSECONDS_PER_SECOND = 10000
|
|
20
|
+
|
|
21
|
+
MICROSECONDS_PER_DAY = 86400000000
|
|
22
|
+
MICROSECONDS_PER_SECOND = 1000000
|
|
23
|
+
MICROSECONDS_PER_DECISECOND = 100000
|
|
24
|
+
MICROSECONDS_PER_MILLISECOND = 1000
|
|
25
|
+
|
|
26
|
+
NANOSECONDS_PER_DAY = 86400000000000
|
|
27
|
+
NANOSECONDS_PER_SECOND = 1000000000
|
|
28
|
+
NANOSECONDS_PER_DECISECOND = 100000000
|
|
29
|
+
NANOSECONDS_PER_CENTISECOND = 10000000
|
|
30
|
+
NANOSECONDS_PER_MILLISECOND = 1000000
|
|
31
|
+
NANOSECONDS_PER_DECIMILISECOND = 100000
|
|
32
|
+
NANOSECONDS_PER_MICROSECOND = 1000
|
|
33
|
+
|
|
34
|
+
PRECISION_1_DAY = "1d"
|
|
35
|
+
PRECISION_1_HOUR = "1h"
|
|
36
|
+
PRECISION_1_NANOSECOND = "1ns"
|
|
37
|
+
PRECISION_10_NANOSECONDS = "10s"
|
|
38
|
+
PRECISION_100_NANOSECONDS = "100ns"
|
|
39
|
+
PRECISION_1_MICROSECOND = "1us"
|
|
40
|
+
PRECISION_10_MICROSECONDS = "10us"
|
|
41
|
+
PRECISION_100_MICROSECONDS = "100us"
|
|
42
|
+
PRECISION_1_MILLISECOND = "1ms"
|
|
43
|
+
PRECISION_10_MILLISECONDS = "10ms"
|
|
44
|
+
PRECISION_100_MILLISECONDS = "100ms"
|
|
45
|
+
PRECISION_1_MINUTE = "1min"
|
|
46
|
+
PRECISION_1_SECOND = "1s"
|
|
47
|
+
PRECISION_2_SECONDS = "2s"
|
|
48
|
+
|
|
49
|
+
PRECISION_VALUES = frozenset(
|
|
50
|
+
[
|
|
51
|
+
PRECISION_1_DAY,
|
|
52
|
+
PRECISION_1_HOUR,
|
|
53
|
+
PRECISION_1_NANOSECOND,
|
|
54
|
+
PRECISION_10_NANOSECONDS,
|
|
55
|
+
PRECISION_100_NANOSECONDS,
|
|
56
|
+
PRECISION_1_MICROSECOND,
|
|
57
|
+
PRECISION_10_MICROSECONDS,
|
|
58
|
+
PRECISION_100_MICROSECONDS,
|
|
59
|
+
PRECISION_1_MILLISECOND,
|
|
60
|
+
PRECISION_10_MILLISECONDS,
|
|
61
|
+
PRECISION_100_MILLISECONDS,
|
|
62
|
+
PRECISION_1_MINUTE,
|
|
63
|
+
PRECISION_1_SECOND,
|
|
64
|
+
PRECISION_2_SECONDS,
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Create a days per century lookup table.
|
|
69
|
+
DAYS_PER_CENTURY = {}
|
|
70
|
+
for year in range(-10000, 10000, 100):
|
|
71
|
+
if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
|
|
72
|
+
number_of_days = 36525
|
|
73
|
+
else:
|
|
74
|
+
number_of_days = 36524
|
|
75
|
+
DAYS_PER_CENTURY[year] = number_of_days
|
|
76
|
+
|
|
77
|
+
# Create a days per year lookup table.
|
|
78
|
+
DAYS_PER_YEAR = {}
|
|
79
|
+
for year in range(-10000, 10000, 1):
|
|
80
|
+
if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
|
|
81
|
+
number_of_days = 366
|
|
82
|
+
else:
|
|
83
|
+
number_of_days = 365
|
|
84
|
+
DAYS_PER_YEAR[year] = number_of_days
|
|
85
|
+
|
|
86
|
+
# Create a days per year in POSIX epoch lookup table.
|
|
87
|
+
DAYS_PER_YEAR_IN_POSIX_EPOCH = {}
|
|
88
|
+
|
|
89
|
+
number_of_days = 0
|
|
90
|
+
for year in range(1969, -10000, -1):
|
|
91
|
+
number_of_days -= DAYS_PER_YEAR[year]
|
|
92
|
+
DAYS_PER_YEAR_IN_POSIX_EPOCH[year] = number_of_days
|
|
93
|
+
|
|
94
|
+
number_of_days = 0
|
|
95
|
+
for year in range(1970, 10000, 1):
|
|
96
|
+
DAYS_PER_YEAR_IN_POSIX_EPOCH[year] = number_of_days
|
|
97
|
+
number_of_days += DAYS_PER_YEAR[year]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Delphi TDateTime implementation."""
|
|
2
|
+
|
|
3
|
+
import decimal
|
|
4
|
+
|
|
5
|
+
from dfdatetime import definitions
|
|
6
|
+
from dfdatetime import factory
|
|
7
|
+
from dfdatetime import interface
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DelphiDateTimeEpoch(interface.DateTimeEpoch):
|
|
11
|
+
"""Delphi TDateTime epoch."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initializes a Delphi TDateTime epoch."""
|
|
15
|
+
super().__init__(1899, 12, 30)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DelphiDateTime(interface.DateTimeValues):
|
|
19
|
+
"""Delphi TDateTime timestamp.
|
|
20
|
+
|
|
21
|
+
The Delphi TDateTime timestamp is a floating point value that contains
|
|
22
|
+
the number of days since 1899-12-30 00:00:00 (also known as the epoch).
|
|
23
|
+
Negative values represent date and times predating the epoch.
|
|
24
|
+
|
|
25
|
+
The maximal correct date supported by TDateTime values is limited to:
|
|
26
|
+
9999-12-31 23:59:59.999
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# The difference between December 30, 1899 and January 1, 1970 in days.
|
|
33
|
+
_DELPHI_TO_POSIX_BASE = 25569
|
|
34
|
+
|
|
35
|
+
_EPOCH = DelphiDateTimeEpoch()
|
|
36
|
+
|
|
37
|
+
def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
|
|
38
|
+
"""Initializes a Delphi TDateTime timestamp.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
42
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
43
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
44
|
+
from UTC or None if not set.
|
|
45
|
+
timestamp (Optional[float]): Delphi TDateTime timestamp.
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(
|
|
48
|
+
precision=precision or definitions.PRECISION_1_MILLISECOND,
|
|
49
|
+
time_zone_offset=time_zone_offset,
|
|
50
|
+
)
|
|
51
|
+
self._timestamp = timestamp
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def timestamp(self):
|
|
55
|
+
"""float: Delphi TDateTime timestamp or None if not set."""
|
|
56
|
+
return self._timestamp
|
|
57
|
+
|
|
58
|
+
def _GetNormalizedTimestamp(self):
|
|
59
|
+
"""Retrieves the normalized timestamp.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
63
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
64
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
65
|
+
determined.
|
|
66
|
+
"""
|
|
67
|
+
if self._normalized_timestamp is None:
|
|
68
|
+
if self._timestamp is not None:
|
|
69
|
+
self._normalized_timestamp = (
|
|
70
|
+
decimal.Decimal(self._timestamp) - self._DELPHI_TO_POSIX_BASE
|
|
71
|
+
)
|
|
72
|
+
self._normalized_timestamp *= definitions.SECONDS_PER_DAY
|
|
73
|
+
|
|
74
|
+
if self._time_zone_offset:
|
|
75
|
+
self._normalized_timestamp -= self._time_zone_offset * 60
|
|
76
|
+
|
|
77
|
+
return self._normalized_timestamp
|
|
78
|
+
|
|
79
|
+
def CopyFromDateTimeString(self, time_string):
|
|
80
|
+
"""Copies a Delphi TDateTime timestamp from a string.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
time_string (str): date and time value formatted as:
|
|
84
|
+
YYYY-MM-DD hh:mm:ss.######[+-]##:##
|
|
85
|
+
|
|
86
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
87
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
88
|
+
fraction and time zone offset are optional. The default time zone
|
|
89
|
+
is UTC.
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ValueError: if the time string is invalid or not supported.
|
|
93
|
+
"""
|
|
94
|
+
date_time_values = self._CopyDateTimeFromString(time_string)
|
|
95
|
+
|
|
96
|
+
year = date_time_values.get("year", 0)
|
|
97
|
+
month = date_time_values.get("month", 0)
|
|
98
|
+
day_of_month = date_time_values.get("day_of_month", 0)
|
|
99
|
+
hours = date_time_values.get("hours", 0)
|
|
100
|
+
minutes = date_time_values.get("minutes", 0)
|
|
101
|
+
seconds = date_time_values.get("seconds", 0)
|
|
102
|
+
nanoseconds = date_time_values.get("nanoseconds", 0)
|
|
103
|
+
time_zone_offset = date_time_values.get("time_zone_offset")
|
|
104
|
+
|
|
105
|
+
if year > 9999:
|
|
106
|
+
raise ValueError(f"Unsupported year value: {year:d}.")
|
|
107
|
+
|
|
108
|
+
timestamp = self._GetNumberOfSecondsFromElements(
|
|
109
|
+
year, month, day_of_month, hours, minutes, seconds
|
|
110
|
+
)
|
|
111
|
+
timestamp = float(timestamp) / definitions.SECONDS_PER_DAY
|
|
112
|
+
timestamp += self._DELPHI_TO_POSIX_BASE
|
|
113
|
+
timestamp += float(nanoseconds) / definitions.NANOSECONDS_PER_DAY
|
|
114
|
+
|
|
115
|
+
self._normalized_timestamp = None
|
|
116
|
+
self._timestamp = timestamp
|
|
117
|
+
self._time_zone_offset = time_zone_offset
|
|
118
|
+
|
|
119
|
+
def CopyToDateTimeString(self):
|
|
120
|
+
"""Copies the Delphi TDateTime timestamp to a date and time string.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
|
|
124
|
+
None if the timestamp is missing.
|
|
125
|
+
"""
|
|
126
|
+
if self._timestamp is None:
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
number_of_seconds = self._timestamp * definitions.SECONDS_PER_DAY
|
|
130
|
+
|
|
131
|
+
number_of_days, hours, minutes, seconds = self._GetTimeValues(
|
|
132
|
+
int(number_of_seconds)
|
|
133
|
+
)
|
|
134
|
+
# The maximum date supported by TDateTime values is limited to:
|
|
135
|
+
# 9999-12-31 23:59:59.999 (approximate 2958465 days since epoch).
|
|
136
|
+
# The minimum date is unknown hence assuming it is limited to:
|
|
137
|
+
# 0001-01-01 00:00:00.000 (approximate -693593 days since epoch).
|
|
138
|
+
|
|
139
|
+
if number_of_days < -693593 or number_of_days > 2958465:
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
year, month, day_of_month = self._GetDateValuesWithEpoch(
|
|
143
|
+
number_of_days, self._EPOCH
|
|
144
|
+
)
|
|
145
|
+
microseconds = int(
|
|
146
|
+
(number_of_seconds % 1) * definitions.MICROSECONDS_PER_SECOND
|
|
147
|
+
)
|
|
148
|
+
return (
|
|
149
|
+
f"{year:04d}-{month:02d}-{day_of_month:02d} "
|
|
150
|
+
f"{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def CopyToSerializableDict(self):
|
|
154
|
+
"""Copies the date time value to a serializable dictionary.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
dict[str, object]: serializable dictionary.
|
|
158
|
+
"""
|
|
159
|
+
serializable_dict = self._CreateSerializableDict()
|
|
160
|
+
|
|
161
|
+
serializable_dict["timestamp"] = self._timestamp
|
|
162
|
+
|
|
163
|
+
return serializable_dict
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
factory.Factory.RegisterDateTimeValues(DelphiDateTime)
|