Skip to content

datetime -> Time accuracy problems #9579

Closed

Description

@aarchiba

Description

Although python's datetime objects support microsecond resolution, and Time objects support sub-nanosecond resolution, conversion and arithmetic between the two can be off by tens of microseconds. (By design datetime objects do not support leap seconds, so UTC cannot be expected to work. The problems described here are for non-UTC timescales like TAI.)

Expected behavior

Computing the difference between two datetime objects then converting to a TimeDelta should give the same result as converting the datetime objects to Time objects and subtracting, to within a couple of microseconds.

Actual behavior

In fact PR #9532 demonstrates several examples where the difference exceeds 20 microseconds.

Steps to Reproduce

def test_datetime_difference_agrees_with_timedelta_no_hypothesis():
    scale = "tai"
    dt1 = datetime(1235, 1, 1, 0, 0)
    dt2 = datetime(9950, 1, 1, 0, 0, 0, 890773)
    t1 = Time(dt1, scale=scale)
    t2 = Time(dt2, scale=scale)
    assert(abs((t2-t1) - TimeDelta(dt2-dt1, scale=scale)) < 25*u.us)

System Details

Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import platform; print(platform.platform())
Linux-5.0.0-32-generic-x86_64-with-Ubuntu-19.04-disco
>>> import sys; print("Python", sys.version)
Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.3.0]
>>> import numpy; print("Numpy", numpy.__version__)
Numpy 1.17.1
>>> import scipy; print("Scipy", scipy.__version__)
Scipy 1.3.1
>>> import astropy; print("astropy", astropy.__version__)
astropy 4.0.dev26780

Activity

  1. mhvk commented on Nov 10, 2019

    @mhvk
    Contributor

    No time right now to fix it, but I did find the problem: it is in the TimeDelta creation - the TimeDeltaDatetime class is based on timedelta.total_seconds() which returns a double, hence the loss of precision (for this case with a very large number of days). Instead it should combine the timedelta.{days, seconds, microseconds} attributes. Similarly, the value property should properly initialize using days, seconds, and microseconds. This should be a very easy fix, so labeling it accordingly.

  2. bmorris3 commented on Nov 11, 2019

    @bmorris3
    Contributor

    @mhvk – I'm working on this and I've I implemented the first part of your suggestion (getting rid of total_seconds), but I'm not sure about what should happen to value – how does one take the JD and properly construct a datetime.timedelta using days, seconds, microseconds? Thanks!

  3. mhvk commented on Nov 11, 2019

    @mhvk
    Contributor

    @bmorris3 - I haven't thought too hard, and it depends a little what timedelta does internally, but jd1 is the integer part of the JD difference, so is in days, and then one can construct seconds and microseconds from jd2 using something like

    seconds, microseconds = divmod(jd2 * 24 * 3600, 1.)
    microseconds = np.round(microseconds * 1e6)
    td = timedelta(jd1, seconds, microseconds)
    
  4. aarchiba commented on Nov 11, 2019

    @aarchiba
    ContributorAuthor

    @bmorris3 - I haven't thought too hard, and it depends a little what timedelta does internally, but jd1 is the integer part of the JD difference, so is in days, and then one can construct seconds and microseconds from jd2 using something like

    Not to be difficult, but I'd like to point out that we were just talking about how we do not guarantee that jd1 and jd2 are related in this way. And in fact as a result of bug #9533 jd1 is not always an integer. For this code, it's probably safest to run jd1, jd2 = day_frac(jd1, jd2) first.

  5. mhvk commented on Nov 11, 2019

    @mhvk
    Contributor

    Actually, timedelta does the addition of days and seconds internally anyway. At the precision needed here, I'd probably do

    days = np.round(jd1 + jd2)
    seconds, microseconds = divmod((jd1 - days + jd2) * 24 * 3600, 1.)
    etc.
    
  6. bmorris3 commented on Nov 11, 2019

    @bmorris3
    Contributor

    Thanks for the tips, but neither tip seems to help make the proposed test pass. I think I'm missing something.

  7. pllim commented on Nov 11, 2019

    @pllim
    Member

    @mhvk , "package-novice" might be a little misleading here, as the solution is only obvious when one already know the code base a little. As someone who doesn't touch the time sub-package, I glanced at the conversation and I didn't get a clear picture on where to start, so I re-labeled it to "package-intermediate". FYI.

  8. added 2 commits that reference this issue on Nov 25, 2019
    89f852b
    b523439
  9. aarchiba commented on Nov 26, 2019

    @aarchiba
    ContributorAuthor

    Indeed I think the subtlety is that your conversion, in both directions, needs to make full use of jd1 and jd2.

  10. added a commit that references this issue on Jan 6, 2020
    5293a53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions