Date arithmetic?

I’m trying to find it in the docs but I might be missing it.

If I want to get what the timestamp would be of the current timestamp plus N days, how would I do that?

Do I just do

date_time_stamp(date(2024, 4, 28),D0),
date_time_stamp(date(2024, 7, 28),D1),
Diff is D1-D0.

and then add the Diff to a timestamp I want to add three months to or is there a better way to do this?

“Three months” is ambiguous. Do you mean 30 days or 31 days, or the average for a year (365.25/12 = 30.4375)?

Anyway, to add a day to a time:

?- get_time(TimeStamp1), stamp_date_time(TimeStamp1, DateTime1, local), TimeStamp2 is TimeStamp1 + 24.0 * 60 * 60, stamp_date_time(TimeStamp2, DateTime2, 'UTC').
TimeStamp1 = 1714355801.1410513,
DateTime1 = date(2024,4,28,21,56,41.141051292419434,14400,'EDT',true),
TimeStamp2 = 1714442201.1410513,
DateTime2 = date(2024,4,30,1,56,41.141051292,0,'UTC',-).
1 Like

Fair. I meant 30 days. Thanks Peter.

I know this isn’t a general programming forum and this question isn’t prolog-specific but could you happen to advise how to best pad a timestamp from 32bit to 64bit?

Chatgpt advised doing something like this

date(Date),date_time_stamp(Date,Date_ts),
Expiry_32bit is Date_ts+(86400*30*3),
Expiry_ms is Expiry_32bit*1000,
Expiry_64bit is Expiry_ms + 999,
floor(Expiry_64bit,Expiry_64bit_f),

is that pretty much the standard way?

See here: SWI-Prolog -- Floating point arithmetic precision

Do you need to export this 64bit floating point value to some binary format?

No I do not. Using it to create the expiration date for an api key.

First about your original question:

@peter.ludemann already answered but it seems that he calculated milliseconds and added them to the timestamp. The docs of date_time_stamp/2 suggest instead:

Values for month, day, hour, minute or second need not be normalized. This flexibility allows for easy computation of the time at any given number of these units from a given timestamp.

There is also an example of adding 200 days without doing 200 * 24 * 60... and so on. To add three months to the now:

?- get_time(T0), % now
   stamp_date_time(T0, DT0, 'UTC'),
   date_time_value(date, DT0, date(Y0,M0,D0)), % year/month/day of now
   M3 is M0 + 3, % add three months!
   date_time_stamp(date(Y0,M3,D0), T1),
   stamp_date_time(T1, X, 'UTC'). % check result
T0 = 1714457392.7585614,
DT0 = date(2024, 4, 30, 6, 9, 52.758561372, 0, 'UTC', -),
Y0 = 2024,
M0 = 4,
D0 = 30,
M3 = 7,
T1 = 1722286800.0,
X = date(2024, 7, 29, 21, 0, 0.0, 0, 'UTC', -).

How exactly it does it and how the results compare to what you’d expect is a question too big :smiley:

Finally, for your api key expiration date, it sounds strange that you’d have to know the binary representation of a 64bit double for it. Can you share what it asks for?

It indeed sounds a bit strange. If you want now+30 days in some string format you use get_time/1 to get now, you add 30*24*3600 and you use format_time/3 to get the desired representation in either local time or UTC.

SWI-Prolog core time representation is a double relative to the POSIX epoch, Jan 1, 1970. That is unconventional but so far it only proved very comfortable. It provides a resolution of less than a microsecond where we are now and can represent time point very far into the past and very far into the future. Best of all, you only deal with seconds, never having to worry about seconds/milliseconds/microseconds/nanoseconds/… Ok, we can’t do nanoseconds unless we move to 128 bit floats. If we do so though, we do not have to change any interface and fully transparently get much more precise time stamps.

Possibly we should consider 128 bit floats? The storage is already 24 bytes on the stack and moving to 128 bit floats makes this 32 bytes. That is not a big deal. No idea how much slower arithmetic would get?

This is opposite to what the docs seem to suggest. I guess it really depends on the use case though.

Most datetime facilities offer some way of “units of time” arithmetic. In SQLite3:

sqlite> select date('now', '+30 days');
2024-05-30

In Python3:

>>> import datetime
>>> datetime.datetime.now() + datetime.timedelta(days=30)
datetime.datetime(2024, 5, 30, 11, 36, 21, 513161)

I would have thought that the SWI-Prolog alternative is to use the normalization of date_time_stamp/2 as in my previous post.

Comparing results between systems in corner cases is not something I would want to start validating, though.

Note how both examples let you blissfully forget about time zones, while SWI-Prolog wisely forces you to deal with it somehow.

?- get_time(T), stamp_date_time(T, DT, local).
T = 1714466341.683617,
DT = date(2024, 4, 30, 11, 39, 1.683617115020752, -10800, 'EEST', true).

?- get_time(T), stamp_date_time(T, DT, 'UTC').
T = 1714466349.725451,
DT = date(2024, 4, 30, 8, 39, 9.725450992, 0, 'UTC', -).

That is what you can do if you start with a broken-down time representation. All time arithmetic works on time stamps as doubles though. The time stamp is always UTC. If you give or want a broken-down representation or string, you have to tell it whether the time is local or UTC. That is just how POSIX does it. SWI-Prolog merely extends precision and range by using doubles rather than some fixed point integer (or pair).

No, I added seconds (but changed the timezone):

?- get_time(TimeStamp1), stamp_date_time(TimeStamp1, DateTime1, local), TimeStamp2 is TimeStamp1 + 24.0 * 60 * 60, stamp_date_time(TimeStamp2, DateTime2, local).
TimeStamp1 = 1714664754.9753954,
DateTime1 = date(2024,5,2,11,45,54.9753954410553,14400,'EDT',true),
TimeStamp2 = 1714751154.9753954,
DateTime2 = date(2024,5,3,11,45,54.9753954410553,14400,'EDT',true).