Source code for dfdatetime.rfc2579_date_time

# -*- coding: utf-8 -*-
"""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(RFC2579DateTime, self).__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', None) 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}')
factory.Factory.RegisterDateTimeValues(RFC2579DateTime)