1# -*- coding: utf-8 -*-
2"""POSIX timestamp implementation."""
3
4import decimal
5
6from dfdatetime import definitions
7from dfdatetime import factory
8from dfdatetime import interface
9
10
11class PosixTimeEpoch(interface.DateTimeEpoch):
12  """POSIX time epoch."""
13
14  def __init__(self):
15    """Initializes a POSIX time epoch."""
16    super(PosixTimeEpoch, self).__init__(1970, 1, 1)
17
18
19class PosixTime(interface.DateTimeValues):
20  """POSIX timestamp.
21
22  The POSIX timestamp is a signed integer that contains the number of
23  seconds since 1970-01-01 00:00:00 (also known as the POSIX epoch).
24  Negative values represent date and times predating the POSIX epoch.
25
26  The POSIX timestamp was initially 32-bit though 64-bit variants
27  are known to be used.
28
29  Attributes:
30    is_local_time (bool): True if the date and time value is in local time.
31  """
32
33  _EPOCH = PosixTimeEpoch()
34
35  def __init__(self, time_zone_offset=None, timestamp=None):
36    """Initializes a POSIX timestamp.
37
38    Args:
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]): POSIX timestamp.
42    """
43    super(PosixTime, self).__init__(time_zone_offset=time_zone_offset)
44    self._precision = definitions.PRECISION_1_SECOND
45    self._timestamp = timestamp
46
47  @property
48  def timestamp(self):
49    """int: POSIX timestamp or None if not set."""
50    return self._timestamp
51
52  def _GetNormalizedTimestamp(self):
53    """Retrieves the normalized timestamp.
54
55    Returns:
56      decimal.Decimal: normalized timestamp, which contains the number of
57          seconds since January 1, 1970 00:00:00 and a fraction of second used
58          for increased precision, or None if the normalized timestamp cannot be
59          determined.
60    """
61    if self._normalized_timestamp is None:
62      if self._timestamp is not None:
63        self._normalized_timestamp = decimal.Decimal(self._timestamp)
64
65        if self._time_zone_offset:
66          self._normalized_timestamp -= self._time_zone_offset * 60
67
68    return self._normalized_timestamp
69
70  def CopyFromDateTimeString(self, time_string):
71    """Copies a POSIX timestamp from a date and time string.
72
73    Args:
74      time_string (str): date and time value formatted as:
75          YYYY-MM-DD hh:mm:ss.######[+-]##:##
76
77          Where # are numeric digits ranging from 0 to 9 and the seconds
78          fraction can be either 3 or 6 digits. The time of day, seconds
79          fraction and time zone offset are optional. The default time zone
80          is UTC.
81    """
82    date_time_values = self._CopyDateTimeFromString(time_string)
83
84    year = date_time_values.get('year', 0)
85    month = date_time_values.get('month', 0)
86    day_of_month = date_time_values.get('day_of_month', 0)
87    hours = date_time_values.get('hours', 0)
88    minutes = date_time_values.get('minutes', 0)
89    seconds = date_time_values.get('seconds', 0)
90    time_zone_offset = date_time_values.get('time_zone_offset', 0)
91
92    self._timestamp = self._GetNumberOfSecondsFromElements(
93        year, month, day_of_month, hours, minutes, seconds)
94    self._time_zone_offset = time_zone_offset
95
96  def CopyToDateTimeString(self):
97    """Copies the POSIX timestamp to a date and time string.
98
99    Returns:
100      str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss" or None
101          if the timestamp is missing.
102    """
103    if self._timestamp is None:
104      return None
105
106    number_of_days, hours, minutes, seconds = self._GetTimeValues(
107        self._timestamp)
108
109    year, month, day_of_month = self._GetDateValuesWithEpoch(
110        number_of_days, self._EPOCH)
111
112    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'.format(
113        year, month, day_of_month, hours, minutes, seconds)
114
115
116class PosixTimeInMilliseconds(interface.DateTimeValues):
117  """POSIX timestamp in milliseconds.
118
119  Variant of the POSIX timestamp in milliseconds.
120
121  Attributes:
122    is_local_time (bool): True if the date and time value is in local time.
123  """
124
125  _EPOCH = PosixTimeEpoch()
126
127  def __init__(self, time_zone_offset=None, timestamp=None):
128    """Initializes a POSIX timestamp in milliseconds.
129
130    Args:
131      time_zone_offset (Optional[int]): time zone offset in number of minutes
132          from UTC or None if not set.
133      timestamp (Optional[int]): POSIX timestamp in milliseconds.
134    """
135    super(PosixTimeInMilliseconds, self).__init__(
136        time_zone_offset=time_zone_offset)
137    self._precision = definitions.PRECISION_1_MILLISECOND
138    self._timestamp = timestamp
139
140  @property
141  def timestamp(self):
142    """int: POSIX timestamp in milliseconds or None if not set."""
143    return self._timestamp
144
145  def _GetNormalizedTimestamp(self):
146    """Retrieves the normalized timestamp.
147
148    Returns:
149      decimal.Decimal: normalized timestamp, which contains the number of
150          seconds since January 1, 1970 00:00:00 and a fraction of second used
151          for increased precision, or None if the normalized timestamp cannot be
152          determined.
153    """
154    if self._normalized_timestamp is None:
155      if self._timestamp is not None:
156        self._normalized_timestamp = (
157            decimal.Decimal(self._timestamp) /
158            definitions.MILLISECONDS_PER_SECOND)
159
160        if self._time_zone_offset:
161          self._normalized_timestamp -= self._time_zone_offset * 60
162
163    return self._normalized_timestamp
164
165  def CopyFromDateTimeString(self, time_string):
166    """Copies a POSIX timestamp from a date and time string.
167
168    Args:
169      time_string (str): date and time value formatted as:
170          YYYY-MM-DD hh:mm:ss.######[+-]##:##
171
172          Where # are numeric digits ranging from 0 to 9 and the seconds
173          fraction can be either 3 or 6 digits. The time of day, seconds
174          fraction and time zone offset are optional. The default time zone
175          is UTC.
176    """
177    date_time_values = self._CopyDateTimeFromString(time_string)
178
179    year = date_time_values.get('year', 0)
180    month = date_time_values.get('month', 0)
181    day_of_month = date_time_values.get('day_of_month', 0)
182    hours = date_time_values.get('hours', 0)
183    minutes = date_time_values.get('minutes', 0)
184    seconds = date_time_values.get('seconds', 0)
185    microseconds = date_time_values.get('microseconds', 0)
186    time_zone_offset = date_time_values.get('time_zone_offset', 0)
187
188    timestamp = self._GetNumberOfSecondsFromElements(
189        year, month, day_of_month, hours, minutes, seconds)
190    timestamp *= definitions.MILLISECONDS_PER_SECOND
191
192    if microseconds:
193      milliseconds, _ = divmod(
194          microseconds, definitions.MILLISECONDS_PER_SECOND)
195      timestamp += milliseconds
196
197    self._timestamp = timestamp
198    self._time_zone_offset = time_zone_offset
199
200  def CopyToDateTimeString(self):
201    """Copies the POSIX timestamp to a date and time string.
202
203    Returns:
204      str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
205          None if the timestamp is missing.
206    """
207    if self._timestamp is None:
208      return None
209
210    timestamp, milliseconds = divmod(
211        self._timestamp, definitions.MILLISECONDS_PER_SECOND)
212    number_of_days, hours, minutes, seconds = self._GetTimeValues(timestamp)
213
214    year, month, day_of_month = self._GetDateValuesWithEpoch(
215        number_of_days, self._EPOCH)
216
217    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:03d}'.format(
218        year, month, day_of_month, hours, minutes, seconds, milliseconds)
219
220
221class PosixTimeInMicroseconds(interface.DateTimeValues):
222  """POSIX timestamp in microseconds.
223
224  Variant of the POSIX timestamp in microseconds.
225
226  Attributes:
227    is_local_time (bool): True if the date and time value is in local time.
228  """
229
230  _EPOCH = PosixTimeEpoch()
231
232  def __init__(self, time_zone_offset=None, timestamp=None):
233    """Initializes a POSIX timestamp in microseconds.
234
235    Args:
236      time_zone_offset (Optional[int]): time zone offset in number of minutes
237          from UTC or None if not set.
238      timestamp (Optional[int]): POSIX timestamp in microseconds.
239    """
240    super(PosixTimeInMicroseconds, self).__init__(
241        time_zone_offset=time_zone_offset)
242    self._precision = definitions.PRECISION_1_MICROSECOND
243    self._timestamp = timestamp
244
245  @property
246  def timestamp(self):
247    """int: POSIX timestamp in microseconds or None if not set."""
248    return self._timestamp
249
250  def _GetNormalizedTimestamp(self):
251    """Retrieves the normalized timestamp.
252
253    Returns:
254      decimal.Decimal: normalized timestamp, which contains the number of
255          seconds since January 1, 1970 00:00:00 and a fraction of second used
256          for increased precision, or None if the normalized timestamp cannot be
257          determined.
258    """
259    if self._normalized_timestamp is None:
260      if self._timestamp is not None:
261        self._normalized_timestamp = (
262            decimal.Decimal(self._timestamp) /
263            definitions.MICROSECONDS_PER_SECOND)
264
265        if self._time_zone_offset:
266          self._normalized_timestamp -= self._time_zone_offset * 60
267
268    return self._normalized_timestamp
269
270  def CopyFromDateTimeString(self, time_string):
271    """Copies a POSIX timestamp from a date and time string.
272
273    Args:
274      time_string (str): date and time value formatted as:
275          YYYY-MM-DD hh:mm:ss.######[+-]##:##
276
277          Where # are numeric digits ranging from 0 to 9 and the seconds
278          fraction can be either 3 or 6 digits. The time of day, seconds
279          fraction and time zone offset are optional. The default time zone
280          is UTC.
281    """
282    date_time_values = self._CopyDateTimeFromString(time_string)
283
284    year = date_time_values.get('year', 0)
285    month = date_time_values.get('month', 0)
286    day_of_month = date_time_values.get('day_of_month', 0)
287    hours = date_time_values.get('hours', 0)
288    minutes = date_time_values.get('minutes', 0)
289    seconds = date_time_values.get('seconds', 0)
290    microseconds = date_time_values.get('microseconds', 0)
291    time_zone_offset = date_time_values.get('time_zone_offset', 0)
292
293    timestamp = self._GetNumberOfSecondsFromElements(
294        year, month, day_of_month, hours, minutes, seconds)
295    timestamp *= definitions.MICROSECONDS_PER_SECOND
296    timestamp += microseconds
297
298    self._timestamp = timestamp
299    self._time_zone_offset = time_zone_offset
300
301  def CopyToDateTimeString(self):
302    """Copies the POSIX timestamp to a date and time string.
303
304    Returns:
305      str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
306          None if the timestamp is missing.
307    """
308    if self._timestamp is None:
309      return None
310
311    timestamp, microseconds = divmod(
312        self._timestamp, definitions.MICROSECONDS_PER_SECOND)
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 '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}'.format(
319        year, month, day_of_month, hours, minutes, seconds, microseconds)
320
321
322class PosixTimeInNanoseconds(interface.DateTimeValues):
323  """POSIX timestamp in nanoseconds.
324
325  Variant of the POSIX timestamp in nanoseconds.
326
327  Attributes:
328    is_local_time (bool): True if the date and time value is in local time.
329  """
330
331  _EPOCH = PosixTimeEpoch()
332
333  def __init__(self, time_zone_offset=None, timestamp=None):
334    """Initializes a POSIX timestamp in nanoseconds.
335
336    Args:
337      time_zone_offset (Optional[int]): time zone offset in number of minutes
338          from UTC or None if not set.
339      timestamp (Optional[int]): POSIX timestamp in nanoseconds.
340    """
341    super(PosixTimeInNanoseconds, self).__init__(
342        time_zone_offset=time_zone_offset)
343    self._precision = definitions.PRECISION_1_NANOSECOND
344    self._timestamp = timestamp
345
346  @property
347  def timestamp(self):
348    """int: POSIX timestamp or None if not set."""
349    return self._timestamp
350
351  def _GetNormalizedTimestamp(self):
352    """Retrieves the normalized timestamp.
353
354    Returns:
355      decimal.Decimal: normalized timestamp, which contains the number of
356          seconds since January 1, 1970 00:00:00 and a fraction of second used
357          for increased precision, or None if the normalized timestamp cannot be
358          determined.
359    """
360    if self._normalized_timestamp is None:
361      if self._timestamp is not None:
362        self._normalized_timestamp = (
363            decimal.Decimal(self._timestamp) /
364            definitions.NANOSECONDS_PER_SECOND)
365
366        if self._time_zone_offset:
367          self._normalized_timestamp -= self._time_zone_offset * 60
368
369    return self._normalized_timestamp
370
371  def _CopyFromDateTimeString(self, time_string):
372    """Copies a POSIX timestamp from a date and time string.
373
374    Args:
375      time_string (str): date and time value formatted as:
376          YYYY-MM-DD hh:mm:ss.######[+-]##:##
377
378          Where # are numeric digits ranging from 0 to 9 and the seconds
379          fraction can be either 3 or 6 digits. The time of day, seconds
380          fraction and time zone offset are optional. The default time zone
381          is UTC.
382    """
383    date_time_values = self._CopyDateTimeFromString(time_string)
384
385    year = date_time_values.get('year', 0)
386    month = date_time_values.get('month', 0)
387    day_of_month = date_time_values.get('day_of_month', 0)
388    hours = date_time_values.get('hours', 0)
389    minutes = date_time_values.get('minutes', 0)
390    seconds = date_time_values.get('seconds', 0)
391    microseconds = date_time_values.get('microseconds', None)
392    time_zone_offset = date_time_values.get('time_zone_offset', 0)
393
394    timestamp = self._GetNumberOfSecondsFromElements(
395        year, month, day_of_month, hours, minutes, seconds)
396    timestamp *= definitions.NANOSECONDS_PER_SECOND
397
398    if microseconds:
399      nanoseconds = microseconds * definitions.MILLISECONDS_PER_SECOND
400      timestamp += nanoseconds
401
402    self._normalized_timestamp = None
403    self._timestamp = timestamp
404    self._time_zone_offset = time_zone_offset
405
406  def CopyFromDateTimeString(self, time_string):
407    """Copies a POSIX timestamp from a date and time string.
408
409    Args:
410      time_string (str): date and time value formatted as:
411          YYYY-MM-DD hh:mm:ss.######[+-]##:##
412
413          Where # are numeric digits ranging from 0 to 9 and the seconds
414          fraction can be either 3 or 6 digits. The time of day, seconds
415          fraction and time zone offset are optional. The default time zone
416          is UTC.
417    """
418    self._CopyFromDateTimeString(time_string)
419
420  def _CopyToDateTimeString(self):
421    """Copies the POSIX timestamp to a date and time string.
422
423    Returns:
424      str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.#########" or
425          None if the timestamp is missing or invalid.
426    """
427    if self._timestamp is None:
428      return None
429
430    timestamp, nanoseconds = divmod(
431        self._timestamp, definitions.NANOSECONDS_PER_SECOND)
432    number_of_days, hours, minutes, seconds = self._GetTimeValues(timestamp)
433
434    year, month, day_of_month = self._GetDateValuesWithEpoch(
435        number_of_days, self._EPOCH)
436
437    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:09d}'.format(
438        year, month, day_of_month, hours, minutes, seconds, nanoseconds)
439
440  def CopyToDateTimeString(self):
441    """Copies the POSIX timestamp to a date and time string.
442
443    Returns:
444      str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.#########" or
445          None if the timestamp is missing or invalid.
446    """
447    return self._CopyToDateTimeString()
448
449
450factory.Factory.RegisterDateTimeValues(PosixTime)
451factory.Factory.RegisterDateTimeValues(PosixTimeInMilliseconds)
452factory.Factory.RegisterDateTimeValues(PosixTimeInMicroseconds)
453factory.Factory.RegisterDateTimeValues(PosixTimeInNanoseconds)
454