Unexpected millisecond offsets when doing subtraction with datetime and timedelta objects

Question:

I have this class implemented in python on which i am performing some unitests

from datetime import datetime, timedelta

class FreeTime:
    """Range in a timeframe where a task can be located"""

    def __init__(self, start: datetime, end: datetime) -> None:
        self.start = start
        """ start of the freetime """

        self.end = end
        """ end of the freetime """

        self.duration = end - start
        """ duration of the freetime"""

    def set_start(self, start: datetime):
        self.start = start
        self.duration = self.end - self.start

    def set_duration(self, duration: timedelta):
        self.duration = duration
        self.start = self.end - self.duration

    def __repr__(self) -> str:
        return f"FreeTime - {self.start} -> {self.end} - {self.duration}"

The test looks like this:

from datetime import datetime, timedelta

def test_FreeTime():
    """test the FreeTime class"""

    start = datetime.now()
    end = start + timedelta(hours=4.0)

    free_time = FreeTime(start=start, end=end)
    assert free_time.start == start
    assert free_time.end == end
    assert free_time.duration == end - start

    start_2 = datetime.now() + timedelta(hours=1.0)

    free_time.set_start(start_2)
    assert free_time.start == start_2
    assert free_time.duration == end - start_2

    delta = timedelta(hours=3.0)
    free_time.set_duration(delta)
    assert free_time.duration == timedelta(hours=3.0)
    assert free_time.start == end - delta

    assert str(free_time) == f"FreeTime - {start_2} -> {end} - {delta}"

Strangely when I run the test I get some variant of this assertion error, (some variant meaning the millisecond difference):

assert str(free_time) == f"FreeTime - {start_2} -> {end} - {delta}"
E       AssertionError: assert 'FreeTime - 2...544 - 3:00:00' == 'FreeTime - 2...544 - 3:00:00'
E         - FreeTime - 2023-01-04 21:54:27.421567 -> 2023-01-05 00:54:27.421544 - 3:00:00
E         ?                                    ^^
E         + FreeTime - 2023-01-04 21:54:27.421544 -> 2023-01-05 00:54:27.421544 - 3:00:00
E         ?                                    ^^

However I dont get how or where the difference in millisecond comes from, since all previous assertion tests pass wiht out errors. It alwasy failes on this assertion but the offset is different every time.

I allready tried instantiatiating all timedelata objects whith floats instead of int, but this did not have any effect.

Asked By: lorenzo

||

Answers:

I try to untangle your test logic:

start = t1 = now()
end = t1 + 4h

free_time.start = start = t1
free_time.end = end = t1 + 4h

start2 = t2 = now() + 1h = t1 + delta_t + 1h
free_time.start = start2 = t2 = t1 + delta_t + 1h
free_time.duration = end - start2 = t1 + 4h - t2

free_time.duration = 3h
free_time.start = free_time.end - 3h = end - 3h = t1 + 1h

The end result is:

free_time.start = t1 + 1h
free_time.end = t1 + 4h
free_time.duration = 3h

And your assertion is that

free_time.start == start2 == t2 == t1 + delta_t + 1h
free_time.end == end == t1 + 4h
free_time.duration == 3h

The error in your logic is that you assume the two calls to datetime.now() result in the same value. They do not. Python is not infinitely fast and the timestamp resolution is sufficiently small that both result in different values, thus resulting in the time difference which I put in the equations as delta_t

Answered By: Homer512

Yes – Based on @homer512 ‘s input, change your test class to be

from datetime import datetime, timedelta

def test_FreeTime():
    """test the FreeTime class"""

    start = datetime.now()
    end = start + timedelta(hours=4.0)

    free_time = FreeTime(start=start, end=end)
    assert free_time.start == start
    assert free_time.end == end
    assert free_time.duration == end - start

    start_2 = start + timedelta(hours=1.0)

    free_time.set_start(start_2)
    assert free_time.start == start_2
    assert free_time.duration == end - start_2

    delta = timedelta(hours=3.0)
    free_time.set_duration(delta)
    assert free_time.duration == timedelta(hours=3.0)
    assert free_time.start == end - delta

    assert str(free_time) == f"FreeTime - {start_2} -> {end} - {delta}"
Answered By: AndreasM_DK