Sub-microsecond precision is lost when parsing Y, M, D, W, h, m, s components

Issue #21 resolved
Brandon Nielsen repo owner created an issue

Continuation of issue #10. In issue #10, we truncated second components to microsecond resolution in the default PythonTimeBuilder, punting support for higher resolutions to alternative builders.

However, other components support decimal precision as well, showing the same erroneous behaviors when using sub-microsecond precision is attempted.

0.000000016667 minutes in a microsecond:

>>> aniso8601.parse_duration('PT0.00000000999M')
datetime.timedelta(0, 0, 1)
>>> aniso8601.parse_time('14:43.999999997')
datetime.time(14, 44)

Same issue with hours:

>>> aniso8601.parse_time('14.99999999997')
datetime.time(15, 0)

Year rollover (365 days per year, rolls over to 2000Y):

>>> aniso8601.parse_duration('P1999.99999999999997Y')
datetime.timedelta(730000)

Month rollover (30 days per month, rolls over to 2M):

>>> aniso8601.parse_duration('P1999Y1.9999999999997M')
datetime.timedelta(729695)

Day rollover to 2D:

>>> aniso8601.parse_duration('P1999Y1M1.99999999997D')
datetime.timedelta(729667)

Week rollover to 2W:

>>> aniso8601.parse_duration('P1.9999999999997W')
datetime.timedelta(14)

The fix for these is less clear since they do not divide evenly into a microsecond, so we can't simply truncate to maximum supported precision unless we're okay with throwing away maximum possible resolution.

Comments (6)

  1. Brandon Nielsen reporter

    The easiest solution may be to expand the fractional component to seconds, truncate to microsecond precision, then continue building.

  2. Brandon Nielsen reporter

    It should be noted that the above issues match the behavior of the Python builtins:

    >>> datetime.timedelta(minutes=0.00000000999).microseconds
    1
    >>> datetime.timedelta(minutes=43.999999997).seconds
    2640
    >>> (datetime.datetime(1999, 1, 1) + datetime.timedelta(hours=14.99999999997)).hour
    15
    >>> datetime.timedelta(days=1999.99999999999997 * 365.0).days
    730000
    >>> datetime.timedelta(days=1.99999999999997 * 30.0).days
    60
    >>> datetime.timedelta(days=1.999999999997).days
    2
    >>> datetime.timedelta(weeks=1.9999999999997).days
    14
    

    Note the months and day tests required an extra digit of precision since there's no years involved.

  3. Log in to comment