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,157 @@
|
|
|
1
|
+
""".NET DateTime 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 DotNetDateTimeEpoch(interface.DateTimeEpoch):
|
|
11
|
+
""".NET DateTime epoch."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initializes a .NET DateTime epoch."""
|
|
15
|
+
super().__init__(1, 1, 1)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DotNetDateTime(interface.DateTimeValues):
|
|
19
|
+
""".NET DateTime ticks.
|
|
20
|
+
|
|
21
|
+
The .NET DateTime timestamp is a 64-bit signed integer that contains the date and
|
|
22
|
+
time as the number of 100 nanoseconds since 12:00 AM January 1, year 1 A.D. in the
|
|
23
|
+
proleptic Gregorian Calendar.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
_EPOCH = DotNetDateTimeEpoch()
|
|
27
|
+
|
|
28
|
+
# The difference between January 1, 1 and January 1, 1970 in seconds.
|
|
29
|
+
_DOTNET_TO_POSIX_BASE = (
|
|
30
|
+
(1969 * 365) + (1969 // 4) - (1969 // 100) + (1969 // 400)
|
|
31
|
+
) * definitions.SECONDS_PER_DAY
|
|
32
|
+
|
|
33
|
+
def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
|
|
34
|
+
"""Initializes a .NET DateTime timestamp.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
38
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
39
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
40
|
+
from UTC or None if not set.
|
|
41
|
+
timestamp (Optional[int]): .NET DateTime ticks.
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(
|
|
44
|
+
precision=precision or definitions.PRECISION_100_NANOSECONDS,
|
|
45
|
+
time_zone_offset=time_zone_offset,
|
|
46
|
+
)
|
|
47
|
+
self._timestamp = timestamp or 0
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def timestamp(self):
|
|
51
|
+
"""int: .NET DateTime timestamp or None if not set."""
|
|
52
|
+
return self._timestamp
|
|
53
|
+
|
|
54
|
+
def _GetNormalizedTimestamp(self):
|
|
55
|
+
"""Retrieves the normalized timestamp.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
59
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
60
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
61
|
+
determined.
|
|
62
|
+
"""
|
|
63
|
+
if self._normalized_timestamp is None:
|
|
64
|
+
if self._timestamp is not None:
|
|
65
|
+
self._normalized_timestamp = (
|
|
66
|
+
decimal.Decimal(self._timestamp) / self._100_NANOSECONDS_PER_SECOND
|
|
67
|
+
)
|
|
68
|
+
self._normalized_timestamp -= self._DOTNET_TO_POSIX_BASE
|
|
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 .NET DateTime timestamp from a 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
|
+
if year > 9999:
|
|
102
|
+
raise ValueError(f"Unsupported year value: {year:d}.")
|
|
103
|
+
|
|
104
|
+
nanoseconds, _ = divmod(nanoseconds, 100)
|
|
105
|
+
|
|
106
|
+
timestamp = self._GetNumberOfSecondsFromElements(
|
|
107
|
+
year, month, day_of_month, hours, minutes, seconds
|
|
108
|
+
)
|
|
109
|
+
timestamp += self._DOTNET_TO_POSIX_BASE
|
|
110
|
+
timestamp *= self._100_NANOSECONDS_PER_SECOND
|
|
111
|
+
timestamp += nanoseconds
|
|
112
|
+
|
|
113
|
+
self._normalized_timestamp = None
|
|
114
|
+
self._timestamp = timestamp
|
|
115
|
+
self._time_zone_offset = time_zone_offset
|
|
116
|
+
|
|
117
|
+
def CopyToDateTimeString(self):
|
|
118
|
+
"""Copies the .NET DateTime timestamp to a date and time string.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
|
|
122
|
+
None if the timestamp is missing.
|
|
123
|
+
"""
|
|
124
|
+
if (
|
|
125
|
+
self._timestamp is None
|
|
126
|
+
or self._timestamp < 0
|
|
127
|
+
or self._timestamp > self._UINT64_MAX
|
|
128
|
+
):
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
timestamp, fraction_of_second = divmod(
|
|
132
|
+
self._timestamp, self._100_NANOSECONDS_PER_SECOND
|
|
133
|
+
)
|
|
134
|
+
number_of_days, hours, minutes, seconds = self._GetTimeValues(timestamp)
|
|
135
|
+
|
|
136
|
+
year, month, day_of_month = self._GetDateValuesWithEpoch(
|
|
137
|
+
number_of_days, self._EPOCH
|
|
138
|
+
)
|
|
139
|
+
return (
|
|
140
|
+
f"{year:04d}-{month:02d}-{day_of_month:02d} "
|
|
141
|
+
f"{hours:02d}:{minutes:02d}:{seconds:02d}.{fraction_of_second:07d}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def CopyToSerializableDict(self):
|
|
145
|
+
"""Copies the date time value to a serializable dictionary.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
dict[str, object]: serializable dictionary.
|
|
149
|
+
"""
|
|
150
|
+
serializable_dict = self._CreateSerializableDict()
|
|
151
|
+
|
|
152
|
+
serializable_dict["timestamp"] = self._timestamp
|
|
153
|
+
|
|
154
|
+
return serializable_dict
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
factory.Factory.RegisterDateTimeValues(DotNetDateTime)
|
dfdatetime/factory.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""The date and time values factory."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Factory:
|
|
5
|
+
"""Date and time values factory."""
|
|
6
|
+
|
|
7
|
+
_date_time_values_types = {}
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
def DeregisterDateTimeValues(cls, date_time_values_type):
|
|
11
|
+
"""Deregisters a date and time values type.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
date_time_values_type (type): date and time values type.
|
|
15
|
+
|
|
16
|
+
Raises:
|
|
17
|
+
KeyError: if date and time values type is not registered.
|
|
18
|
+
"""
|
|
19
|
+
class_name = date_time_values_type.__name__
|
|
20
|
+
if class_name not in cls._date_time_values_types:
|
|
21
|
+
raise KeyError(f"Date and time values type: {class_name:s} not set.")
|
|
22
|
+
|
|
23
|
+
del cls._date_time_values_types[class_name]
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def NewDateTimeValues(cls, class_name, **kwargs):
|
|
27
|
+
"""Creates a new date and time values for the specific type indicator.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
class_name (str): type indicator.
|
|
31
|
+
kwargs (dict): keyword arguments depending on the date and time values.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
DateTimeValues: date and time values.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
KeyError: if date and time values is not registered.
|
|
38
|
+
"""
|
|
39
|
+
if class_name not in cls._date_time_values_types:
|
|
40
|
+
raise KeyError(f"Date and time values type: {class_name:s} not set.")
|
|
41
|
+
|
|
42
|
+
date_time_values_type = cls._date_time_values_types[class_name]
|
|
43
|
+
return date_time_values_type(**kwargs)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def RegisterDateTimeValues(cls, date_time_values_type):
|
|
47
|
+
"""Registers a date and time values type.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
date_time_values_type (type): date and time values type.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
KeyError: if date and time values is already registered.
|
|
54
|
+
"""
|
|
55
|
+
class_name = date_time_values_type.__name__
|
|
56
|
+
if class_name in cls._date_time_values_types:
|
|
57
|
+
raise KeyError(f"Date and time values type: {class_name:s} already set.")
|
|
58
|
+
|
|
59
|
+
cls._date_time_values_types[class_name] = date_time_values_type
|
dfdatetime/fake_time.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Fake timestamp implementation."""
|
|
2
|
+
|
|
3
|
+
import decimal
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from dfdatetime import definitions
|
|
7
|
+
from dfdatetime import interface
|
|
8
|
+
from dfdatetime import posix_time
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FakeTime(interface.DateTimeValues):
|
|
12
|
+
"""Fake timestamp.
|
|
13
|
+
|
|
14
|
+
The fake timestamp is intended for testing purposes. On initialization
|
|
15
|
+
it contains the current time in UTC in microsecond precision.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
_EPOCH = posix_time.PosixTimeEpoch()
|
|
22
|
+
|
|
23
|
+
def __init__(self, precision=None, time_zone_offset=None):
|
|
24
|
+
"""Initializes a fake timestamp.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
28
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
29
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
30
|
+
from UTC or None if not set.
|
|
31
|
+
"""
|
|
32
|
+
# Note that time.time() and divmod return floating point values.
|
|
33
|
+
timestamp, fraction_of_second = divmod(time.time(), 1)
|
|
34
|
+
|
|
35
|
+
super().__init__(
|
|
36
|
+
precision=precision or definitions.PRECISION_1_MICROSECOND,
|
|
37
|
+
time_zone_offset=time_zone_offset,
|
|
38
|
+
)
|
|
39
|
+
self._microseconds = int(
|
|
40
|
+
fraction_of_second * definitions.MICROSECONDS_PER_SECOND
|
|
41
|
+
)
|
|
42
|
+
self._number_of_seconds = int(timestamp)
|
|
43
|
+
|
|
44
|
+
def _GetNormalizedTimestamp(self):
|
|
45
|
+
"""Retrieves the normalized timestamp.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
49
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
50
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
51
|
+
determined.
|
|
52
|
+
"""
|
|
53
|
+
if self._normalized_timestamp is None:
|
|
54
|
+
if self._number_of_seconds is not None:
|
|
55
|
+
self._normalized_timestamp = (
|
|
56
|
+
decimal.Decimal(self._microseconds)
|
|
57
|
+
/ definitions.MICROSECONDS_PER_SECOND
|
|
58
|
+
)
|
|
59
|
+
self._normalized_timestamp += decimal.Decimal(self._number_of_seconds)
|
|
60
|
+
|
|
61
|
+
if self._time_zone_offset:
|
|
62
|
+
self._normalized_timestamp -= self._time_zone_offset * 60
|
|
63
|
+
|
|
64
|
+
return self._normalized_timestamp
|
|
65
|
+
|
|
66
|
+
def CopyFromDateTimeString(self, time_string):
|
|
67
|
+
"""Copies a fake timestamp from a date and time string.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
time_string (str): date and time value formatted as:
|
|
71
|
+
YYYY-MM-DD hh:mm:ss.######[+-]##:##
|
|
72
|
+
|
|
73
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
74
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
75
|
+
fraction and time zone offset are optional. The default time zone
|
|
76
|
+
is UTC.
|
|
77
|
+
"""
|
|
78
|
+
date_time_values = self._CopyDateTimeFromString(time_string)
|
|
79
|
+
|
|
80
|
+
year = date_time_values.get("year", 0)
|
|
81
|
+
month = date_time_values.get("month", 0)
|
|
82
|
+
day_of_month = date_time_values.get("day_of_month", 0)
|
|
83
|
+
hours = date_time_values.get("hours", 0)
|
|
84
|
+
minutes = date_time_values.get("minutes", 0)
|
|
85
|
+
seconds = date_time_values.get("seconds", 0)
|
|
86
|
+
nanoseconds = date_time_values.get("nanoseconds")
|
|
87
|
+
time_zone_offset = date_time_values.get("time_zone_offset")
|
|
88
|
+
|
|
89
|
+
self._normalized_timestamp = None
|
|
90
|
+
self._number_of_seconds = self._GetNumberOfSecondsFromElements(
|
|
91
|
+
year, month, day_of_month, hours, minutes, seconds
|
|
92
|
+
)
|
|
93
|
+
if nanoseconds is None:
|
|
94
|
+
self._microseconds = None
|
|
95
|
+
else:
|
|
96
|
+
self._microseconds, _ = divmod(nanoseconds, 1000)
|
|
97
|
+
|
|
98
|
+
self._time_zone_offset = time_zone_offset
|
|
99
|
+
|
|
100
|
+
def CopyToDateTimeString(self):
|
|
101
|
+
"""Copies the fake timestamp to a date and time string.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss" or
|
|
105
|
+
"YYYY-MM-DD hh:mm:ss.######" or None if the number of seconds
|
|
106
|
+
is missing.
|
|
107
|
+
"""
|
|
108
|
+
if self._number_of_seconds is None:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
number_of_days, hours, minutes, seconds = self._GetTimeValues(
|
|
112
|
+
self._number_of_seconds
|
|
113
|
+
)
|
|
114
|
+
year, month, day_of_month = self._GetDateValuesWithEpoch(
|
|
115
|
+
number_of_days, self._EPOCH
|
|
116
|
+
)
|
|
117
|
+
date_time_string = (
|
|
118
|
+
f"{year:04d}-{month:02d}-{day_of_month:02d} "
|
|
119
|
+
f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
120
|
+
)
|
|
121
|
+
if self._microseconds is not None:
|
|
122
|
+
date_time_string = ".".join([date_time_string, f"{self._microseconds:06d}"])
|
|
123
|
+
|
|
124
|
+
return date_time_string
|
|
125
|
+
|
|
126
|
+
def CopyToSerializableDict(self):
|
|
127
|
+
"""Copies the date time value to a serializable dictionary.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
dict[str, object]: serializable dictionary.
|
|
131
|
+
"""
|
|
132
|
+
normalized_timestamp = self._GetNormalizedTimestamp()
|
|
133
|
+
|
|
134
|
+
serializable_dict = self._CreateSerializableDict()
|
|
135
|
+
|
|
136
|
+
serializable_dict["timestamp"] = int(
|
|
137
|
+
normalized_timestamp * definitions.MICROSECONDS_PER_SECOND
|
|
138
|
+
)
|
|
139
|
+
return serializable_dict
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""FAT date time 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 FATDateTimeEpoch(interface.DateTimeEpoch):
|
|
11
|
+
"""FAT date time time epoch."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initializes a FAT date time epoch."""
|
|
15
|
+
super().__init__(1980, 1, 1)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FATDateTime(interface.DateTimeValues):
|
|
19
|
+
"""FAT date time.
|
|
20
|
+
|
|
21
|
+
The FAT date time is mainly used in DOS/Windows file formats and FAT.
|
|
22
|
+
|
|
23
|
+
The FAT date and time is a 32-bit value containing two 16-bit values:
|
|
24
|
+
* The date (lower 16-bit).
|
|
25
|
+
* bits 0 - 4: day of month, where 1 represents the first day
|
|
26
|
+
* bits 5 - 8: month of year, where 1 represent January
|
|
27
|
+
* bits 9 - 15: year since 1980
|
|
28
|
+
* The time of day (upper 16-bit).
|
|
29
|
+
* bits 0 - 4: seconds (in 2 second intervals)
|
|
30
|
+
* bits 5 - 10: minutes
|
|
31
|
+
* bits 11 - 15: hours
|
|
32
|
+
|
|
33
|
+
The FAT date time has no time zone information and is typically stored
|
|
34
|
+
in the local time of the computer.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
_EPOCH = FATDateTimeEpoch()
|
|
41
|
+
|
|
42
|
+
# The difference between January 1, 1980 and January 1, 1970 in seconds.
|
|
43
|
+
_FAT_DATE_TO_POSIX_BASE = 315532800
|
|
44
|
+
|
|
45
|
+
def __init__(self, fat_date_time=None, precision=None, time_zone_offset=None):
|
|
46
|
+
"""Initializes a FAT date time.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
fat_date_time (Optional[int]): FAT date time.
|
|
50
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
51
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
52
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
53
|
+
from UTC or None if not set.
|
|
54
|
+
"""
|
|
55
|
+
super().__init__(
|
|
56
|
+
precision=precision or definitions.PRECISION_2_SECONDS,
|
|
57
|
+
time_zone_offset=time_zone_offset,
|
|
58
|
+
)
|
|
59
|
+
self._fat_date_time = fat_date_time
|
|
60
|
+
self._number_of_seconds = None
|
|
61
|
+
|
|
62
|
+
if fat_date_time is not None:
|
|
63
|
+
self._number_of_seconds = self._GetNumberOfSeconds(fat_date_time)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def fat_date_time(self):
|
|
67
|
+
"""int: FAT date time or None if not set."""
|
|
68
|
+
return self._fat_date_time
|
|
69
|
+
|
|
70
|
+
def _GetNormalizedTimestamp(self):
|
|
71
|
+
"""Retrieves the normalized timestamp.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
75
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
76
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
77
|
+
determined.
|
|
78
|
+
"""
|
|
79
|
+
if self._normalized_timestamp is None:
|
|
80
|
+
if self._number_of_seconds is not None and self._number_of_seconds >= 0:
|
|
81
|
+
self._normalized_timestamp = (
|
|
82
|
+
decimal.Decimal(self._number_of_seconds)
|
|
83
|
+
+ self._FAT_DATE_TO_POSIX_BASE
|
|
84
|
+
)
|
|
85
|
+
if self._time_zone_offset:
|
|
86
|
+
self._normalized_timestamp -= self._time_zone_offset * 60
|
|
87
|
+
|
|
88
|
+
return self._normalized_timestamp
|
|
89
|
+
|
|
90
|
+
def _GetNumberOfSeconds(self, fat_date_time):
|
|
91
|
+
"""Retrieves the number of seconds from a FAT date time.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
fat_date_time (int): FAT date time.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
int: number of seconds since January 1, 1980 00:00:00.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: if the month, day of month, hours, minutes or seconds
|
|
101
|
+
value is out of bounds.
|
|
102
|
+
"""
|
|
103
|
+
day_of_month = fat_date_time & 0x1F
|
|
104
|
+
month = (fat_date_time >> 5) & 0x0F
|
|
105
|
+
year = (fat_date_time >> 9) & 0x7F
|
|
106
|
+
|
|
107
|
+
days_per_month = self._GetDaysPerMonth(year, month)
|
|
108
|
+
if day_of_month < 1 or day_of_month > days_per_month:
|
|
109
|
+
raise ValueError("Day of month value out of bounds.")
|
|
110
|
+
|
|
111
|
+
number_of_days = self._GetDayOfYear(1980 + year, month, day_of_month)
|
|
112
|
+
number_of_days -= 1
|
|
113
|
+
for past_year in range(0, year):
|
|
114
|
+
number_of_days += self._GetNumberOfDaysInYear(past_year)
|
|
115
|
+
|
|
116
|
+
fat_date_time >>= 16
|
|
117
|
+
|
|
118
|
+
seconds = (fat_date_time & 0x1F) * 2
|
|
119
|
+
minutes = (fat_date_time >> 5) & 0x3F
|
|
120
|
+
hours = (fat_date_time >> 11) & 0x1F
|
|
121
|
+
|
|
122
|
+
if hours not in range(0, 24):
|
|
123
|
+
raise ValueError("Hours value out of bounds.")
|
|
124
|
+
|
|
125
|
+
if minutes not in range(0, 60):
|
|
126
|
+
raise ValueError("Minutes value out of bounds.")
|
|
127
|
+
|
|
128
|
+
if seconds not in range(0, 60):
|
|
129
|
+
raise ValueError("Seconds value out of bounds.")
|
|
130
|
+
|
|
131
|
+
number_of_seconds = (((hours * 60) + minutes) * 60) + seconds
|
|
132
|
+
number_of_seconds += number_of_days * definitions.SECONDS_PER_DAY
|
|
133
|
+
return number_of_seconds
|
|
134
|
+
|
|
135
|
+
def CopyFromDateTimeString(self, time_string):
|
|
136
|
+
"""Copies a FAT date time from a date and time string.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
time_string (str): date and time value formatted as:
|
|
140
|
+
YYYY-MM-DD hh:mm:ss.######[+-]##:##
|
|
141
|
+
|
|
142
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
143
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
144
|
+
fraction and time zone offset are optional. The default time zone
|
|
145
|
+
is UTC.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
ValueError: if the time string is invalid or not supported.
|
|
149
|
+
"""
|
|
150
|
+
date_time_values = self._CopyDateTimeFromString(time_string)
|
|
151
|
+
|
|
152
|
+
year = date_time_values.get("year", 0)
|
|
153
|
+
month = date_time_values.get("month", 0)
|
|
154
|
+
day_of_month = date_time_values.get("day_of_month", 0)
|
|
155
|
+
hours = date_time_values.get("hours", 0)
|
|
156
|
+
minutes = date_time_values.get("minutes", 0)
|
|
157
|
+
seconds = date_time_values.get("seconds", 0)
|
|
158
|
+
time_zone_offset = date_time_values.get("time_zone_offset")
|
|
159
|
+
|
|
160
|
+
if year < 1980 or year > (1980 + 0x7F):
|
|
161
|
+
raise ValueError(f"Year value not supported: {year!s}.")
|
|
162
|
+
|
|
163
|
+
self._normalized_timestamp = None
|
|
164
|
+
self._number_of_seconds = self._GetNumberOfSecondsFromElements(
|
|
165
|
+
year, month, day_of_month, hours, minutes, seconds
|
|
166
|
+
)
|
|
167
|
+
self._number_of_seconds -= self._FAT_DATE_TO_POSIX_BASE
|
|
168
|
+
self._time_zone_offset = time_zone_offset
|
|
169
|
+
|
|
170
|
+
def CopyToDateTimeString(self):
|
|
171
|
+
"""Copies the FAT date time to a date and time string.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss" or None
|
|
175
|
+
if number of seconds is missing.
|
|
176
|
+
"""
|
|
177
|
+
if self._number_of_seconds is None:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
number_of_days, hours, minutes, seconds = self._GetTimeValues(
|
|
181
|
+
self._number_of_seconds
|
|
182
|
+
)
|
|
183
|
+
year, month, day_of_month = self._GetDateValuesWithEpoch(
|
|
184
|
+
number_of_days, self._EPOCH
|
|
185
|
+
)
|
|
186
|
+
return (
|
|
187
|
+
f"{year:04d}-{month:02d}-{day_of_month:02d} "
|
|
188
|
+
f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def CopyToSerializableDict(self):
|
|
192
|
+
"""Copies the date time value to a serializable dictionary.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
dict[str, object]: serializable dictionary.
|
|
196
|
+
"""
|
|
197
|
+
serializable_dict = self._CreateSerializableDict()
|
|
198
|
+
|
|
199
|
+
serializable_dict["fat_date_time"] = self._fat_date_time
|
|
200
|
+
|
|
201
|
+
return serializable_dict
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class FATTimestamp(interface.DateTimeValues):
|
|
205
|
+
"""FAT timestamp.
|
|
206
|
+
|
|
207
|
+
The FAT timestamp is an unsigned integer that contains the number of
|
|
208
|
+
10 milli seconds intervals since 1980-01-01 00:00:00 (also known as
|
|
209
|
+
the FAT date time epoch).
|
|
210
|
+
|
|
211
|
+
Attributes:
|
|
212
|
+
is_local_time (bool): True if the date and time value is in local time.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
_EPOCH = FATDateTimeEpoch()
|
|
216
|
+
|
|
217
|
+
# The difference between January 1, 1980 and January 1, 1970 in seconds.
|
|
218
|
+
_FAT_DATE_TO_POSIX_BASE = 315532800
|
|
219
|
+
|
|
220
|
+
def __init__(self, precision=None, time_zone_offset=None, timestamp=None):
|
|
221
|
+
"""Initializes a FAT timestamp.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
precision (Optional[str]): precision of the date and time value, which
|
|
225
|
+
should be one of the PRECISION_VALUES in definitions.
|
|
226
|
+
time_zone_offset (Optional[int]): time zone offset in number of minutes
|
|
227
|
+
from UTC or None if not set.
|
|
228
|
+
timestamp (Optional[int]): FAT timestamp.
|
|
229
|
+
"""
|
|
230
|
+
super().__init__(
|
|
231
|
+
precision=precision or definitions.PRECISION_10_MILLISECONDS,
|
|
232
|
+
time_zone_offset=time_zone_offset,
|
|
233
|
+
)
|
|
234
|
+
self._timestamp = timestamp
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def timestamp(self):
|
|
238
|
+
"""int: FAT timestamp or None if not set."""
|
|
239
|
+
return self._timestamp
|
|
240
|
+
|
|
241
|
+
def _GetNormalizedTimestamp(self):
|
|
242
|
+
"""Retrieves the normalized timestamp.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
decimal.Decimal: normalized timestamp, which contains the number of
|
|
246
|
+
seconds since January 1, 1970 00:00:00 and a fraction of second used
|
|
247
|
+
for increased precision, or None if the normalized timestamp cannot be
|
|
248
|
+
determined.
|
|
249
|
+
"""
|
|
250
|
+
if self._normalized_timestamp is None:
|
|
251
|
+
if self._timestamp is not None:
|
|
252
|
+
self._normalized_timestamp = (
|
|
253
|
+
decimal.Decimal(self._timestamp) / 100
|
|
254
|
+
) + self._FAT_DATE_TO_POSIX_BASE
|
|
255
|
+
|
|
256
|
+
if self._time_zone_offset:
|
|
257
|
+
self._normalized_timestamp -= self._time_zone_offset * 60
|
|
258
|
+
|
|
259
|
+
return self._normalized_timestamp
|
|
260
|
+
|
|
261
|
+
def CopyFromDateTimeString(self, time_string):
|
|
262
|
+
"""Copies a FAT timestamp from a date and time string.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
time_string (str): date and time value formatted as:
|
|
266
|
+
YYYY-MM-DD hh:mm:ss.######[+-]##:##
|
|
267
|
+
|
|
268
|
+
Where # are numeric digits ranging from 0 to 9 and the seconds
|
|
269
|
+
fraction can be either 3, 6 or 9 digits. The time of day, seconds
|
|
270
|
+
fraction and time zone offset are optional. The default time zone
|
|
271
|
+
is UTC.
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
ValueError: if the time string is invalid or not supported.
|
|
275
|
+
"""
|
|
276
|
+
date_time_values = self._CopyDateTimeFromString(time_string)
|
|
277
|
+
|
|
278
|
+
year = date_time_values.get("year", 0)
|
|
279
|
+
month = date_time_values.get("month", 0)
|
|
280
|
+
day_of_month = date_time_values.get("day_of_month", 0)
|
|
281
|
+
hours = date_time_values.get("hours", 0)
|
|
282
|
+
minutes = date_time_values.get("minutes", 0)
|
|
283
|
+
seconds = date_time_values.get("seconds", 0)
|
|
284
|
+
nanoseconds = date_time_values.get("nanoseconds", 0)
|
|
285
|
+
time_zone_offset = date_time_values.get("time_zone_offset")
|
|
286
|
+
|
|
287
|
+
if year < 1980 or year > (1980 + 0x7F):
|
|
288
|
+
raise ValueError(f"Year value not supported: {year!s}.")
|
|
289
|
+
|
|
290
|
+
milliseconds, _ = divmod(nanoseconds, 10000000)
|
|
291
|
+
|
|
292
|
+
timestamp = self._GetNumberOfSecondsFromElements(
|
|
293
|
+
year, month, day_of_month, hours, minutes, seconds
|
|
294
|
+
)
|
|
295
|
+
timestamp -= self._FAT_DATE_TO_POSIX_BASE
|
|
296
|
+
timestamp *= 100
|
|
297
|
+
timestamp += milliseconds
|
|
298
|
+
|
|
299
|
+
self._timestamp = timestamp
|
|
300
|
+
self._time_zone_offset = time_zone_offset
|
|
301
|
+
|
|
302
|
+
def CopyToDateTimeString(self):
|
|
303
|
+
"""Copies the FAT timestamp to a date and time string.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
|
|
307
|
+
None if the timestamp is missing.
|
|
308
|
+
"""
|
|
309
|
+
if self._timestamp is None:
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
timestamp, milliseconds = divmod(self._timestamp, 100)
|
|
313
|
+
number_of_days, hours, minutes, seconds = self._GetTimeValues(timestamp)
|
|
314
|
+
|
|
315
|
+
year, month, day_of_month = self._GetDateValuesWithEpoch(
|
|
316
|
+
number_of_days, self._EPOCH
|
|
317
|
+
)
|
|
318
|
+
return (
|
|
319
|
+
f"{year:04d}-{month:02d}-{day_of_month:02d} "
|
|
320
|
+
f"{hours:02d}:{minutes:02d}:{seconds:02d}.{milliseconds:02d}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def CopyToSerializableDict(self):
|
|
324
|
+
"""Copies the date time value to a serializable dictionary.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
dict[str, object]: serializable dictionary.
|
|
328
|
+
"""
|
|
329
|
+
serializable_dict = self._CreateSerializableDict()
|
|
330
|
+
|
|
331
|
+
serializable_dict["timestamp"] = self._timestamp
|
|
332
|
+
|
|
333
|
+
return serializable_dict
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
factory.Factory.RegisterDateTimeValues(FATDateTime)
|
|
337
|
+
factory.Factory.RegisterDateTimeValues(FATTimestamp)
|