# -*- coding: utf-8 -*-
"""Date and time interfaces."""
import abc
import decimal
from dfdatetime import definitions
[docs]
class DateTimeEpoch(object):
"""Date and time epoch interface.
This is the super class of different epoch representations.
Attributes:
year (int): year that is the start of the epoch e.g. 1970.
month (int): month that is the start of the epoch, where 1 represents
January.
day_of_month (int): day of the month that is the start of the epoch,
where 1 represents the first day.
"""
[docs]
def __init__(self, year, month, day_of_month):
"""Initializes a date time epoch.
Args:
year (int): year that is the start of the epoch e.g. 1970.
month (int): month that is the start of the epoch, where 1 represents
January.
day_of_month (int): day of the month that is the start of the epoch,
where 1 represents the first day.
"""
super(DateTimeEpoch, self).__init__()
self.day_of_month = day_of_month
self.month = month
self.year = year
[docs]
class NormalizedTimeEpoch(DateTimeEpoch):
"""dfDateTime normalized time epoch."""
[docs]
def __init__(self):
"""Initializes a dfDateTime normalized time epoch."""
super(NormalizedTimeEpoch, self).__init__(1970, 1, 1)
[docs]
class DateTimeValues(object):
"""Date and time values interface.
This is the super class of different date and time representations.
Attributes:
is_local_time (bool): True if the date and time value is in local time.
time_zone_hint (str): time zone hint, such as "Europe/Amsterdam", "CET" or
"UTC+1", or None if not set.
"""
# pylint: disable=redundant-returns-doc
_EPOCH_NORMALIZED_TIME = NormalizedTimeEpoch()
_100_MILLISECONDS_PER_SECOND = 10
_10_MILLISECONDS_PER_SECOND = 100
_1_MILLISECOND_PER_SECOND = 1000
_100_MICROSECONDS_PER_SECOND = 10000
_10_MICROSECONDS_PER_SECOND = 100000
_1_MICROSECOND_PER_SECOND = 1000000
_100_NANOSECONDS_PER_SECOND = 10000000
_10_NANOSECONDS_PER_SECOND = 100000000
_1_NANOSECOND_PER_SECOND = definitions.NANOSECONDS_PER_SECOND
_100_NANOSECONDS_PER_MICROSECOND = 10
_INT64_MIN = -(1 << 63)
_INT64_MAX = (1 << 63) - 1
_UINT32_MAX = (1 << 32) - 1
_UINT60_MAX = (1 << 60) - 1
_UINT64_MAX = (1 << 64) - 1
_REMAINDER_MULTIPLIER = {
definitions.PRECISION_1_MILLISECOND: _1_MILLISECOND_PER_SECOND,
definitions.PRECISION_10_MILLISECONDS: _10_MILLISECONDS_PER_SECOND,
definitions.PRECISION_100_MILLISECONDS: _100_MILLISECONDS_PER_SECOND,
definitions.PRECISION_1_MICROSECOND: _1_MICROSECOND_PER_SECOND,
definitions.PRECISION_10_MICROSECONDS: _10_MICROSECONDS_PER_SECOND,
definitions.PRECISION_100_MICROSECONDS: _100_MICROSECONDS_PER_SECOND,
definitions.PRECISION_1_NANOSECOND: _1_NANOSECOND_PER_SECOND,
definitions.PRECISION_10_NANOSECONDS: _10_NANOSECONDS_PER_SECOND,
definitions.PRECISION_100_NANOSECONDS: _100_NANOSECONDS_PER_SECOND}
[docs]
def __init__(self, is_delta=False, precision=None, time_zone_offset=None):
"""Initializes date time values.
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_zone_offset (Optional[int]): time zone offset in number of minutes
from UTC or None if not set.
"""
super(DateTimeValues, self).__init__()
self._cached_date_time_values = None
self._is_delta = is_delta
self._normalized_timestamp = None
self._precision = precision
self._time_zone_offset = time_zone_offset
self.is_local_time = False
self.time_zone_hint = False
@property
def is_delta(self):
"""Is delta (bool): True if the date and time is relative to another."""
return self._is_delta
@property
def precision(self):
"""Precision (str): precision of the date and time value."""
return self._precision
@property
def time_zone_offset(self):
"""Time zone offset (int): time zone offset in minutes from UTC."""
return self._time_zone_offset
@time_zone_offset.setter
def time_zone_offset(self, time_zone_offset):
"""Sets the time zone offset.
Args:
time_zone_offset (int): time zone offset in number of minutes from UTC or
None if not set.
"""
self._normalized_timestamp = None
self._time_zone_offset = time_zone_offset
[docs]
def __eq__(self, other):
"""Determines if the date time values are equal to other.
Args:
other (DateTimeValues): date time values to compare against.
Returns:
bool: True if the date time values are equal to other.
"""
if not isinstance(other, DateTimeValues):
return False
normalized_timestamp = self._GetNormalizedTimestamp()
other_normalized_timestamp = other._GetNormalizedTimestamp() # pylint: disable=protected-access
if normalized_timestamp is None and other_normalized_timestamp is not None:
return False
if normalized_timestamp is not None and other_normalized_timestamp is None:
return False
return normalized_timestamp == other_normalized_timestamp
[docs]
def __ge__(self, other):
"""Determines if the date time values are greater than or equal to other.
Args:
other (DateTimeValues): date time values to compare against.
Returns:
bool: True if the date time values are greater than or equal to other.
Raises:
ValueError: if other is not an instance of DateTimeValues.
"""
if not isinstance(other, DateTimeValues):
raise ValueError('Other not an instance of DateTimeValues')
normalized_timestamp = self._GetNormalizedTimestamp()
other_normalized_timestamp = other._GetNormalizedTimestamp() # pylint: disable=protected-access
if normalized_timestamp is None:
return other_normalized_timestamp is None
if other_normalized_timestamp is None:
return True
return normalized_timestamp >= other_normalized_timestamp
[docs]
def __gt__(self, other):
"""Determines if the date time values are greater than other.
Args:
other (DateTimeValues): date time values to compare against.
Returns:
bool: True if the date time values are greater than other.
Raises:
ValueError: if other is not an instance of DateTimeValues.
"""
if not isinstance(other, DateTimeValues):
raise ValueError('Other not an instance of DateTimeValues')
normalized_timestamp = self._GetNormalizedTimestamp()
other_normalized_timestamp = other._GetNormalizedTimestamp() # pylint: disable=protected-access
if normalized_timestamp is None:
return False
if other_normalized_timestamp is None:
return True
return normalized_timestamp > other_normalized_timestamp
[docs]
def __le__(self, other):
"""Determines if the date time values are greater than or equal to other.
Args:
other (DateTimeValues): date time values to compare against.
Returns:
bool: True if the date time values are greater than or equal to other.
Raises:
ValueError: if other is not an instance of DateTimeValues.
"""
if not isinstance(other, DateTimeValues):
raise ValueError('Other not an instance of DateTimeValues')
normalized_timestamp = self._GetNormalizedTimestamp()
other_normalized_timestamp = other._GetNormalizedTimestamp() # pylint: disable=protected-access
if normalized_timestamp is None:
return True
if other_normalized_timestamp is None:
return False
return normalized_timestamp <= other_normalized_timestamp
[docs]
def __lt__(self, other):
"""Determines if the date time values are less than other.
Args:
other (DateTimeValues): date time values to compare against.
Returns:
bool: True if the date time values are less than other.
Raises:
ValueError: if other is not an instance of DateTimeValues.
"""
if not isinstance(other, DateTimeValues):
raise ValueError('Other not an instance of DateTimeValues')
normalized_timestamp = self._GetNormalizedTimestamp()
other_normalized_timestamp = other._GetNormalizedTimestamp() # pylint: disable=protected-access
if normalized_timestamp is None:
return other_normalized_timestamp is not None
if other_normalized_timestamp is None:
return False
return normalized_timestamp < other_normalized_timestamp
[docs]
def __ne__(self, other):
"""Determines if the date time values are not equal to other.
Args:
other (DateTimeValues): date time values to compare against.
Returns:
bool: True if the date time values are not equal to other.
"""
if not isinstance(other, DateTimeValues):
return True
normalized_timestamp = self._GetNormalizedTimestamp()
other_normalized_timestamp = other._GetNormalizedTimestamp() # pylint: disable=protected-access
if normalized_timestamp is None and other_normalized_timestamp is not None:
return True
if normalized_timestamp is not None and other_normalized_timestamp is None:
return True
return normalized_timestamp != other_normalized_timestamp
def _CopyDateFromString(self, date_string):
"""Copies a date from a string.
Args:
date_string (str): date value formatted as: YYYY-MM-DD
Returns:
tuple[int, int, int]: year, month, day of month.
Raises:
ValueError: if the date string is invalid or not supported.
"""
date_string_length = len(date_string)
# The date string should at least contain 'YYYY-MM-DD'.
if date_string_length < 10:
raise ValueError('Date string too short.')
if date_string[4] != '-' or date_string[7] != '-':
raise ValueError('Invalid date string.')
try:
year = int(date_string[0:4], 10)
except ValueError:
raise ValueError('Unable to parse year.')
try:
month = int(date_string[5:7], 10)
except ValueError:
raise ValueError('Unable to parse month.')
try:
day_of_month = int(date_string[8:10], 10)
except ValueError:
raise ValueError('Unable to parse day of month.')
days_per_month = self._GetDaysPerMonth(year, month)
if day_of_month < 1 or day_of_month > days_per_month:
raise ValueError('Day of month value out of bounds.')
return year, month, day_of_month
def _CopyDateTimeFromString(self, time_string):
"""Copies a date and time from a 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.
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-DD hh:mm:ss'.
if time_string[10] != ' ':
raise ValueError(
'Invalid time string - space missing as date and time separator.')
hours, minutes, seconds, nanoseconds, time_zone_offset = (
self._CopyTimeFromString(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 _CopyTimeFromString(self, time_string):
"""Copies a time from a 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 seconds fraction 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.
"""
time_string_length = len(time_string)
# The time string should at least contain 'hh:mm:ss'.
if time_string_length < 8:
raise ValueError('Time string too short.')
if time_string[2] != ':' or time_string[5] != ':':
raise ValueError('Invalid time string.')
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.')
try:
seconds = int(time_string[6:8], 10)
except ValueError:
raise ValueError('Unable to parse day of seconds.')
# TODO: support a leap second?
if seconds not in range(0, 60):
raise ValueError(f'Seconds value: {seconds:d} out of bounds.')
nanoseconds = None
time_zone_offset = None
time_zone_string_index = 8
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 > 8 and time_string[8] == '.':
time_fraction_length = time_zone_string_index - 9
if time_fraction_length not in (3, 6, 9):
raise ValueError('Invalid time string.')
try:
time_fraction = time_string[9:time_zone_string_index]
time_fraction = int(time_fraction, 10)
except ValueError:
raise ValueError('Unable to parse time fraction.')
if time_fraction_length == 3:
time_fraction *= 1000000
elif time_fraction_length == 6:
time_fraction *= 1000
nanoseconds = time_fraction
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 _GetDateValues(
self, number_of_days, epoch_year, epoch_month, epoch_day_of_month):
"""Determines date values.
Args:
number_of_days (int): number of days since epoch.
epoch_year (int): year that is the start of the epoch e.g. 1970.
epoch_month (int): month that is the start of the epoch, where
1 represents January.
epoch_day_of_month (int): day of month that is the start of the epoch,
where 1 represents the first day.
Returns:
tuple[int, int, int]: year, month, day of month.
Raises:
ValueError: if the epoch year, month or day of month values are out
of bounds.
"""
if epoch_year < 0:
raise ValueError(f'Epoch year value: {epoch_year:d} out of bounds.')
if epoch_month not in range(1, 13):
raise ValueError(f'Epoch month value: {epoch_month:d} out of bounds.')
epoch_days_per_month = self._GetDaysPerMonth(epoch_year, epoch_month)
if epoch_day_of_month < 1 or epoch_day_of_month > epoch_days_per_month:
raise ValueError(
f'Epoch day of month value: {epoch_day_of_month:d} out of bounds.')
before_epoch = number_of_days < 0
year = epoch_year
month = epoch_month
if before_epoch:
month -= 1
if month <= 0:
month = 12
year -= 1
number_of_days += epoch_day_of_month
if before_epoch:
number_of_days *= -1
# Align with the start of the year.
while month > 1:
days_per_month = self._GetDaysPerMonth(year, month)
if number_of_days < days_per_month:
break
if before_epoch:
month -= 1
else:
month += 1
if month > 12:
month = 1
year += 1
number_of_days -= days_per_month
# Align with the start of the next century.
_, remainder = divmod(year, 100)
for _ in range(remainder, 100):
days_in_year = self._GetNumberOfDaysInYear(year)
if number_of_days < days_in_year:
break
if before_epoch:
year -= 1
else:
year += 1
number_of_days -= days_in_year
days_in_century = self._GetNumberOfDaysInCentury(year)
while number_of_days > days_in_century:
if before_epoch:
year -= 100
else:
year += 100
number_of_days -= days_in_century
days_in_century = self._GetNumberOfDaysInCentury(year)
days_in_year = self._GetNumberOfDaysInYear(year)
while number_of_days > days_in_year:
if before_epoch:
year -= 1
else:
year += 1
number_of_days -= days_in_year
days_in_year = self._GetNumberOfDaysInYear(year)
days_per_month = self._GetDaysPerMonth(year, month)
while number_of_days > days_per_month:
if before_epoch:
month -= 1
else:
month += 1
if month <= 0:
month = 12
year -= 1
elif month > 12:
month = 1
year += 1
number_of_days -= days_per_month
days_per_month = self._GetDaysPerMonth(year, month)
if before_epoch:
days_per_month = self._GetDaysPerMonth(year, month)
number_of_days = days_per_month - number_of_days
elif number_of_days == 0:
number_of_days = 31
month = 12
year -= 1
return year, month, number_of_days
def _GetDateValuesWithEpoch(self, number_of_days, date_time_epoch):
"""Determines date values.
Args:
number_of_days (int): number of days since epoch.
date_time_epoch (DateTimeEpoch): date and time of the epoch.
Returns:
tuple[int, int, int]: year, month, day of month.
"""
return self._GetDateValues(
number_of_days, date_time_epoch.year, date_time_epoch.month,
date_time_epoch.day_of_month)
def _GetDateWithTimeOfDay(self):
"""Retrieves the date with time of day.
Note that the date and time are adjusted to UTC.
Returns:
tuple[int, int, int, int, int, int]: year, month, day of month, hours,
minutes, seconds or (None, None, None, None, None, None)
if the date and time values do not represent a date or time of day.
"""
normalized_timestamp = self._GetNormalizedTimestamp()
if normalized_timestamp is None:
return None, None, None, None, None, None
if (not self._cached_date_time_values or
self._cached_date_time_values[0] != normalized_timestamp):
number_of_days, hours, minutes, seconds = self._GetTimeValues(
normalized_timestamp)
try:
year, month, day_of_month = self._GetDateValuesWithEpoch(
number_of_days, self._EPOCH_NORMALIZED_TIME)
except ValueError:
return None, None, None, None, None, None
self._cached_date_time_values = (
normalized_timestamp, year, month, day_of_month, hours, minutes,
seconds)
return self._cached_date_time_values[1:]
def _GetDayOfYear(self, year, month, day_of_month):
"""Retrieves the day of the year for a specific day of a month in a year.
Args:
year (int): year e.g. 1970.
month (int): month, where 1 represents January.
day_of_month (int): day of the month, where 1 represents the first day.
Returns:
int: day of year.
Raises:
ValueError: if the month or day of month value is out of bounds.
"""
if month not in range(1, 13):
raise ValueError('Month value out of bounds.')
days_per_month = self._GetDaysPerMonth(year, month)
if day_of_month < 1 or day_of_month > days_per_month:
raise ValueError('Day of month value out of bounds.')
day_of_year = day_of_month
for past_month in range(1, month):
day_of_year += self._GetDaysPerMonth(year, past_month)
return day_of_year
def _GetDaysPerMonth(self, year, month):
"""Retrieves the number of days in a month of a specific year.
Args:
year (int): year e.g. 1970.
month (int): month, where 1 represents January.
Returns:
int: number of days in the month.
Raises:
ValueError: if the month value is out of bounds.
"""
if month not in range(1, 13):
raise ValueError('Month value out of bounds.')
days_per_month = definitions.DAYS_PER_MONTH[month - 1]
if month == 2 and (self._is_delta or self._IsLeapYear(year)):
days_per_month += 1
return days_per_month
@abc.abstractmethod
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.
"""
def _GetNumberOfDaysInCentury(self, year):
"""Retrieves the number of days in a century.
Args:
year (int): year in the century e.g. 1970.
Returns:
int: number of (remaining) days in the century.
Raises:
ValueError: if the year value is out of bounds.
"""
if year < 0:
raise ValueError('Year value out of bounds.')
year, _ = divmod(year, 100)
year *= 100
number_of_days = definitions.DAYS_PER_CENTURY.get(year, None)
if number_of_days is not None:
return number_of_days
if self._IsLeapYear(year):
return 36525
return 36524
def _GetNumberOfDaysInYear(self, year):
"""Retrieves the number of days in a specific year.
Args:
year (int): year e.g. 1970.
Returns:
int: number of days in the year.
"""
number_of_days = definitions.DAYS_PER_YEAR.get(year, None)
if number_of_days is not None:
return number_of_days
if self._IsLeapYear(year):
return 366
return 365
def _GetNumberOfSecondsFromElements(
self, year, month, day_of_month, hours, minutes, seconds):
"""Retrieves the number of seconds from the date and time elements.
Args:
year (int): year e.g. 1970.
month (int): month, where 1 represents January.
day_of_month (int): day of the month, where 1 represents the first day.
hours (int): hours.
minutes (int): minutes.
seconds (int): seconds.
Returns:
int: number of seconds since January 1, 1970 00:00:00 or None if year,
month or day of month are not set.
Raises:
ValueError: if the time elements are invalid.
"""
if not month or not day_of_month:
return None
if hours is None:
hours = 0
elif hours not in range(0, 24):
raise ValueError(f'Hours value: {hours!s} out of bounds.')
if minutes is None:
minutes = 0
elif minutes not in range(0, 60):
raise ValueError(f'Minutes value: {minutes!s} out of bounds.')
# TODO: support a leap second?
if seconds is None:
seconds = 0
elif seconds not in range(0, 60):
raise ValueError(f'Seconds value: {seconds!s} out of bounds.')
number_of_days = definitions.DAYS_PER_YEAR_IN_POSIX_EPOCH.get(year, None)
if number_of_days is None:
raise ValueError(f'Year value: {year!s} out of bounds.')
number_of_days += sum(
definitions.DAYS_PER_MONTH[index] for index in range(month - 1))
if month > 2 and self._IsLeapYear(year):
number_of_days += 1
days_per_month = self._GetDaysPerMonth(year, month)
if day_of_month < 1 or day_of_month > days_per_month:
raise ValueError(f'Day of month value: {day_of_month:d} out of bounds.')
number_of_days += day_of_month - 1
number_of_hours = (number_of_days * 24) + hours
number_of_minutes = (number_of_hours * 60) + minutes
number_of_seconds = (number_of_minutes * 60) + seconds
return number_of_seconds
def _GetTimeValues(self, number_of_seconds):
"""Determines time values.
Args:
number_of_seconds (int|decimal.Decimal): number of seconds.
Returns:
tuple[int, int, int, int]: days, hours, minutes, seconds.
"""
number_of_seconds = int(number_of_seconds)
number_of_minutes, seconds = divmod(number_of_seconds, 60)
number_of_hours, minutes = divmod(number_of_minutes, 60)
number_of_days, hours = divmod(number_of_hours, 24)
return number_of_days, hours, minutes, seconds
def _IsLeapYear(self, year):
"""Determines if a year is a leap year.
Args:
year (int): year e.g. 1970.
Returns:
bool: True if the year is a leap year.
"""
# pylint: disable=consider-using-ternary
return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
[docs]
@abc.abstractmethod
def CopyFromDateTimeString(self, time_string):
"""Copies a date time value 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 time string is invalid or not supported.
"""
[docs]
def CopyToPosixTimestamp(self):
"""Copies the date time value to a POSIX timestamp.
Returns:
int: a POSIX timestamp in seconds or None if no timestamp is available.
"""
normalized_timestamp = self._GetNormalizedTimestamp()
if normalized_timestamp is None:
return None
return int(normalized_timestamp)
[docs]
def CopyToPosixTimestampWithFractionOfSecond(self):
"""Copies the date time value to a POSIX timestamp with fraction of second.
Returns:
tuple[int, int]: a POSIX timestamp in seconds with fraction of second or
None, None if no timestamp is available.
"""
normalized_timestamp = self._GetNormalizedTimestamp()
if normalized_timestamp is None:
return None, None
remainder_multiplier = self._REMAINDER_MULTIPLIER.get(self._precision, None)
if remainder_multiplier:
remainder = int((normalized_timestamp % 1) * remainder_multiplier)
else:
remainder = None
return int(normalized_timestamp), remainder
[docs]
@abc.abstractmethod
def CopyToDateTimeString(self):
"""Copies the date time value to a date and time string.
Returns:
str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss.######" or
None if the timestamp cannot be copied to a date and time string.
"""
[docs]
def CopyToDateTimeStringISO8601(self):
"""Copies the date time value to an ISO 8601 date and time string.
Returns:
str: date and time value formatted as an ISO 8601 date and time string or
None if the timestamp cannot be copied to a date and time string.
"""
date_time_string = self.CopyToDateTimeString()
if date_time_string:
date_time_string = date_time_string.replace(' ', 'T')
if self._time_zone_offset is not None or not self.is_local_time:
time_zone_offset_hours, time_zone_offset_minutes = divmod(
self._time_zone_offset or 0, 60)
if time_zone_offset_hours >= 0:
time_zone_offset_sign = '+'
else:
time_zone_offset_sign = '-'
time_zone_offset_hours *= -1
time_zone_string = (
f'{time_zone_offset_hours:02d}:{time_zone_offset_minutes:02d}')
date_time_string = time_zone_offset_sign.join([
date_time_string, time_zone_string])
return date_time_string
[docs]
def GetDate(self):
"""Retrieves the date represented by the date and time values.
Note that the date is adjusted to UTC.
Returns:
tuple[int, int, int]: year, month, day of month or (None, None, None)
if the date and time values do not represent a date.
"""
year, month, day_of_month, _, _, _ = self._GetDateWithTimeOfDay()
return year, month, day_of_month
[docs]
def GetDateWithTimeOfDay(self):
"""Retrieves the date with time of day.
Note that the date and time are adjusted to UTC.
Returns:
tuple[int, int, int, int, int, int]: year, month, day of month, hours,
minutes, seconds or (None, None, None, None, None, None)
if the date and time values do not represent a date or time of day.
"""
return self._GetDateWithTimeOfDay()
# TODO: remove this method when there is no more need for it in Plaso.
[docs]
def GetPlasoTimestamp(self):
"""Retrieves a timestamp that is compatible with Plaso.
Returns:
int: a POSIX timestamp in microseconds or None if no timestamp is
available.
Raises:
ValueError: if the timestamp cannot be determined.
"""
normalized_timestamp = self._GetNormalizedTimestamp()
if normalized_timestamp is None:
return None
normalized_timestamp *= definitions.MICROSECONDS_PER_SECOND
try:
normalized_timestamp = normalized_timestamp.quantize(
1, rounding=decimal.ROUND_HALF_UP)
except decimal.InvalidOperation as exception:
raise ValueError(
f'Unable to round normalized timestamp with error: {exception!s}')
return int(normalized_timestamp)
[docs]
def GetTimeOfDay(self):
"""Retrieves the time of day represented by the date and time values.
Note that the time is adjusted to UTC.
Returns:
tuple[int, int, int]: hours, minutes, seconds or (None, None, None)
if the date and time values do not represent a time of day.
"""
_, _, _, hours, minutes, seconds = self._GetDateWithTimeOfDay()
return hours, minutes, seconds