diff --git a/.github/workflows/test_tox.yml b/.github/workflows/test_tox.yml index 4f4aef6..9e02a93 100644 --- a/.github/workflows/test_tox.yml +++ b/.github/workflows/test_tox.yml @@ -60,8 +60,8 @@ jobs: strategy: matrix: include: - - python-version: '3.11' - toxenv: 'py311,coverage' + - python-version: '3.10' + toxenv: 'coverage' container: image: ubuntu:22.04 steps: diff --git a/config/dpkg/changelog b/config/dpkg/changelog index 3eaefe9..3a182e5 100644 --- a/config/dpkg/changelog +++ b/config/dpkg/changelog @@ -1,5 +1,5 @@ -dfdatetime (20231205-1) unstable; urgency=low +dfdatetime (20231210-1) unstable; urgency=low * Auto-generated - -- Log2Timeline maintainers Tue, 05 Dec 2023 05:27:13 +0100 + -- Log2Timeline maintainers Sun, 10 Dec 2023 10:02:43 +0100 diff --git a/dfdatetime/__init__.py b/dfdatetime/__init__.py index f1f1aa6..3c9766a 100644 --- a/dfdatetime/__init__.py +++ b/dfdatetime/__init__.py @@ -25,4 +25,4 @@ from dfdatetime import webkit_time -__version__ = '20231205' +__version__ = '20231210' diff --git a/dfdatetime/apfs_time.py b/dfdatetime/apfs_time.py index b29bcd7..c074042 100644 --- a/dfdatetime/apfs_time.py +++ b/dfdatetime/apfs_time.py @@ -47,7 +47,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. diff --git a/dfdatetime/cocoa_time.py b/dfdatetime/cocoa_time.py index 90c3c8b..17cb853 100644 --- a/dfdatetime/cocoa_time.py +++ b/dfdatetime/cocoa_time.py @@ -78,7 +78,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -93,7 +93,7 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', None) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) timestamp = self._GetNumberOfSecondsFromElements( @@ -101,8 +101,7 @@ def CopyFromDateTimeString(self, time_string): timestamp += self._COCOA_TO_POSIX_BASE timestamp = float(timestamp) - if microseconds is not None: - timestamp += float(microseconds) / definitions.MICROSECONDS_PER_SECOND + timestamp += float(nanoseconds) / definitions.NANOSECONDS_PER_SECOND self._normalized_timestamp = None self._timestamp = timestamp diff --git a/dfdatetime/definitions.py b/dfdatetime/definitions.py index 071677f..46200f4 100644 --- a/dfdatetime/definitions.py +++ b/dfdatetime/definitions.py @@ -9,7 +9,7 @@ DAYS_PER_MONTH = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) -SECONDS_PER_DAY = 24 * 60 * 60 +SECONDS_PER_DAY = 86400 DECISECONDS_PER_SECOND = 10 @@ -20,8 +20,11 @@ MICROSECONDS_PER_DECISECOND = 100000 MICROSECONDS_PER_MILLISECOND = 1000 -NANOSECONDS_PER_MICROSECOND = 1000 +NANOSECONDS_PER_DAY = 86400000000000 NANOSECONDS_PER_SECOND = 1000000000 +NANOSECONDS_PER_DECISECOND = 100000000 +NANOSECONDS_PER_MILLISECOND = 1000000 +NANOSECONDS_PER_MICROSECOND = 1000 PRECISION_1_DAY = '1d' PRECISION_1_HOUR = '1h' diff --git a/dfdatetime/delphi_date_time.py b/dfdatetime/delphi_date_time.py index 16a88a3..a844599 100644 --- a/dfdatetime/delphi_date_time.py +++ b/dfdatetime/delphi_date_time.py @@ -86,7 +86,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -101,7 +101,7 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', None) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) if year > 9999: @@ -112,8 +112,7 @@ def CopyFromDateTimeString(self, time_string): timestamp = float(timestamp) / definitions.SECONDS_PER_DAY timestamp += self._DELPHI_TO_POSIX_BASE - if microseconds is not None: - timestamp += float(microseconds) / definitions.MICROSECONDS_PER_DAY + timestamp += float(nanoseconds) / definitions.NANOSECONDS_PER_DAY self._normalized_timestamp = None self._timestamp = timestamp diff --git a/dfdatetime/dotnet_datetime.py b/dfdatetime/dotnet_datetime.py index c76b0f5..73e13c9 100644 --- a/dfdatetime/dotnet_datetime.py +++ b/dfdatetime/dotnet_datetime.py @@ -79,7 +79,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -94,18 +94,19 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', 0) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) if year > 9999: raise ValueError(f'Unsupported year value: {year:d}.') + nanoseconds, _ = divmod(nanoseconds, 100) + timestamp = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) timestamp += self._DOTNET_TO_POSIX_BASE - timestamp *= definitions.MICROSECONDS_PER_SECOND - timestamp += microseconds - timestamp *= self._100_NANOSECONDS_PER_MICROSECOND + timestamp *= self._100_NANOSECONDS_PER_SECOND + timestamp += nanoseconds self._normalized_timestamp = None self._timestamp = timestamp diff --git a/dfdatetime/fake_time.py b/dfdatetime/fake_time.py index 83594db..d65133f 100644 --- a/dfdatetime/fake_time.py +++ b/dfdatetime/fake_time.py @@ -69,7 +69,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. """ @@ -81,12 +81,18 @@ def CopyFromDateTimeString(self, time_string): 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', None) time_zone_offset = date_time_values.get('time_zone_offset', 0) self._normalized_timestamp = None self._number_of_seconds = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) - self._microseconds = date_time_values.get('microseconds', None) + + if nanoseconds is None: + self._microseconds = None + else: + self._microseconds, _ = divmod(nanoseconds, 1000) + self._time_zone_offset = time_zone_offset def CopyToDateTimeString(self): diff --git a/dfdatetime/fat_date_time.py b/dfdatetime/fat_date_time.py index c5f007a..76c9494 100644 --- a/dfdatetime/fat_date_time.py +++ b/dfdatetime/fat_date_time.py @@ -140,7 +140,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -250,7 +250,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -265,20 +265,19 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', 0) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) if year < 1980 or year > (1980 + 0x7f): raise ValueError(f'Year value not supported: {year!s}.') + milliseconds, _ = divmod(nanoseconds, 10000000) + timestamp = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) timestamp -= self._FAT_DATE_TO_POSIX_BASE timestamp *= 100 - - if microseconds: - milliseconds, _ = divmod(microseconds, 10000) - timestamp += milliseconds + timestamp += milliseconds self._timestamp = timestamp self._time_zone_offset = time_zone_offset diff --git a/dfdatetime/filetime.py b/dfdatetime/filetime.py index c29cacc..3fc6127 100644 --- a/dfdatetime/filetime.py +++ b/dfdatetime/filetime.py @@ -83,7 +83,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -98,17 +98,19 @@ def CopyFromDateTimeString(self, time_string): 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', 0) if year < 1601: raise ValueError(f'Year value not supported: {year!s}.') + nanoseconds, _ = divmod(nanoseconds, 100) + timestamp = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) timestamp += self._FILETIME_TO_POSIX_BASE - timestamp *= definitions.MICROSECONDS_PER_SECOND - timestamp += date_time_values.get('microseconds', 0) - timestamp *= self._100_NANOSECONDS_PER_MICROSECOND + timestamp *= self._100_NANOSECONDS_PER_SECOND + timestamp += nanoseconds self._normalized_timestamp = None self._timestamp = timestamp diff --git a/dfdatetime/golang_time.py b/dfdatetime/golang_time.py index c34d25a..54346d8 100644 --- a/dfdatetime/golang_time.py +++ b/dfdatetime/golang_time.py @@ -161,7 +161,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -175,7 +175,7 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', 0) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) if year < 0: @@ -185,7 +185,6 @@ def CopyFromDateTimeString(self, time_string): year, month, day_of_month, hours, minutes, seconds) seconds += self._GOLANG_TO_POSIX_BASE - nanoseconds = microseconds * definitions.NANOSECONDS_PER_MICROSECOND self._normalized_timestamp = None self._number_of_seconds = seconds diff --git a/dfdatetime/hfs_time.py b/dfdatetime/hfs_time.py index 7a24d86..c817c42 100644 --- a/dfdatetime/hfs_time.py +++ b/dfdatetime/hfs_time.py @@ -80,7 +80,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. diff --git a/dfdatetime/interface.py b/dfdatetime/interface.py index e2287eb..1fc57d1 100644 --- a/dfdatetime/interface.py +++ b/dfdatetime/interface.py @@ -342,13 +342,13 @@ def _CopyDateTimeFromString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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, microseconds, time zone offset in minutes. + hours, minutes, seconds, nanoseconds, time zone offset in minutes. Raises: ValueError: if the time string is invalid or not supported. @@ -372,7 +372,7 @@ def _CopyDateTimeFromString(self, time_string): raise ValueError( 'Invalid time string - space missing as date and time separator.') - hours, minutes, seconds, microseconds, time_zone_offset = ( + hours, minutes, seconds, nanoseconds, time_zone_offset = ( self._CopyTimeFromString(time_string[11:])) date_time_values = { @@ -383,8 +383,8 @@ def _CopyDateTimeFromString(self, time_string): 'minutes': minutes, 'seconds': seconds} - if microseconds is not None: - date_time_values['microseconds'] = microseconds + 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 @@ -398,11 +398,11 @@ def _CopyTimeFromString(self, time_string): hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The seconds fraction and + 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, microseconds, + tuple[int, int, int, int, int]: hours, minutes, seconds, nanoseconds, time zone offset in minutes. Raises: @@ -442,7 +442,7 @@ def _CopyTimeFromString(self, time_string): if seconds not in range(0, 60): raise ValueError(f'Seconds value: {seconds:d} out of bounds.') - microseconds = None + nanoseconds = None time_zone_offset = None time_zone_string_index = 8 @@ -459,7 +459,7 @@ def _CopyTimeFromString(self, time_string): 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): + if time_fraction_length not in (3, 6, 9): raise ValueError('Invalid time string.') try: @@ -469,9 +469,11 @@ def _CopyTimeFromString(self, time_string): raise ValueError('Unable to parse time fraction.') if time_fraction_length == 3: + time_fraction *= 1000000 + elif time_fraction_length == 6: time_fraction *= 1000 - microseconds = time_fraction + nanoseconds = time_fraction if time_zone_string_index < time_string_length: if (time_string_length - time_zone_string_index != 6 or @@ -502,7 +504,7 @@ def _CopyTimeFromString(self, time_string): if time_string[time_zone_string_index] == '-': time_zone_offset = -time_zone_offset - return hours, minutes, seconds, microseconds, 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): @@ -870,7 +872,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. diff --git a/dfdatetime/ole_automation_date.py b/dfdatetime/ole_automation_date.py index 4ec9b36..22602b8 100644 --- a/dfdatetime/ole_automation_date.py +++ b/dfdatetime/ole_automation_date.py @@ -84,7 +84,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -99,15 +99,14 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', None) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) timestamp = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) timestamp = float(timestamp) - if microseconds is not None: - timestamp += float(microseconds) / definitions.MICROSECONDS_PER_SECOND + timestamp += float(nanoseconds) / definitions.NANOSECONDS_PER_SECOND timestamp /= definitions.SECONDS_PER_DAY timestamp += self._OLE_AUTOMATION_DATE_TO_POSIX_BASE diff --git a/dfdatetime/posix_time.py b/dfdatetime/posix_time.py index a31fd34..d446bb4 100644 --- a/dfdatetime/posix_time.py +++ b/dfdatetime/posix_time.py @@ -78,7 +78,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. """ @@ -175,7 +175,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. """ @@ -187,17 +187,16 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', 0) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) + milliseconds, _ = divmod( + nanoseconds, definitions.NANOSECONDS_PER_MILLISECOND) + timestamp = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) timestamp *= definitions.MILLISECONDS_PER_SECOND - - if microseconds: - milliseconds, _ = divmod( - microseconds, definitions.MILLISECONDS_PER_SECOND) - timestamp += milliseconds + timestamp += milliseconds self._timestamp = timestamp self._time_zone_offset = time_zone_offset @@ -282,7 +281,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. """ @@ -294,13 +293,16 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', 0) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) + milliseconds, _ = divmod( + nanoseconds, definitions.NANOSECONDS_PER_MICROSECOND) + timestamp = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) timestamp *= definitions.MICROSECONDS_PER_SECOND - timestamp += microseconds + timestamp += milliseconds self._timestamp = timestamp self._time_zone_offset = time_zone_offset @@ -385,7 +387,7 @@ def _CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. """ @@ -397,16 +399,13 @@ def _CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', None) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) timestamp = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) timestamp *= definitions.NANOSECONDS_PER_SECOND - - if microseconds: - nanoseconds = microseconds * definitions.MILLISECONDS_PER_SECOND - timestamp += nanoseconds + timestamp += nanoseconds self._normalized_timestamp = None self._timestamp = timestamp @@ -420,7 +419,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. """ diff --git a/dfdatetime/precisions.py b/dfdatetime/precisions.py index 9a79274..108ca1b 100644 --- a/dfdatetime/precisions.py +++ b/dfdatetime/precisions.py @@ -18,11 +18,11 @@ class DateTimePrecisionHelper(object): # pylint: disable=missing-raises-doc,redundant-returns-doc @classmethod - def CopyMicrosecondsToFractionOfSecond(cls, microseconds): - """Copies the number of microseconds to a fraction of second value. + def CopyNanosecondsToFractionOfSecond(cls, nanoseconds): + """Copies the number of nanoseconds to a fraction of second value. Args: - microseconds (int): number of microseconds. + nanoseconds (int): number of nanoseconds. Returns: decimal.Decimal: fraction of second, which must be a value between 0.0 @@ -52,22 +52,22 @@ class SecondsPrecisionHelper(DateTimePrecisionHelper): """Seconds precision helper.""" @classmethod - def CopyMicrosecondsToFractionOfSecond(cls, microseconds): - """Copies the number of microseconds to a fraction of second value. + def CopyNanosecondsToFractionOfSecond(cls, nanoseconds): + """Copies the number of nanoseconds to a fraction of second value. Args: - microseconds (int): number of microseconds. + nanoseconds (int): number of nanoseconds. Returns: decimal.Decimal: fraction of second, which must be a value between 0.0 and 1.0. For the seconds precision helper this will always be 0.0. Raises: - ValueError: if the number of microseconds is out of bounds. + ValueError: if the number of nanoseconds is out of bounds. """ - if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND: + if nanoseconds < 0 or nanoseconds >= definitions.NANOSECONDS_PER_SECOND: raise ValueError( - f'Number of microseconds value: {microseconds:d} out of bounds.') + f'Number of nanoseconds value: {nanoseconds:d} out of bounds.') return decimal.Decimal(0.0) @@ -103,25 +103,25 @@ class MillisecondsPrecisionHelper(DateTimePrecisionHelper): """Milliseconds precision helper.""" @classmethod - def CopyMicrosecondsToFractionOfSecond(cls, microseconds): - """Copies the number of microseconds to a fraction of second value. + def CopyNanosecondsToFractionOfSecond(cls, nanoseconds): + """Copies the number of nanoseconds to a fraction of second value. Args: - microseconds (int): number of microseconds. + nanoseconds (int): number of nanoseconds. Returns: decimal.Decimal: fraction of second, which must be a value between 0.0 and 1.0. Raises: - ValueError: if the number of microseconds is out of bounds. + ValueError: if the number of nanoseconds is out of bounds. """ - if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND: + if nanoseconds < 0 or nanoseconds >= definitions.NANOSECONDS_PER_SECOND: raise ValueError( - f'Number of microseconds value: {microseconds:d} out of bounds.') + f'Number of nanoseconds value: {nanoseconds:d} out of bounds.') milliseconds, _ = divmod( - microseconds, definitions.MICROSECONDS_PER_MILLISECOND) + nanoseconds, definitions.NANOSECONDS_PER_MILLISECOND) return decimal.Decimal(milliseconds) / definitions.MILLISECONDS_PER_SECOND @classmethod @@ -157,23 +157,25 @@ class MicrosecondsPrecisionHelper(DateTimePrecisionHelper): """Microseconds precision helper.""" @classmethod - def CopyMicrosecondsToFractionOfSecond(cls, microseconds): - """Copies the number of microseconds to a fraction of second value. + def CopyNanosecondsToFractionOfSecond(cls, nanoseconds): + """Copies the number of nanoseconds to a fraction of second value. Args: - microseconds (int): number of microseconds. + nanoseconds (int): number of nanoseconds. Returns: decimal.Decimal: fraction of second, which must be a value between 0.0 and 1.0. Raises: - ValueError: if the number of microseconds is out of bounds. + ValueError: if the number of nanoseconds is out of bounds. """ - if microseconds < 0 or microseconds >= definitions.MICROSECONDS_PER_SECOND: + if nanoseconds < 0 or nanoseconds >= definitions.NANOSECONDS_PER_SECOND: raise ValueError( - f'Number of microseconds value: {microseconds:d} out of bounds.') + f'Number of nanoseconds value: {nanoseconds:d} out of bounds.') + microseconds, _ = divmod( + nanoseconds, definitions.NANOSECONDS_PER_MICROSECOND) return decimal.Decimal(microseconds) / definitions.MICROSECONDS_PER_SECOND @classmethod @@ -205,12 +207,65 @@ def CopyToDateTimeString(cls, time_elements_tuple, fraction_of_second): f'{hours:02d}:{minutes:02d}:{seconds:02d}.{microseconds:06d}') +class NanosecondsPrecisionHelper(DateTimePrecisionHelper): + """Nanoseconds precision helper.""" + + @classmethod + def CopyNanosecondsToFractionOfSecond(cls, nanoseconds): + """Copies the number of nanoseconds to a fraction of second value. + + Args: + nanoseconds (int): number of nanoseconds. + + Returns: + decimal.Decimal: fraction of second, which must be a value between 0.0 and + 1.0. + + Raises: + ValueError: if the number of nanoseconds is out of bounds. + """ + if nanoseconds < 0 or nanoseconds >= definitions.NANOSECONDS_PER_SECOND: + raise ValueError( + f'Number of nanoseconds value: {nanoseconds:d} out of bounds.') + + return decimal.Decimal(nanoseconds) / definitions.NANOSECONDS_PER_SECOND + + @classmethod + def CopyToDateTimeString(cls, time_elements_tuple, fraction_of_second): + """Copies the time elements and fraction of second to a string. + + Args: + time_elements_tuple (tuple[int, int, int, int, int, int]): + time elements, contains year, month, day of month, hours, minutes and + seconds. + fraction_of_second (decimal.Decimal): fraction of second, which must be a + value between 0.0 and 1.0. + + Returns: + str: date and time value formatted as: + YYYY-MM-DD hh:mm:ss.###### + + Raises: + ValueError: if the fraction of second is out of bounds. + """ + 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.') + + year, month, day_of_month, hours, minutes, seconds = time_elements_tuple + nanoseconds = int(fraction_of_second * definitions.NANOSECONDS_PER_SECOND) + + return (f'{year:04d}-{month:02d}-{day_of_month:02d} ' + f'{hours:02d}:{minutes:02d}:{seconds:02d}.{nanoseconds:09d}') + + class PrecisionHelperFactory(object): """Date time precision helper factory.""" _PRECISION_CLASSES = { definitions.PRECISION_1_MICROSECOND: MicrosecondsPrecisionHelper, definitions.PRECISION_1_MILLISECOND: MillisecondsPrecisionHelper, + definitions.PRECISION_1_NANOSECOND: NanosecondsPrecisionHelper, definitions.PRECISION_1_SECOND: SecondsPrecisionHelper} @classmethod diff --git a/dfdatetime/rfc2579_date_time.py b/dfdatetime/rfc2579_date_time.py index e404871..d137466 100644 --- a/dfdatetime/rfc2579_date_time.py +++ b/dfdatetime/rfc2579_date_time.py @@ -193,7 +193,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -208,11 +208,11 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', 0) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) deciseconds, _ = divmod( - microseconds, definitions.MICROSECONDS_PER_DECISECOND) + nanoseconds, definitions.NANOSECONDS_PER_DECISECOND) if year < 0 or year > 65536: raise ValueError(f'Unsupported year value: {year:d}.') diff --git a/dfdatetime/systemtime.py b/dfdatetime/systemtime.py index 760e7b5..d37b135 100644 --- a/dfdatetime/systemtime.py +++ b/dfdatetime/systemtime.py @@ -168,7 +168,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -183,11 +183,11 @@ def CopyFromDateTimeString(self, time_string): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', 0) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) milliseconds, _ = divmod( - microseconds, definitions.MICROSECONDS_PER_MILLISECOND) + nanoseconds, definitions.NANOSECONDS_PER_MILLISECOND) if year < 1601 or year > 30827: raise ValueError(f'Unsupported year value: {year:d}.') diff --git a/dfdatetime/time_elements.py b/dfdatetime/time_elements.py index 4f035c1..b9a3f0c 100644 --- a/dfdatetime/time_elements.py +++ b/dfdatetime/time_elements.py @@ -138,12 +138,12 @@ def _CopyDateTimeFromStringISO8601(self, time_string): hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The fraction of second and + 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, microseconds, time zone offset in minutes. + hours, minutes, seconds, nanoseconds, time zone offset in minutes. Raises: ValueError: if the time string is invalid or not supported. @@ -166,7 +166,7 @@ def _CopyDateTimeFromStringISO8601(self, time_string): if time_string[10] != 'T': raise ValueError('Invalid time string - missing date and time separator.') - hours, minutes, seconds, microseconds, time_zone_offset = ( + hours, minutes, seconds, nanoseconds, time_zone_offset = ( self._CopyTimeFromStringISO8601(time_string[11:])) date_time_values = { @@ -177,8 +177,8 @@ def _CopyDateTimeFromStringISO8601(self, time_string): 'minutes': minutes, 'seconds': seconds} - if microseconds is not None: - date_time_values['microseconds'] = microseconds + 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 @@ -349,7 +349,7 @@ def _CopyFromDateTimeValues(self, date_time_values): Args: date_time_values (dict[str, int]): date and time values, such as year, - month, day of month, hours, minutes, seconds, microseconds, time zone + month, day of month, hours, minutes, seconds, nanoseconds, time zone offset in minutes. """ year = date_time_values.get('year', 0) @@ -375,11 +375,11 @@ def _CopyTimeFromStringISO8601(self, time_string): hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The faction of second and + 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, microseconds, + tuple[int, int, int, int, int]: hours, minutes, seconds, nanoseconds, time zone offset in minutes. Raises: @@ -404,7 +404,7 @@ def _CopyTimeFromStringISO8601(self, time_string): minutes = None seconds = None - microseconds = None + nanoseconds = None time_zone_offset = None time_string_index = 2 @@ -481,8 +481,8 @@ def _CopyTimeFromStringISO8601(self, time_string): seconds = int(time_fraction) time_fraction -= seconds - time_fraction *= definitions.MICROSECONDS_PER_SECOND - microseconds = int(time_fraction) + 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.') @@ -520,7 +520,7 @@ def _CopyTimeFromStringISO8601(self, time_string): if time_string[time_zone_string_index] == '-': time_zone_offset = -time_zone_offset - return hours, minutes, seconds, microseconds, 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. @@ -695,7 +695,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. """ @@ -718,7 +718,7 @@ def CopyFromStringISO8601(self, time_string): YYYY-MM-DDThh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -941,7 +941,7 @@ def _CopyFromDateTimeValues(self, date_time_values): Args: date_time_values (dict[str, int]): date and time values, such as year, - month, day of month, hours, minutes, seconds, microseconds, time zone + month, day of month, hours, minutes, seconds, nanoseconds, time zone offset in minutes. Raises: @@ -953,14 +953,14 @@ def _CopyFromDateTimeValues(self, date_time_values): hours = date_time_values.get('hours', 0) minutes = date_time_values.get('minutes', 0) seconds = date_time_values.get('seconds', 0) - microseconds = date_time_values.get('microseconds', 0) + nanoseconds = date_time_values.get('nanoseconds', 0) time_zone_offset = date_time_values.get('time_zone_offset', 0) precision_helper = precisions.PrecisionHelperFactory.CreatePrecisionHelper( self._precision) - fraction_of_second = precision_helper.CopyMicrosecondsToFractionOfSecond( - microseconds) + fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond( + nanoseconds) self._normalized_timestamp = None self._number_of_seconds = self._GetNumberOfSecondsFromElements( @@ -985,8 +985,8 @@ def CopyFromDatetime(self, datetime_object): precision_helper = precisions.PrecisionHelperFactory.CreatePrecisionHelper( self._precision) - fraction_of_second = precision_helper.CopyMicrosecondsToFractionOfSecond( - datetime_object.microsecond) + fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond( + datetime_object.microsecond * definitions.NANOSECONDS_PER_MICROSECOND) self.fraction_of_second = fraction_of_second def CopyFromStringTuple(self, time_elements_tuple): diff --git a/dfdatetime/uuid_time.py b/dfdatetime/uuid_time.py index 73940f4..0c7eb17 100644 --- a/dfdatetime/uuid_time.py +++ b/dfdatetime/uuid_time.py @@ -89,7 +89,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -104,17 +104,19 @@ def CopyFromDateTimeString(self, time_string): 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', 0) if year < 1582: raise ValueError('Year value not supported.') + nanoseconds, _ = divmod(nanoseconds, 100) + timestamp = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) timestamp += self._UUID_TO_POSIX_BASE - timestamp *= definitions.MICROSECONDS_PER_SECOND - timestamp += date_time_values.get('microseconds', 0) - timestamp *= self._100_NANOSECONDS_PER_MICROSECOND + timestamp *= self._100_NANOSECONDS_PER_SECOND + timestamp += nanoseconds self._normalized_timestamp = None self._timestamp = timestamp diff --git a/dfdatetime/webkit_time.py b/dfdatetime/webkit_time.py index 350452d..ac37a82 100644 --- a/dfdatetime/webkit_time.py +++ b/dfdatetime/webkit_time.py @@ -80,7 +80,7 @@ def CopyFromDateTimeString(self, time_string): YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds - fraction can be either 3 or 6 digits. The time of day, 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. @@ -95,13 +95,17 @@ def CopyFromDateTimeString(self, time_string): 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', 0) + microseconds, _ = divmod( + nanoseconds, definitions.NANOSECONDS_PER_MICROSECOND) + timestamp = self._GetNumberOfSecondsFromElements( year, month, day_of_month, hours, minutes, seconds) timestamp += self._WEBKIT_TO_POSIX_BASE timestamp *= definitions.MICROSECONDS_PER_SECOND - timestamp += date_time_values.get('microseconds', 0) + timestamp += microseconds self._normalized_timestamp = None self._timestamp = timestamp diff --git a/setup.cfg b/setup.cfg index 6e35c01..ea154a8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = dfdatetime -version = 20231205 +version = 20231210 description = Digital Forensics date and time (dfDateTime). long_description = dfDateTime, or Digital Forensics date and time, provides date and time objects to preserve accuracy and precision. long_description_content_type = text/plain diff --git a/tests/interface.py b/tests/interface.py index aab2ace..1af29c4 100644 --- a/tests/interface.py +++ b/tests/interface.py @@ -191,14 +191,14 @@ def testCopyDateTimeFromString(self): expected_date_dict = { 'year': 2010, 'month': 8, 'day_of_month': 12, - 'hours': 21, 'minutes': 6, 'seconds': 31, 'microseconds': 546875} + 'hours': 21, 'minutes': 6, 'seconds': 31, 'nanoseconds': 546875000} date_dict = date_time_values._CopyDateTimeFromString( '2010-08-12 21:06:31.546875') self.assertEqual(date_dict, expected_date_dict) expected_date_dict = { 'year': 2010, 'month': 8, 'day_of_month': 12, - 'hours': 21, 'minutes': 6, 'seconds': 31, 'microseconds': 546875, + 'hours': 21, 'minutes': 6, 'seconds': 31, 'nanoseconds': 546875000, 'time_zone_offset': -60} date_dict = date_time_values._CopyDateTimeFromString( '2010-08-12 21:06:31.546875-01:00') @@ -206,12 +206,20 @@ def testCopyDateTimeFromString(self): expected_date_dict = { 'year': 2010, 'month': 8, 'day_of_month': 12, - 'hours': 21, 'minutes': 6, 'seconds': 31, 'microseconds': 546875, + 'hours': 21, 'minutes': 6, 'seconds': 31, 'nanoseconds': 546875000, 'time_zone_offset': 60} date_dict = date_time_values._CopyDateTimeFromString( '2010-08-12 21:06:31.546875+01:00') self.assertEqual(date_dict, expected_date_dict) + expected_date_dict = { + 'year': 2010, 'month': 8, 'day_of_month': 12, + 'hours': 21, 'minutes': 6, 'seconds': 31, 'nanoseconds': 546875333, + 'time_zone_offset': 60} + date_dict = date_time_values._CopyDateTimeFromString( + '2010-08-12 21:06:31.546875333+01:00') + self.assertEqual(date_dict, expected_date_dict) + with self.assertRaises(ValueError): date_time_values._CopyDateTimeFromString('') @@ -235,19 +243,19 @@ def testCopyTimeFromString(self): time_tuple = date_time_values._CopyTimeFromString('20:23:56+05:30') self.assertEqual(time_tuple, expected_time_tuple) - expected_time_tuple = (20, 23, 56, 327000, None) + expected_time_tuple = (20, 23, 56, 327000000, None) time_tuple = date_time_values._CopyTimeFromString('20:23:56.327') self.assertEqual(time_tuple, expected_time_tuple) - expected_time_tuple = (20, 23, 56, 327000, 60) + expected_time_tuple = (20, 23, 56, 327000000, 60) time_tuple = date_time_values._CopyTimeFromString('20:23:56.327+01:00') self.assertEqual(time_tuple, expected_time_tuple) - expected_time_tuple = (20, 23, 56, 327124, None) + expected_time_tuple = (20, 23, 56, 327124000, None) time_tuple = date_time_values._CopyTimeFromString('20:23:56.327124') self.assertEqual(time_tuple, expected_time_tuple) - expected_time_tuple = (20, 23, 56, 327124, -300) + expected_time_tuple = (20, 23, 56, 327124000, -300) time_tuple = date_time_values._CopyTimeFromString('20:23:56.327124-05:00') self.assertEqual(time_tuple, expected_time_tuple) diff --git a/tests/precisions.py b/tests/precisions.py index b74238c..fcd558a 100644 --- a/tests/precisions.py +++ b/tests/precisions.py @@ -12,12 +12,12 @@ class DateTimePrecisionHelperTest(unittest.TestCase): """Tests for the date time precision helper interface.""" - def testCopyMicrosecondsToFractionOfSecond(self): - """Tests the CopyMicrosecondsToFractionOfSecond function.""" + def testCopyNanosecondsToFractionOfSecond(self): + """Tests the CopyNanosecondsToFractionOfSecond function.""" precision_helper = precisions.DateTimePrecisionHelper with self.assertRaises(NotImplementedError): - precision_helper.CopyMicrosecondsToFractionOfSecond(0) + precision_helper.CopyNanosecondsToFractionOfSecond(0) def testCopyToDateTimeString(self): """Tests the CopyToDateTimeString function.""" @@ -30,19 +30,19 @@ def testCopyToDateTimeString(self): class SecondsPrecisionHelperTest(unittest.TestCase): """Tests for the seconds precision helper.""" - def testCopyMicrosecondsToFractionOfSecond(self): - """Tests the CopyMicrosecondsToFractionOfSecond function.""" + def testCopyNanosecondsToFractionOfSecond(self): + """Tests the CopyNanosecondsToFractionOfSecond function.""" precision_helper = precisions.SecondsPrecisionHelper - fraction_of_second = precision_helper.CopyMicrosecondsToFractionOfSecond( + fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond( 123456) self.assertEqual(fraction_of_second, 0.0) with self.assertRaises(ValueError): - precision_helper.CopyMicrosecondsToFractionOfSecond(-1) + precision_helper.CopyNanosecondsToFractionOfSecond(-1) with self.assertRaises(ValueError): - precision_helper.CopyMicrosecondsToFractionOfSecond(1000000) + precision_helper.CopyNanosecondsToFractionOfSecond(1000000000) def testCopyToDateTimeString(self): """Tests the CopyToDateTimeString function.""" @@ -59,19 +59,19 @@ def testCopyToDateTimeString(self): class MillisecondsPrecisionHelperTest(unittest.TestCase): """Tests for the milliseconds precision helper.""" - def testCopyMicrosecondsToFractionOfSecond(self): - """Tests the CopyMicrosecondsToFractionOfSecond function.""" + def testCopyNanosecondsToFractionOfSecond(self): + """Tests the CopyNanosecondsToFractionOfSecond function.""" precision_helper = precisions.MillisecondsPrecisionHelper - fraction_of_second = precision_helper.CopyMicrosecondsToFractionOfSecond( - 123456) + fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond( + 123456789) self.assertEqual(fraction_of_second, decimal.Decimal('0.123')) with self.assertRaises(ValueError): - precision_helper.CopyMicrosecondsToFractionOfSecond(-1) + precision_helper.CopyNanosecondsToFractionOfSecond(-1) with self.assertRaises(ValueError): - precision_helper.CopyMicrosecondsToFractionOfSecond(1000000) + precision_helper.CopyNanosecondsToFractionOfSecond(1000000000) def testCopyToDateTimeString(self): """Tests the CopyToDateTimeString function.""" @@ -86,21 +86,21 @@ def testCopyToDateTimeString(self): class MicrosecondsPrecisionHelperTest(unittest.TestCase): - """Tests for the milliseconds precision helper.""" + """Tests for the microseconds precision helper.""" - def testCopyMicrosecondsToFractionOfSecond(self): - """Tests the CopyMicrosecondsToFractionOfSecond function.""" + def testCopyNanosecondsToFractionOfSecond(self): + """Tests the CopyNanosecondsToFractionOfSecond function.""" precision_helper = precisions.MicrosecondsPrecisionHelper - fraction_of_second = precision_helper.CopyMicrosecondsToFractionOfSecond( - 123456) + fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond( + 123456789) self.assertEqual(fraction_of_second, decimal.Decimal('0.123456')) with self.assertRaises(ValueError): - precision_helper.CopyMicrosecondsToFractionOfSecond(-1) + precision_helper.CopyNanosecondsToFractionOfSecond(-1) with self.assertRaises(ValueError): - precision_helper.CopyMicrosecondsToFractionOfSecond(1000000) + precision_helper.CopyNanosecondsToFractionOfSecond(1000000000) def testCopyToDateTimeString(self): """Tests the CopyToDateTimeString function.""" @@ -114,6 +114,36 @@ def testCopyToDateTimeString(self): precision_helper.CopyToDateTimeString((2018, 1, 2, 19, 45, 12), 4.123456) +class NanosecondsPrecisionHelperTest(unittest.TestCase): + """Tests for the nanoseconds precision helper.""" + + def testCopyNanosecondsToFractionOfSecond(self): + """Tests the CopyNanosecondsToFractionOfSecond function.""" + precision_helper = precisions.NanosecondsPrecisionHelper + + fraction_of_second = precision_helper.CopyNanosecondsToFractionOfSecond( + 123456789) + self.assertEqual(fraction_of_second, decimal.Decimal('0.123456789')) + + with self.assertRaises(ValueError): + precision_helper.CopyNanosecondsToFractionOfSecond(-1) + + with self.assertRaises(ValueError): + precision_helper.CopyNanosecondsToFractionOfSecond(1000000000) + + def testCopyToDateTimeString(self): + """Tests the CopyToDateTimeString function.""" + precision_helper = precisions.NanosecondsPrecisionHelper + + date_time_string = precision_helper.CopyToDateTimeString( + (2018, 1, 2, 19, 45, 12), 0.123456789) + self.assertEqual(date_time_string, '2018-01-02 19:45:12.123456789') + + with self.assertRaises(ValueError): + precision_helper.CopyToDateTimeString( + (2018, 1, 2, 19, 45, 12), 4.123456789) + + class PrecisionHelperFactoryTest(unittest.TestCase): """Tests for the date time precision helper factory.""" diff --git a/tests/time_elements.py b/tests/time_elements.py index e043501..092e986 100644 --- a/tests/time_elements.py +++ b/tests/time_elements.py @@ -103,14 +103,14 @@ def testCopyDateTimeFromStringISO8601(self): expected_date_dict = { 'year': 2010, 'month': 8, 'day_of_month': 12, - 'hours': 21, 'minutes': 6, 'seconds': 31, 'microseconds': 546875} + 'hours': 21, 'minutes': 6, 'seconds': 31, 'nanoseconds': 546875000} date_dict = time_elements_object._CopyDateTimeFromStringISO8601( '2010-08-12T21:06:31.546875') self.assertEqual(date_dict, expected_date_dict) expected_date_dict = { 'year': 2010, 'month': 8, 'day_of_month': 12, - 'hours': 21, 'minutes': 6, 'seconds': 31, 'microseconds': 546875, + 'hours': 21, 'minutes': 6, 'seconds': 31, 'nanoseconds': 546875000, 'time_zone_offset': -60} date_dict = time_elements_object._CopyDateTimeFromStringISO8601( '2010-08-12T21:06:31.546875-01:00') @@ -118,12 +118,20 @@ def testCopyDateTimeFromStringISO8601(self): expected_date_dict = { 'year': 2010, 'month': 8, 'day_of_month': 12, - 'hours': 21, 'minutes': 6, 'seconds': 31, 'microseconds': 546875, + 'hours': 21, 'minutes': 6, 'seconds': 31, 'nanoseconds': 546875000, 'time_zone_offset': 60} date_dict = time_elements_object._CopyDateTimeFromStringISO8601( '2010-08-12T21:06:31.546875+01:00') self.assertEqual(date_dict, expected_date_dict) + expected_date_dict = { + 'year': 2010, 'month': 8, 'day_of_month': 12, + 'hours': 21, 'minutes': 6, 'seconds': 31, 'nanoseconds': 546875333, + 'time_zone_offset': 60} + date_dict = time_elements_object._CopyDateTimeFromStringISO8601( + '2010-08-12T21:06:31.546875333+01:00') + self.assertEqual(date_dict, expected_date_dict) + with self.assertRaises(ValueError): time_elements_object._CopyDateTimeFromStringISO8601('') @@ -309,16 +317,16 @@ def testCopyTimeFromStringISO8601(self): '20:23:56+05:30') self.assertEqual(time_tuple, expected_time_tuple) - expected_time_tuple = (20, 23, 56, 327000, None) + expected_time_tuple = (20, 23, 56, 327000000, None) time_tuple = time_elements_object._CopyTimeFromStringISO8601('20:23:56.327') self.assertEqual(time_tuple, expected_time_tuple) - expected_time_tuple = (20, 23, 56, 327000, 60) + expected_time_tuple = (20, 23, 56, 327000000, 60) time_tuple = time_elements_object._CopyTimeFromStringISO8601( '20:23:56.327+01:00') self.assertEqual(time_tuple, expected_time_tuple) - expected_time_tuple = (20, 23, 56, 327124, None) + expected_time_tuple = (20, 23, 56, 327124000, None) time_tuple = time_elements_object._CopyTimeFromStringISO8601( '20:23:56.327124') self.assertEqual(time_tuple, expected_time_tuple) @@ -332,7 +340,7 @@ def testCopyTimeFromStringISO8601(self): '08:04:32+00:00') self.assertEqual(time_tuple, expected_time_tuple) - expected_time_tuple = (20, 23, 56, 327124, -300) + expected_time_tuple = (20, 23, 56, 327124000, -300) time_tuple = time_elements_object._CopyTimeFromStringISO8601( '20:23:56.327124-05:00') self.assertEqual(time_tuple, expected_time_tuple)