# -*- coding: utf-8 -*-
"""SYSTEMTIME structure implementation."""
import decimal
from dfdatetime import definitions
from dfdatetime import factory
from dfdatetime import interface
[docs]
class Systemtime(interface.DateTimeValues):
"""SYSTEMTIME structure.
The SYSTEMTIME structure is 16 bytes of size and contains:
struct {
WORD year,
WORD month,
WORD day_of_week,
WORD day_of_month,
WORD hour,
WORD minute,
WORD second,
WORD millisecond
}
"""
[docs]
def __init__(
self, precision=None, system_time_tuple=None, time_zone_offset=None):
"""Initializes a SYSTEMTIME structure.
Args:
precision (Optional[str]): precision of the date and time value, which
should be one of the PRECISION_VALUES in definitions.
system_time_tuple
(Optional[tuple[int, int, int, int, int, int, int, int]]):
system time, contains year, month, day of week, 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 system time is invalid.
"""
super(Systemtime, self).__init__(
precision=precision or definitions.PRECISION_1_MILLISECOND,
time_zone_offset=time_zone_offset)
self._number_of_seconds = None
self._day_of_month = None
self._day_of_week = None
self._hours = None
self._milliseconds = None
self._minutes = None
self._month = None
self._seconds = None
self._year = None
if system_time_tuple:
if len(system_time_tuple) < 8:
raise ValueError('Invalid system time tuple 8 elements required.')
if system_time_tuple[0] < 1601 or system_time_tuple[0] > 30827:
raise ValueError('Year value out of bounds.')
if system_time_tuple[1] not in range(1, 13):
raise ValueError('Month value out of bounds.')
if system_time_tuple[2] not in range(0, 7):
raise ValueError('Day of week value out of bounds.')
days_per_month = self._GetDaysPerMonth(
system_time_tuple[0], system_time_tuple[1])
if system_time_tuple[3] < 1 or system_time_tuple[3] > days_per_month:
raise ValueError('Day of month value out of bounds.')
if system_time_tuple[4] not in range(0, 24):
raise ValueError('Hours value out of bounds.')
if system_time_tuple[5] not in range(0, 60):
raise ValueError('Minutes value out of bounds.')
# TODO: support a leap second?
if system_time_tuple[6] not in range(0, 60):
raise ValueError('Seconds value out of bounds.')
if system_time_tuple[7] < 0 or system_time_tuple[7] > 999:
raise ValueError('Milliseconds value out of bounds.')
self._day_of_month = system_time_tuple[3]
self._day_of_week = system_time_tuple[2]
self._hours = system_time_tuple[4]
self._milliseconds = system_time_tuple[7]
self._minutes = system_time_tuple[5]
self._month = system_time_tuple[1]
self._seconds = system_time_tuple[6]
self._year = system_time_tuple[0]
self._number_of_seconds = self._GetNumberOfSecondsFromElements(
self._year, self._month, self._day_of_month, self._hours,
self._minutes, self._seconds)
@property
def day_of_month(self):
"""day_of_month (int): day of month, 1 through 31."""
return self._day_of_month
@property
def day_of_week(self):
"""day_of_week (int): day of week, 0 through 6."""
return self._day_of_week
@property
def hours(self):
"""Hours (int): hours, 0 through 23."""
return self._hours
@property
def milliseconds(self):
"""Milliseconds (int): milliseconds, 0 through 999."""
return self._milliseconds
@property
def minutes(self):
"""Minutes (int): minutes, 0 through 59."""
return self._minutes
@property
def month(self):
"""Month (int): month of year, 1 through 12."""
return self._month
@property
def seconds(self):
"""Seconds (int): seconds, 0 through 59."""
return self._seconds
@property
def year(self):
"""Year (int): year, 1601 through 30827."""
return self._year
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._milliseconds) /
definitions.MILLISECONDS_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
[docs]
def CopyFromDateTimeString(self, time_string):
"""Copies a SYSTEMTIME structure 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)
milliseconds, _ = divmod(
nanoseconds, definitions.NANOSECONDS_PER_MILLISECOND)
if year < 1601 or year > 30827:
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
# TODO: calculate day of week on demand.
self._day_of_week = None
self._hours = hours
self._minutes = minutes
self._seconds = seconds
self._milliseconds = milliseconds
[docs]
def CopyToDateTimeString(self):
"""Copies the SYSTEMTIME structure 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._milliseconds:03d}')
factory.Factory.RegisterDateTimeValues(Systemtime)