Source code for dfdatetime.rfc2579_date_time

"""RFC2579 date-time implementation."""

import decimal

from dfdatetime import definitions
from dfdatetime import factory
from dfdatetime import interface


[docs] class RFC2579DateTime(interface.DateTimeValues): """RFC2579 date-time. The RFC2579 date-time structure is 11 bytes of size and contains: struct { uin16_t year, uint8_t month, uint8_t day_of_month, uint8_t hours, uint8_t minutes, uint8_t seconds, uint8_t deciseconds, char direction_from_utc, uint8_t hours_from_utc, uint8_t minutes_from_utc } Also see: https://datatracker.ietf.org/doc/html/rfc2579 Attributes: year (int): year, 0 through 65536. month (int): month of year, 1 through 12. day_of_month (int): day of month, 1 through 31. hours (int): hours, 0 through 23. minutes (int): minutes, 0 through 59. seconds (int): seconds, 0 through 59, where 60 is used to represent a leap-second. deciseconds (int): deciseconds, 0 through 9. """ # TODO: make attributes read-only. # pylint: disable=missing-type-doc
[docs] def __init__(self, precision=None, rfc2579_date_time_tuple=None): """Initializes a RFC2579 date-time. Args: precision (Optional[str]): precision of the date and time value, which should be one of the PRECISION_VALUES in definitions. rfc2579_date_time_tuple: (Optional[tuple[int, int, int, int, int, int, int, str, int, int]]): RFC2579 date-time time, contains year, month, day of month, hours, minutes, seconds and deciseconds, and time zone offset in hours and minutes from UTC. Raises: ValueError: if the system time is invalid. """ super().__init__(precision=precision or definitions.PRECISION_100_MILLISECONDS) self._day_of_month = None self._deciseconds = None self._hours = None self._minutes = None self._month = None self._number_of_seconds = None self._seconds = None self._year = None if rfc2579_date_time_tuple: if len(rfc2579_date_time_tuple) < 10: raise ValueError( "Invalid RFC2579 date-time tuple 10 elements required." ) if rfc2579_date_time_tuple[0] < 0 or rfc2579_date_time_tuple[0] > 65536: raise ValueError("Year value out of bounds.") if rfc2579_date_time_tuple[1] not in range(1, 13): raise ValueError("Month value out of bounds.") days_per_month = self._GetDaysPerMonth( rfc2579_date_time_tuple[0], rfc2579_date_time_tuple[1] ) if ( rfc2579_date_time_tuple[2] < 1 or rfc2579_date_time_tuple[2] > days_per_month ): raise ValueError("Day of month value out of bounds.") if rfc2579_date_time_tuple[3] not in range(0, 24): raise ValueError("Hours value out of bounds.") if rfc2579_date_time_tuple[4] not in range(0, 60): raise ValueError("Minutes value out of bounds.") # TODO: support a leap second? if rfc2579_date_time_tuple[5] not in range(0, 60): raise ValueError("Seconds value out of bounds.") if rfc2579_date_time_tuple[6] < 0 or rfc2579_date_time_tuple[6] > 9: raise ValueError("Deciseconds value out of bounds.") if rfc2579_date_time_tuple[7] not in ("+", "-"): raise ValueError("Direction from UTC value out of bounds.") if rfc2579_date_time_tuple[8] not in range(0, 14): raise ValueError("Hours from UTC value out of bounds.") if rfc2579_date_time_tuple[9] not in range(0, 60): raise ValueError("Minutes from UTC value out of bounds.") time_zone_offset = ( rfc2579_date_time_tuple[8] * 60 ) + rfc2579_date_time_tuple[9] if rfc2579_date_time_tuple[7] == "-": time_zone_offset = -time_zone_offset self._time_zone_offset = time_zone_offset self._year = rfc2579_date_time_tuple[0] self._month = rfc2579_date_time_tuple[1] self._day_of_month = rfc2579_date_time_tuple[2] self._hours = rfc2579_date_time_tuple[3] self._minutes = rfc2579_date_time_tuple[4] self._seconds = rfc2579_date_time_tuple[5] self._deciseconds = rfc2579_date_time_tuple[6] self._number_of_seconds = self._GetNumberOfSecondsFromElements( self._year, self._month, self._day_of_month, self._hours, self._minutes, self._seconds, )
def _GetNormalizedTimestamp(self): """Retrieves the normalized timestamp. Returns: decimal.Decimal: normalized timestamp, which contains the number of seconds since January 1, 1970 00:00:00 and a fraction of second used for increased precision, or None if the normalized timestamp cannot be determined. """ if self._normalized_timestamp is None: if self._number_of_seconds is not None: self._normalized_timestamp = ( decimal.Decimal(self._deciseconds) / definitions.DECISECONDS_PER_SECOND ) self._normalized_timestamp += decimal.Decimal(self._number_of_seconds) if self._time_zone_offset: self._normalized_timestamp -= self._time_zone_offset * 60 return self._normalized_timestamp @property def deciseconds(self): """int: number of deciseconds or None if not set.""" return self._deciseconds @property def day_of_month(self): """int: day of month or None if not set.""" return self._day_of_month @property def hours(self): """int: number of hours or None if not set.""" return self._hours @property def minutes(self): """int: number of minutes or None if not set.""" return self._minutes @property def month(self): """int: month or None if not set.""" return self._month @property def seconds(self): """int: number of seconds or None if not set.""" return self._seconds @property def year(self): """int: year or None if not set.""" return self._year
[docs] def CopyFromDateTimeString(self, time_string): """Copies a RFC2579 date-time from a date and time string. Args: time_string (str): date and time value formatted as: YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds fraction can be either 3, 6 or 9 digits. The time of day, seconds fraction and time zone offset are optional. The default time zone is UTC. Raises: ValueError: if the date string is invalid or not supported. """ date_time_values = self._CopyDateTimeFromString(time_string) year = date_time_values.get("year", 0) month = date_time_values.get("month", 0) day_of_month = date_time_values.get("day_of_month", 0) hours = date_time_values.get("hours", 0) minutes = date_time_values.get("minutes", 0) seconds = date_time_values.get("seconds", 0) nanoseconds = date_time_values.get("nanoseconds", 0) time_zone_offset = date_time_values.get("time_zone_offset") deciseconds, _ = divmod(nanoseconds, definitions.NANOSECONDS_PER_DECISECOND) if year < 0 or year > 65536: raise ValueError(f"Unsupported year value: {year:d}.") self._normalized_timestamp = None self._number_of_seconds = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds ) self._time_zone_offset = time_zone_offset self._year = year self._month = month self._day_of_month = day_of_month self._hours = hours self._minutes = minutes self._seconds = seconds self._deciseconds = deciseconds
[docs] def CopyToDateTimeString(self): """Copies the RFC2579 date-time to a date and time string. Returns: str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.#" or None if the number of seconds is missing. """ if self._number_of_seconds is None: return None return ( f"{self._year:04d}-{self._month:02d}-{self._day_of_month:02d} " f"{self._hours:02d}:{self._minutes:02d}:{self._seconds:02d}" f".{self._deciseconds:01d}" )
[docs] def CopyToSerializableDict(self): """Copies the date time value to a serializable dictionary. Returns: dict[str, object]: serializable dictionary. """ time_zone_hours, time_zone_minutes = divmod(self._time_zone_offset, 60) if self._time_zone_offset < 0: time_zone_sign = "-" time_zone_hours *= -1 else: time_zone_sign = "+" return { "__class_name__": type(self).__name__, "__type__": "DateTimeValues", "rfc2579_date_time_tuple": ( self._year, self._month, self._day_of_month, self._hours, self._minutes, self._seconds, self._deciseconds, time_zone_sign, time_zone_hours, time_zone_minutes, ), }
factory.Factory.RegisterDateTimeValues(RFC2579DateTime)