Source code for dfdatetime.time_elements

# -*- coding: utf-8 -*-
"""Time elements implementation."""

import decimal

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


[docs] class TimeElements(interface.DateTimeValues): """Time elements. Time elements contain separate values for year, month, day of month, hours, minutes and seconds. Attributes: is_local_time (bool): True if the date and time value is in local time. """ # Maps the RFC 822, RFC 1123 and RFC 2822 definitions to their corresponding # integer values. _RFC_MONTH_MAPPINGS = { 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12} _RFC_TIME_ZONE_MAPPINGS = { 'UT': 0, 'GMT': 0, 'EST': -5, 'EDT': -4, 'CST': -6, 'CDT': -5, 'MST': -7, 'MDT': -6, 'PST': -8, 'PDT': -7, 'A': -1, 'B': -2, 'C': -3, 'D': -4, 'E': -5, 'F': -6, 'G': -7, 'H': -8, 'I': -9, 'K': -10, 'L': -11, 'M': -12, 'N': 1, 'O': 2, 'P': 3, 'Q': 4, 'R': 5, 'S': 6, 'T': 7, 'U': 8, 'V': 9, 'W': 10, 'X': 11, 'Y': 12, 'Z': 0} _RFC_WEEKDAYS = frozenset(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
[docs] def __init__( self, is_delta=False, precision=None, time_elements_tuple=None, time_zone_offset=None): """Initializes time elements. Args: is_delta (Optional[bool]): True if the date and time value is relative to another date and time value. precision (Optional[str]): precision of the date and time value, which should be one of the PRECISION_VALUES in definitions. time_elements_tuple (Optional[tuple[int, int, int, int, int, int]]): time elements, contains year, month, day of month, hours, minutes and seconds. time_zone_offset (Optional[int]): time zone offset in number of minutes from UTC or None if not set. Raises: ValueError: if the time elements tuple is invalid. """ super(TimeElements, self).__init__( is_delta=is_delta, precision=precision or definitions.PRECISION_1_SECOND, time_zone_offset=time_zone_offset) self._number_of_seconds = None self._time_elements_tuple = time_elements_tuple if time_elements_tuple: number_of_elements = len(time_elements_tuple) if number_of_elements < 6: raise ValueError(( f'Invalid time elements tuple at least 6 elements required,' f'got: {number_of_elements:d}')) self._number_of_seconds = self._GetNumberOfSecondsFromElements( time_elements_tuple[0], time_elements_tuple[1], time_elements_tuple[2], time_elements_tuple[3], time_elements_tuple[4], time_elements_tuple[5])
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._number_of_seconds) if self._time_zone_offset: self._normalized_timestamp -= self._time_zone_offset * 60 return self._normalized_timestamp def _CopyDateTimeFromStringISO8601(self, time_string): """Copies a date and time from an ISO 8601 date and time string. Args: time_string (str): time value formatted as: 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 fraction of second and time zone offset are optional. Returns: dict[str, int]: date and time values, such as year, month, day of month, hours, minutes, seconds, nanoseconds, time zone offset in minutes. Raises: ValueError: if the time string is invalid or not supported. """ if not time_string: raise ValueError('Invalid time string.') time_string_length = len(time_string) year, month, day_of_month = self._CopyDateFromString(time_string) if time_string_length <= 10: return { 'year': year, 'month': month, 'day_of_month': day_of_month} # If a time of day is specified the time string it should at least # contain 'YYYY-MM-DDThh'. if time_string[10] != 'T': raise ValueError('Invalid time string - missing date and time separator.') hours, minutes, seconds, nanoseconds, time_zone_offset = ( self._CopyTimeFromStringISO8601(time_string[11:])) date_time_values = { 'year': year, 'month': month, 'day_of_month': day_of_month, 'hours': hours, 'minutes': minutes, 'seconds': seconds} if nanoseconds is not None: date_time_values['nanoseconds'] = nanoseconds if time_zone_offset is not None: date_time_values['time_zone_offset'] = time_zone_offset return date_time_values def _CopyDateTimeFromStringRFC822(self, time_string): """Copies a date and time from a RFC 822 date and time string. Args: time_string (str): date and time value formatted as: DAY, D MONTH YY hh:mm:ss ZONE Where weekday (DAY) and seconds (ss) are optional and day of month (D) can consist of 1 or 2 digits. Returns: dict[str, int]: date and time values, such as year, month, day of month, hours, minutes, seconds, time zone offset in minutes. Raises: ValueError: if the time string is invalid or not supported. """ if not time_string: raise ValueError('Invalid time string.') string_segments = time_string.split(' ') if len(string_segments) not in (5, 6): raise ValueError('Unsupported number of time string segments.') weekday_string = string_segments[0] if weekday_string.endswith(','): weekday_string = weekday_string[:-1] if weekday_string not in self._RFC_WEEKDAYS: raise ValueError(f'Invalid weekday: {weekday_string:s}.') string_segments.pop(0) day_of_month_string = string_segments[0] day_of_month = 0 if len(day_of_month_string) in (1, 2): try: day_of_month = int(day_of_month_string, 10) except ValueError: pass if day_of_month == 0: raise ValueError(f'Invalid day of month: {day_of_month_string:s}.') month_string = string_segments[1] month = self._RFC_MONTH_MAPPINGS.get(month_string) if not month: raise ValueError(f'Invalid month: {month_string:s}.') year_string = string_segments[2] year = None if len(year_string) == 2: try: year = int(year_string, 10) except ValueError: pass if year is None: raise ValueError(f'Invalid year: {0:s}.') year += 1900 hours, minutes, seconds, time_zone_offset = self._CopyTimeFromStringRFC( string_segments[3], string_segments[4]) date_time_values = { 'year': year, 'month': month, 'day_of_month': day_of_month, 'hours': hours, 'minutes': minutes, 'time_zone_offset': time_zone_offset} if seconds is not None: date_time_values['seconds'] = seconds return date_time_values def _CopyDateTimeFromStringRFC1123(self, time_string): """Copies a date and time from a RFC 1123 date and time string. Args: time_string (str): date and time value formatted as: DAY, D MONTH YYYY hh:mm:ss ZONE Where weekday (DAY) and seconds (ss) are optional and day of month (D) can consist of 1 or 2 digits. Returns: dict[str, int]: date and time values, such as year, month, day of month, hours, minutes, seconds, time zone offset in minutes. Raises: ValueError: if the time string is invalid or not supported. """ if not time_string: raise ValueError('Invalid time string.') string_segments = time_string.split(' ') if len(string_segments) not in (5, 6): raise ValueError('Unsupported number of time string segments.') weekday_string = string_segments[0] if weekday_string.endswith(','): weekday_string = weekday_string[:-1] if weekday_string not in self._RFC_WEEKDAYS: raise ValueError(f'Invalid weekday: {weekday_string:s}.') string_segments.pop(0) day_of_month_string = string_segments[0] day_of_month = 0 if len(day_of_month_string) in (1, 2): try: day_of_month = int(day_of_month_string, 10) except ValueError: pass if day_of_month == 0: raise ValueError(f'Invalid day of month: {day_of_month_string:s}.') month_string = string_segments[1] month = self._RFC_MONTH_MAPPINGS.get(month_string) if not month: raise ValueError(f'Invalid month: {month_string:s}.') year_string = string_segments[2] year = None if len(year_string) == 4: try: year = int(year_string, 10) except ValueError: pass if year is None: raise ValueError(f'Invalid year: {year_string:s}.') hours, minutes, seconds, time_zone_offset = self._CopyTimeFromStringRFC( string_segments[3], string_segments[4]) date_time_values = { 'year': year, 'month': month, 'day_of_month': day_of_month, 'hours': hours, 'minutes': minutes, 'time_zone_offset': time_zone_offset} if seconds is not None: date_time_values['seconds'] = seconds return date_time_values def _CopyFromDateTimeValues(self, date_time_values): """Copies time elements from date and time values. Args: date_time_values (dict[str, int]): date and time values, such as year, month, day of month, hours, minutes, seconds, nanoseconds, time zone offset in minutes. """ 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) time_zone_offset = date_time_values.get('time_zone_offset', None) self._normalized_timestamp = None self._number_of_seconds = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) self._time_elements_tuple = ( year, month, day_of_month, hours, minutes, seconds) self._time_zone_offset = time_zone_offset def _CopyTimeFromStringISO8601(self, time_string): """Copies a time from an ISO 8601 time string. Args: time_string (str): time value formatted as: 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 fraction of second and time zone offset are optional. Returns: tuple[int, int, int, int, int]: hours, minutes, seconds, nanoseconds, time zone offset in minutes. Raises: ValueError: if the time string is invalid or not supported. """ if time_string.endswith('Z'): time_string = ''.join([time_string[:-1], '+00:00']) time_string_length = len(time_string) # The time string should at least contain 'hh'. if time_string_length < 2: raise ValueError('Time string too short.') try: hours = int(time_string[0:2], 10) except ValueError: raise ValueError('Unable to parse hours.') if hours not in range(0, 24): raise ValueError(f'Hours value: {hours:d} out of bounds.') minutes = None seconds = None nanoseconds = None time_zone_offset = None time_string_index = 2 # Minutes are either specified as 'hhmm', 'hh:mm' or as a fractional part # 'hh[.,]###'. if (time_string_index + 1 < time_string_length and time_string[time_string_index] not in ('.', ',')): if time_string[time_string_index] == ':': time_string_index += 1 if time_string_index + 2 > time_string_length: raise ValueError('Time string too short.') try: minutes = time_string[time_string_index:time_string_index + 2] minutes = int(minutes, 10) except ValueError: raise ValueError('Unable to parse minutes.') time_string_index += 2 # Seconds are either specified as 'hhmmss', 'hh:mm:ss' or as a fractional # part 'hh:mm[.,]###' or 'hhmm[.,]###'. if (time_string_index + 1 < time_string_length and time_string[time_string_index] not in ('.', ',')): if time_string[time_string_index] == ':': time_string_index += 1 if time_string_index + 2 > time_string_length: raise ValueError('Time string too short.') try: seconds = time_string[time_string_index:time_string_index + 2] seconds = int(seconds, 10) except ValueError: raise ValueError('Unable to parse day of seconds.') time_string_index += 2 time_zone_string_index = time_string_index while time_zone_string_index < time_string_length: if time_string[time_zone_string_index] in ('+', '-'): break time_zone_string_index += 1 # The calculations that follow rely on the time zone string index # to point beyond the string in case no time zone offset was defined. if time_zone_string_index == time_string_length - 1: time_zone_string_index += 1 if (time_string_length > time_string_index and time_string[time_string_index] in ('.', ',')): time_string_index += 1 time_fraction_length = time_zone_string_index - time_string_index try: time_fraction = time_string[time_string_index:time_zone_string_index] time_fraction = int(time_fraction, 10) time_fraction = ( decimal.Decimal(time_fraction) / decimal.Decimal(10 ** time_fraction_length)) except ValueError: raise ValueError('Unable to parse time fraction.') if minutes is None: time_fraction *= 60 minutes = int(time_fraction) time_fraction -= minutes if seconds is None: time_fraction *= 60 seconds = int(time_fraction) time_fraction -= seconds time_fraction *= definitions.NANOSECONDS_PER_SECOND nanoseconds = int(time_fraction) if minutes is not None and minutes not in range(0, 60): raise ValueError(f'Minutes value: {minutes:d} out of bounds.') # TODO: support a leap second? if seconds is not None and seconds not in range(0, 60): raise ValueError(f'Seconds value: {seconds:d} out of bounds.') if time_zone_string_index < time_string_length: if (time_string_length - time_zone_string_index != 6 or time_string[time_zone_string_index + 3] != ':'): raise ValueError('Invalid time string.') try: hours_from_utc = int(time_string[ time_zone_string_index + 1:time_zone_string_index + 3]) except ValueError: raise ValueError('Unable to parse time zone hours offset.') if hours_from_utc not in range(0, 15): raise ValueError('Time zone hours offset value out of bounds.') try: minutes_from_utc = int(time_string[ time_zone_string_index + 4:time_zone_string_index + 6]) except ValueError: raise ValueError('Unable to parse time zone minutes offset.') if minutes_from_utc not in range(0, 60): raise ValueError('Time zone minutes offset value out of bounds.') # pylint: disable=invalid-unary-operand-type time_zone_offset = (hours_from_utc * 60) + minutes_from_utc if time_string[time_zone_string_index] == '-': time_zone_offset = -time_zone_offset return hours, minutes, seconds, nanoseconds, time_zone_offset def _CopyTimeFromStringRFC(self, time_string, time_zone_string): """Copies a time from a RFC 822, RFC 1123 or RFC 2822 time string. Args: time_string (str): time value formatted as: hh:mm[:ss], where seconds (ss) are optional. time_zone_string (str): time zone value formatted as predefined time zone indicator or [+-]HHMM Returns: tuple[int, int, int, int]: hours, minutes, seconds, time zone offset in minutes. Raises: ValueError: if the time string is invalid or not supported. """ time_string_length = len(time_string) # The time string should at least contain 'hh:mm'. if time_string_length < 5: raise ValueError('Time string too short.') if time_string_length > 8: raise ValueError('Time string too long.') if time_string[2] != ':': raise ValueError('Invalid hours and minutes separator.') try: hours = int(time_string[0:2], 10) except ValueError: raise ValueError('Unable to parse hours.') if hours not in range(0, 24): raise ValueError(f'Hours value: {hours:d} out of bounds.') try: minutes = int(time_string[3:5], 10) except ValueError: raise ValueError('Unable to parse minutes.') if minutes not in range(0, 60): raise ValueError(f'Minutes value: {minutes:d} out of bounds.') seconds = None if time_string_length > 5: if time_string_length < 8: raise ValueError('Time string too short.') if time_string[5] != ':': raise ValueError('Invalid minutes and seconds separator.') try: seconds = int(time_string[6:8], 10) except ValueError: raise ValueError('Unable to parse seconds.') if seconds not in range(0, 60): raise ValueError(f'Seconds value: {seconds:d} out of bounds.') if time_string_length < 5: raise ValueError('Time string too short.') time_zone_string_length = len(time_zone_string) if time_zone_string_length > 5: raise ValueError('Time zone string too long.') if time_zone_string_length < 5: hours_from_utc = self._RFC_TIME_ZONE_MAPPINGS.get(time_zone_string, None) minutes_from_utc = 0 if hours_from_utc is None: raise ValueError(f'Invalid time zone: {time_zone_string:s}.') else: if time_zone_string[0] not in ('+', '-'): raise ValueError(f'Invalid time zone: {time_zone_string:s}.') try: hours_from_utc = int(time_zone_string[1:3], 10) except ValueError: raise ValueError('Unable to parse time zone hours offset.') if hours_from_utc not in range(0, 15): raise ValueError('Time zone hours offset value out of bounds.') try: minutes_from_utc = int(time_zone_string[3:5], 10) except ValueError: raise ValueError('Unable to parse time zone minutes offset.') if minutes_from_utc not in range(0, 60): raise ValueError('Time zone minutes offset value out of bounds.') time_zone_offset = (hours_from_utc * 60) + minutes_from_utc if time_zone_string[0] == '-': time_zone_offset = -time_zone_offset return hours, minutes, seconds, time_zone_offset @property def day_of_month(self): """int: day of month or None if not set.""" if not self._time_elements_tuple: return None return self._time_elements_tuple[2] @property def hours(self): """int: number of hours or None if not set.""" if not self._time_elements_tuple: return None return self._time_elements_tuple[3] @property def minutes(self): """int: number of minutes or None if not set.""" if not self._time_elements_tuple: return None return self._time_elements_tuple[4] @property def month(self): """int: month or None if not set.""" if not self._time_elements_tuple: return None return self._time_elements_tuple[1] @property def seconds(self): """int: number of seconds or None if not set.""" if not self._time_elements_tuple: return None return self._time_elements_tuple[5] @property def year(self): """int: year or None if not set.""" if not self._time_elements_tuple: return None return self._time_elements_tuple[0]
[docs] def CopyFromDatetime(self, datetime_object): """Copies time elements from a Python datetime object. A naive datetime object is considered in local time. Args: datetime_object (datetime.datetime): Python datetime object. """ year, month, day_of_month, hours, minutes, seconds, _, _, _ = ( datetime_object.utctimetuple()) date_time_values = { 'year': year, 'month': month, 'day_of_month': day_of_month, 'hours': hours, 'minutes': minutes, 'seconds': seconds} self._CopyFromDateTimeValues(date_time_values) self.is_local_time = bool(datetime_object.tzinfo is None)
[docs] def CopyFromDateTimeString(self, time_string): """Copies time elements 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. """ date_time_values = self._CopyDateTimeFromString(time_string) self._CopyFromDateTimeValues(date_time_values)
[docs] def CopyFromStringISO8601(self, time_string): """Copies time elements from an ISO 8601 date and time string. Currently not supported: * Duration notation: "P..." * Week notation "2016-W33" * Date with week number notation "2016-W33-3" * Date without year notation "--08-17" * Ordinal date notation "2016-230" Args: time_string (str): date and time value formatted as: YYYY-MM-DDThh: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 time string is invalid or not supported. """ date_time_values = self._CopyDateTimeFromStringISO8601(time_string) self._CopyFromDateTimeValues(date_time_values)
[docs] def CopyFromStringRFC822(self, time_string): """Copies time elements from a RFC 822 date and time string. Args: time_string (str): date and time value formatted as: DAY, D MONTH YY hh:mm:ss ZONE Where weekday (DAY) and seconds (ss) are optional and day of month (D) can consist of 1 or 2 digits. Raises: ValueError: if the time string is invalid or not supported. """ date_time_values = self._CopyDateTimeFromStringRFC822(time_string) self._CopyFromDateTimeValues(date_time_values)
[docs] def CopyFromStringRFC1123(self, time_string): """Copies time elements from a RFC 1123 date and time string. Args: time_string (str): date and time value formatted as: DAY, D MONTH YYYY hh:mm:ss ZONE Where weekday (DAY) and seconds (ss) are optional and day of month (D) can consist of 1 or 2 digits. Raises: ValueError: if the time string is invalid or not supported. """ date_time_values = self._CopyDateTimeFromStringRFC1123(time_string) self._CopyFromDateTimeValues(date_time_values)
[docs] def CopyFromStringTuple(self, time_elements_tuple): """Copies time elements from string-based time elements tuple. Args: time_elements_tuple (Optional[tuple[str, str, str, str, str, str]]): time elements, contains year, month, day of month, hours, minutes and seconds. Raises: ValueError: if the time elements tuple is invalid. """ number_of_elements = len(time_elements_tuple) if number_of_elements < 6: raise ValueError(( f'Invalid time elements tuple at least 6 elements required,' f'got: {number_of_elements:d}')) year_string = time_elements_tuple[0] month_string = time_elements_tuple[1] day_of_month_string = time_elements_tuple[2] hours_string = time_elements_tuple[3] minutes_string = time_elements_tuple[4] seconds_string = time_elements_tuple[5] try: year = int(year_string, 10) except (TypeError, ValueError): raise ValueError(f'Invalid year value: {year_string!s}') try: month = int(month_string, 10) except (TypeError, ValueError): raise ValueError(f'Invalid month value: {month_string!s}') try: day_of_month = int(day_of_month_string, 10) except (TypeError, ValueError): raise ValueError(f'Invalid day of month value: {day_of_month_string!s}') try: hours = int(hours_string, 10) except (TypeError, ValueError): raise ValueError(f'Invalid hours value: {hours_string!s}') try: minutes = int(minutes_string, 10) except (TypeError, ValueError): raise ValueError(f'Invalid minutes value: {minutes_string!s}') try: seconds = int(seconds_string, 10) except (TypeError, ValueError): raise ValueError(f'Invalid seconds value: {seconds_string!s}') self._normalized_timestamp = None self._number_of_seconds = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) self._time_elements_tuple = ( year, month, day_of_month, hours, minutes, seconds)
[docs] def CopyToDateTimeString(self): """Copies the time elements to a date and time string. Returns: str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss" or None if time elements are missing. """ if self._number_of_seconds is None: return None year, month, day_of_month, hours, minutes, seconds = ( self._time_elements_tuple) return ( f'{year:04d}-{month:02d}-{day_of_month:02d} ' f'{hours:02d}:{minutes:02d}:{seconds:02d}')
[docs] def NewFromDeltaAndDate(self, year, month, day_of_month): """Creates a new time elements instance from a date time delta and a date. Args: year (int): year. month (int): month, where 1 represents January and 0 if not set. day_of_month (int): day of month, where 1 represents the first day and 0 if not set. Returns: TimeElements: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ if not self._is_delta: raise ValueError('Not a date time delta.') if self._time_elements_tuple is None: return None delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = ( self._time_elements_tuple) time_elements_tuple = ( year + delta_year, month + delta_month, day_of_month + delta_day_of_month, hours, minutes, seconds) date_time = TimeElements( precision=self._precision, time_elements_tuple=time_elements_tuple, time_zone_offset=self._time_zone_offset) date_time.is_local_time = self.is_local_time return date_time
[docs] def NewFromDeltaAndYear(self, year): """Creates a new time elements instance from a date time delta and a year. Args: year (int): year. Returns: TimeElements: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ return self.NewFromDeltaAndDate(year, 0, 0)
[docs] class TimeElementsWithFractionOfSecond(TimeElements): """Time elements with a fraction of second interface. Attributes: fraction_of_second (decimal.Decimal): fraction of second, which must be a value between 0.0 and 1.0. is_local_time (bool): True if the date and time value is in local time. """
[docs] def __init__( self, fraction_of_second=None, is_delta=False, precision=None, time_elements_tuple=None, time_zone_offset=None): """Initializes time elements. Args: fraction_of_second (Optional[decimal.Decimal]): fraction of second, which must be a value between 0.0 and 1.0. is_delta (Optional[bool]): True if the date and time value is relative to another date and time value. precision (Optional[str]): precision of the date and time value, which should be one of the PRECISION_VALUES in definitions. time_elements_tuple (Optional[tuple[int, int, int, int, int, int]]): time elements, contains year, month, day of month, hours, minutes and seconds. time_zone_offset (Optional[int]): time zone offset in number of minutes from UTC or None if not set. Raises: ValueError: if the time elements tuple is invalid or fraction of second value is out of bounds. """ if fraction_of_second is not None: if fraction_of_second < 0.0 or fraction_of_second >= 1.0: raise ValueError( f'Fraction of second value: {fraction_of_second:f} out of bounds.') super(TimeElementsWithFractionOfSecond, self).__init__( is_delta=is_delta, precision=precision or definitions.PRECISION_1_SECOND, time_elements_tuple=time_elements_tuple, time_zone_offset=time_zone_offset) self.fraction_of_second = fraction_of_second
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 and self.fraction_of_second is not None): self._normalized_timestamp = ( decimal.Decimal(self._number_of_seconds) + self.fraction_of_second) if self._time_zone_offset: self._normalized_timestamp -= self._time_zone_offset * 60 return self._normalized_timestamp def _CopyFromDateTimeValues(self, date_time_values): """Copies time elements from date and time values. Args: date_time_values (dict[str, int]): date and time values, such as year, month, day of month, hours, minutes, seconds, nanoseconds, time zone offset in minutes. Raises: ValueError: if no helper can be created for the current precision. """ 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) precision_helper = precisions.PrecisionHelperFactory.CreatePrecisionHelper( self._precision) fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond( nanoseconds) self._normalized_timestamp = None self._number_of_seconds = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) self._time_elements_tuple = ( year, month, day_of_month, hours, minutes, seconds) self._time_zone_offset = time_zone_offset self.fraction_of_second = fraction_of_second
[docs] def CopyFromDatetime(self, datetime_object): """Copies time elements from a Python datetime object. A naive datetime object is considered in local time. Args: datetime_object (datetime.datetime): Python datetime object. """ super(TimeElementsWithFractionOfSecond, self).CopyFromDatetime( datetime_object) precision_helper = precisions.PrecisionHelperFactory.CreatePrecisionHelper( self._precision) fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond( datetime_object.microsecond * definitions.NANOSECONDS_PER_MICROSECOND) self.fraction_of_second = fraction_of_second
[docs] def CopyFromStringTuple(self, time_elements_tuple): """Copies time elements from string-based time elements tuple. Args: time_elements_tuple (Optional[tuple[str, str, str, str, str, str, str]]): time elements, contains year, month, day of month, hours, minutes, seconds and fraction of seconds. Raises: ValueError: if the time elements tuple is invalid. """ number_of_elements = len(time_elements_tuple) if number_of_elements < 7: raise ValueError(( f'Invalid time elements tuple at least 7 elements required,' f'got: {number_of_elements:d}')) super(TimeElementsWithFractionOfSecond, self).CopyFromStringTuple( time_elements_tuple) fraction_of_second_string = time_elements_tuple[6] try: fraction_of_second = decimal.Decimal(fraction_of_second_string) except (TypeError, ValueError): raise ValueError( f'Invalid fraction of second value: {fraction_of_second_string!s}') if fraction_of_second < 0.0 or fraction_of_second >= 1.0: raise ValueError( f'Fraction of second value: {fraction_of_second:f} out of bounds.') self.fraction_of_second = fraction_of_second
[docs] def CopyToDateTimeString(self): """Copies the time elements to a date and time string. Returns: str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss" or "YYYY-MM-DD hh:mm:ss.######" or None if time elements are missing. Raises: ValueError: if the precision value is unsupported. """ if self._number_of_seconds is None or self.fraction_of_second is None: return None precision_helper = precisions.PrecisionHelperFactory.CreatePrecisionHelper( self._precision) return precision_helper.CopyToDateTimeString( self._time_elements_tuple, self.fraction_of_second)
[docs] def NewFromDeltaAndDate(self, year, month, day_of_month): """Creates a new time elements instance from a date time delta and a date. Args: year (int): year. month (int): month, where 1 represents January and 0 if not set. day_of_month (int): day of month, where 1 represents the first day and 0 if not set. Returns: TimeElementsWithFractionOfSecond: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ if not self._is_delta: raise ValueError('Not a date time delta.') if self._time_elements_tuple is None: return None delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = ( self._time_elements_tuple) time_elements_tuple = ( year + delta_year, month + delta_month, day_of_month + delta_day_of_month, hours, minutes, seconds) return TimeElementsWithFractionOfSecond( fraction_of_second=self.fraction_of_second, precision=self._precision, time_elements_tuple=time_elements_tuple, time_zone_offset=self._time_zone_offset)
[docs] def NewFromDeltaAndYear(self, year): """Creates a new time elements instance from a date time delta and a year. Args: year (int): year. Returns: TimeElementsWithFractionOfSecond: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ return self.NewFromDeltaAndDate(year, 0, 0)
[docs] class TimeElementsInMilliseconds(TimeElementsWithFractionOfSecond): """Time elements in milliseconds. Attributes: fraction_of_second (decimal.Decimal): fraction of second, which must be a value between 0.0 and 1.0. is_local_time (bool): True if the date and time value is in local time. precision (str): precision of the date of the date and time value, that represents 1 millisecond (PRECISION_1_MILLISECOND). """
[docs] def __init__( self, is_delta=False, precision=None, time_elements_tuple=None, time_zone_offset=None): """Initializes time elements. Args: is_delta (Optional[bool]): True if the date and time value is relative to another date and time value. precision (Optional[str]): precision of the date and time value, which should be one of the PRECISION_VALUES in definitions. time_elements_tuple (Optional[tuple[int, int, int, int, int, int, int]]): time elements, contains year, month, day of month, hours, minutes, seconds and milliseconds. time_zone_offset (Optional[int]): time zone offset in number of minutes from UTC or None if not set. Raises: ValueError: if the time elements tuple is invalid. """ fraction_of_second = None if time_elements_tuple: number_of_elements = len(time_elements_tuple) if number_of_elements < 7: raise ValueError(( f'Invalid time elements tuple at least 7 elements required,' f'got: {number_of_elements:d}')) milliseconds = time_elements_tuple[6] time_elements_tuple = time_elements_tuple[:6] if (milliseconds < 0 or milliseconds >= definitions.MILLISECONDS_PER_SECOND): raise ValueError('Invalid number of milliseconds.') fraction_of_second = ( decimal.Decimal(milliseconds) / definitions.MILLISECONDS_PER_SECOND) super(TimeElementsInMilliseconds, self).__init__( fraction_of_second=fraction_of_second, is_delta=is_delta, precision=precision or definitions.PRECISION_1_MILLISECOND, time_elements_tuple=time_elements_tuple, time_zone_offset=time_zone_offset)
@property def milliseconds(self): """int: number of milliseconds.""" return int(self.fraction_of_second * definitions.MILLISECONDS_PER_SECOND)
[docs] def CopyFromStringTuple(self, time_elements_tuple): """Copies time elements from string-based time elements tuple. Args: time_elements_tuple (Optional[tuple[str, str, str, str, str, str, str]]): time elements, contains year, month, day of month, hours, minutes, seconds and milliseconds. Raises: ValueError: if the time elements tuple is invalid. """ number_of_elements = len(time_elements_tuple) if len(time_elements_tuple) < 7: raise ValueError(( f'Invalid time elements tuple at least 7 elements required,' f'got: {number_of_elements:d}')) year, month, day_of_month, hours, minutes, seconds, milliseconds = ( time_elements_tuple) try: milliseconds = int(milliseconds, 10) except (TypeError, ValueError): raise ValueError(f'Invalid millisecond value: {milliseconds!s}') if milliseconds < 0 or milliseconds >= definitions.MILLISECONDS_PER_SECOND: raise ValueError('Invalid number of milliseconds.') fraction_of_second = ( decimal.Decimal(milliseconds) / definitions.MILLISECONDS_PER_SECOND) time_elements_tuple = ( year, month, day_of_month, hours, minutes, seconds, str(fraction_of_second)) super(TimeElementsInMilliseconds, self).CopyFromStringTuple( time_elements_tuple)
[docs] def NewFromDeltaAndDate(self, year, month, day_of_month): """Creates a new time elements instance from a date time delta and a date. Args: year (int): year. month (int): month, where 1 represents January and 0 if not set. day_of_month (int): day of month, where 1 represents the first day and 0 if not set. Returns: TimeElementsInMilliseconds: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ if not self._is_delta: raise ValueError('Not a date time delta.') if self._time_elements_tuple is None: return None delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = ( self._time_elements_tuple) time_elements_tuple = ( year + delta_year, month + delta_month, day_of_month + delta_day_of_month, hours, minutes, seconds, self.milliseconds) return TimeElementsInMilliseconds( precision=self._precision, time_elements_tuple=time_elements_tuple, time_zone_offset=self._time_zone_offset)
[docs] def NewFromDeltaAndYear(self, year): """Creates a new time elements instance from a date time delta and a year. Args: year (int): year. Returns: TimeElementsInMilliseconds: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ return self.NewFromDeltaAndDate(year, 0, 0)
[docs] class TimeElementsInMicroseconds(TimeElementsWithFractionOfSecond): """Time elements in microseconds. Attributes: fraction_of_second (decimal.Decimal): fraction of second, which must be a value between 0.0 and 1.0. is_local_time (bool): True if the date and time value is in local time. precision (str): precision of the date of the date and time value, that represents 1 microsecond (PRECISION_1_MICROSECOND). """
[docs] def __init__( self, is_delta=False, precision=None, time_elements_tuple=None, time_zone_offset=None): """Initializes time elements. Args: is_delta (Optional[bool]): True if the date and time value is relative to another date and time value. precision (Optional[str]): precision of the date and time value, which should be one of the PRECISION_VALUES in definitions. time_elements_tuple (Optional[tuple[int, int, int, int, int, int, int]]): time elements, contains year, month, day of month, hours, minutes, seconds and microseconds. time_zone_offset (Optional[int]): time zone offset in number of minutes from UTC or None if not set. Raises: ValueError: if the time elements tuple is invalid. """ fraction_of_second = None if time_elements_tuple: number_of_elements = len(time_elements_tuple) if number_of_elements < 7: raise ValueError(( f'Invalid time elements tuple at least 7 elements required,' f'got: {number_of_elements:d}')) microseconds = time_elements_tuple[6] time_elements_tuple = time_elements_tuple[:6] if (microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND): raise ValueError('Invalid number of microseconds.') fraction_of_second = ( decimal.Decimal(microseconds) / definitions.MICROSECONDS_PER_SECOND) super(TimeElementsInMicroseconds, self).__init__( fraction_of_second=fraction_of_second, is_delta=is_delta, precision=precision or definitions.PRECISION_1_MICROSECOND, time_elements_tuple=time_elements_tuple, time_zone_offset=time_zone_offset)
@property def microseconds(self): """int: number of microseconds.""" return int(self.fraction_of_second * definitions.MICROSECONDS_PER_SECOND)
[docs] def CopyFromStringTuple(self, time_elements_tuple): """Copies time elements from string-based time elements tuple. Args: time_elements_tuple (Optional[tuple[str, str, str, str, str, str, str]]): time elements, contains year, month, day of month, hours, minutes, seconds and microseconds. Raises: ValueError: if the time elements tuple is invalid. """ number_of_elements = len(time_elements_tuple) if len(time_elements_tuple) < 7: raise ValueError(( f'Invalid time elements tuple at least 7 elements required,' f'got: {number_of_elements:d}')) year, month, day_of_month, hours, minutes, seconds, microseconds = ( time_elements_tuple) try: microseconds = int(microseconds, 10) except (TypeError, ValueError): raise ValueError(f'Invalid microsecond value: {microseconds!s}') if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND: raise ValueError('Invalid number of microseconds.') fraction_of_second = ( decimal.Decimal(microseconds) / definitions.MICROSECONDS_PER_SECOND) time_elements_tuple = ( year, month, day_of_month, hours, minutes, seconds, str(fraction_of_second)) super(TimeElementsInMicroseconds, self).CopyFromStringTuple( time_elements_tuple)
[docs] def NewFromDeltaAndDate(self, year, month, day_of_month): """Creates a new time elements instance from a date time delta and a year. Args: year (int): year. month (int): month, where 1 represents January and 0 if not set. day_of_month (int): day of month, where 1 represents the first day and 0 if not set. Returns: TimeElementsInMicroseconds: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ if not self._is_delta: raise ValueError('Not a date time delta.') if self._time_elements_tuple is None: return None delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = ( self._time_elements_tuple) time_elements_tuple = ( year + delta_year, month + delta_month, day_of_month + delta_day_of_month, hours, minutes, seconds, self.microseconds) return TimeElementsInMicroseconds( precision=self._precision, time_elements_tuple=time_elements_tuple, time_zone_offset=self._time_zone_offset)
[docs] def NewFromDeltaAndYear(self, year): """Creates a new time elements instance from a date time delta and a year. Args: year (int): year. Returns: TimeElementsInMicroseconds: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ return self.NewFromDeltaAndDate(year, 0, 0)
[docs] class TimeElementsInNanoseconds(TimeElementsWithFractionOfSecond): """Time elements in nanoseconds. Attributes: fraction_of_second (decimal.Decimal): fraction of second, which must be a value between 0.0 and 1.0. is_local_time (bool): True if the date and time value is in local time. precision (str): precision of the date of the date and time value, that represents 1 nanosecond (PRECISION_1_NANOSECOND). """
[docs] def __init__( self, is_delta=False, precision=None, time_elements_tuple=None, time_zone_offset=None): """Initializes time elements. Args: is_delta (Optional[bool]): True if the date and time value is relative to another date and time value. precision (Optional[str]): precision of the date and time value, which should be one of the PRECISION_VALUES in definitions. time_elements_tuple (Optional[tuple[int, int, int, int, int, int, int]]): time elements, contains year, month, day of month, hours, minutes, seconds and nanoseconds. time_zone_offset (Optional[int]): time zone offset in number of minutes from UTC or None if not set. Raises: ValueError: if the time elements tuple is invalid. """ fraction_of_second = None if time_elements_tuple: number_of_elements = len(time_elements_tuple) if number_of_elements < 7: raise ValueError(( f'Invalid time elements tuple at least 7 elements required,' f'got: {number_of_elements:d}')) nanoseconds = time_elements_tuple[6] time_elements_tuple = time_elements_tuple[:6] if (nanoseconds < 0 or nanoseconds >= definitions.NANOSECONDS_PER_SECOND): raise ValueError('Invalid number of nanoseconds.') fraction_of_second = ( decimal.Decimal(nanoseconds) / definitions.NANOSECONDS_PER_SECOND) super(TimeElementsInNanoseconds, self).__init__( fraction_of_second=fraction_of_second, is_delta=is_delta, precision=precision or definitions.PRECISION_1_NANOSECOND, time_elements_tuple=time_elements_tuple, time_zone_offset=time_zone_offset)
@property def nanoseconds(self): """int: number of nanoseconds.""" return int(self.fraction_of_second * definitions.NANOSECONDS_PER_SECOND)
[docs] def CopyFromStringTuple(self, time_elements_tuple): """Copies time elements from string-based time elements tuple. Args: time_elements_tuple (Optional[tuple[str, str, str, str, str, str, str]]): time elements, contains year, month, day of month, hours, minutes, seconds and nanoseconds. Raises: ValueError: if the time elements tuple is invalid. """ number_of_elements = len(time_elements_tuple) if len(time_elements_tuple) < 7: raise ValueError(( f'Invalid time elements tuple at least 7 elements required,' f'got: {number_of_elements:d}')) year, month, day_of_month, hours, minutes, seconds, nanoseconds = ( time_elements_tuple) try: nanoseconds = int(nanoseconds, 10) except (TypeError, ValueError): raise ValueError(f'Invalid nanosecond value: {nanoseconds!s}') if nanoseconds < 0 or nanoseconds >= definitions.NANOSECONDS_PER_SECOND: raise ValueError('Invalid number of nanoseconds.') fraction_of_second = ( decimal.Decimal(nanoseconds) / definitions.NANOSECONDS_PER_SECOND) time_elements_tuple = ( year, month, day_of_month, hours, minutes, seconds, str(fraction_of_second)) super(TimeElementsInNanoseconds, self).CopyFromStringTuple( time_elements_tuple)
[docs] def NewFromDeltaAndDate(self, year, month, day_of_month): """Creates a new time elements instance from a date time delta and a year. Args: year (int): year. month (int): month, where 1 represents January and 0 if not set. day_of_month (int): day of month, where 1 represents the first day and 0 if not set. Returns: TimeElementsInNanoseconds: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ if not self._is_delta: raise ValueError('Not a date time delta.') if self._time_elements_tuple is None: return None delta_year, delta_month, delta_day_of_month, hours, minutes, seconds = ( self._time_elements_tuple) time_elements_tuple = ( year + delta_year, month + delta_month, day_of_month + delta_day_of_month, hours, minutes, seconds, self.nanoseconds) return TimeElementsInNanoseconds( precision=self._precision, time_elements_tuple=time_elements_tuple, time_zone_offset=self._time_zone_offset)
[docs] def NewFromDeltaAndYear(self, year): """Creates a new time elements instance from a date time delta and a year. Args: year (int): year. Returns: TimeElementsInNanoseconds: time elements or None if time elements are missing. Raises: ValueError: if the instance is not a date time delta. """ return self.NewFromDeltaAndDate(year, 0, 0)
factory.Factory.RegisterDateTimeValues(TimeElements) factory.Factory.RegisterDateTimeValues(TimeElementsInMilliseconds) factory.Factory.RegisterDateTimeValues(TimeElementsInMicroseconds) factory.Factory.RegisterDateTimeValues(TimeElementsInNanoseconds)