Bug - Date Library

Hi, I believe I have found a bug in the date library. The full code is provided on the StackOverflow link below. But I think the bug exists somewhere in here:

    DateTime1 = date(Y1, M1, D1, H1, Mn1, S1, _, _, _),
    S2 is S1 + X,
    date_time_stamp(date(Y1, M1, D1, H1, Mn1, S2, _, _, _), Stamp2),

When attempting to increment the seconds value S1 by a value X large enough that the Mn1 value needs to be incremented by 1, the Mn1 value will instead be incremented by 2. E.g. a Mn1 value of 22, S value of 59 and X value of 1, the Mn1 value should increment to 23 but it instead increments to 24. When attempting the same logic with Minutes and Hours instead, it works.

1 Like

Here is the major mistake:

S2 is S1 + X

The number of seconds (including milliseconds as the fraction) in a valid time must be >= 0 and < 60.

Example of safe datetime arithmetic (also showing the imperfection of floating-point arithmetic):

?- D = datetime(2021, 12, 31, 23, 59, 59.9), datetime_add(D, 2 secs, D2).
D = datetime(2021,12,31,23,59,59.9),
D2 = datetime(2022,1,1,0,0,1.8999999999999986).

I don’t think there is any issue with S2 is S1 + X. Using the documentation timedate documentation under date_time_stamp it explains that going beyond 0 to 59 is allowed.

This example computes the date 200 days after 2006-07-14:
?- date_time_stamp(date(2006,7,214,0,0,0,0,-,-), Stamp),
   stamp_date_time(Stamp, D, 0),
   date_time_value(date, D, Date).
Date = date(2007, 1, 30)

This example is with increasing the day value past 31, it also works for minutes when going over 59 as shown on StackOverflow but it breaks for seconds.

Not sure why, but it works fine if you actually pass on the timezone info:

:- use_module(library(date)).

get_dt(Datestring, Y, M, D, H, Mn, S, X) :-
    parse_time(Datestring, Stamp1),
    stamp_date_time(Stamp1, DateTime1, 'UTC'),
    DateTime1 = date(Y1, M1, D1, H1, Mn1, S1, Off, TZ, DST),
    S2 is S1 + X,
    date_time_stamp(date(Y1, M1, D1, H1, Mn1, S2, Off, TZ, DST), Stamp2),
    stamp_date_time(Stamp2, DateTime2, 'UTC'),
    DateTime2 = date(Y, M, D, H, Mn, S, _, _, _).
?- get_dt('2020-03-04 06:22:59.012315131', Y, M, D, H, Mn, S, 1).
Y = 2020,
M = 3,
D = 4,
H = 6,
Mn = 23,
S = 0.012315034.

Surely passing on is a good idea. I’m afraid I do not have time right now to figure out what exactly is supposed to happen if you pass variables.

Surely if you want to compute second offsets, doing so based on the time stamp is a lot easier. The non-normalized values for the time stamp are useful if you want to change a date by a month or a year as these do not have a defined number of seconds.

2 Likes

Great find thanks, could you post on StackOverflow and I can close the question if possible?

in case he can’t, feel free to answer it yourself. it’s allowed on SO.