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.
@@ -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
@@ -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)