-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
What is one day? #94
Comments
What do other Ada compilers do? |
The type Time does not inherently have a time zone, so I personally would certainly presume that any change in timezone that occurs in the interval being skipped is irrelevant. Time zone is really only relevant when producing a "local" time, but these operators are operating on Time rather than a split-up local time. A potentially more controversial question relates to leap seconds ... ;-) |
The type Time does not inherently have a time zone, ...
We decided to leave that unspecified, so it may or may not. For Janus/Ada,
"Time" is a local time value; the values are stored "split up" (since that's
how they came from MS-DOS or Windows). If one asks for something in a
specific time zone, the values are adjusted accordingly.
... so I personally would certainly presume that any change in timezone
that occurs in the
interval being skipped is irrelevant.
That's also unspecified, I think. For Janus/Ada, math occurs in whatever
timezone the original value was produced. It doesn't change unless someone
asks for it to change.
Time zone is really only relevant when producing a "local" time, but these
operators
are operating on Time rather than a split-up local time.
That is a very Unix-centric view. The Janus/Ada representation is better for
Windows (closer to the OS values, so Clock calls are faster), better for
many typical uses (those that don't use a lot of math, but do lots of splits
(such as happen when you output times), and moreover doesn't require any
expensive 64-bit math. (This latter was probably more of a consideration
back in the day, but whatever.)
A potentially more controversial question relates to leap seconds ... ;-)
I think the sensible answer is the same here; math doesn't change the number
of leap seconds. They come from Clock values; I don't think it is practical
for them to come from anywhere else. (It makes no sense to keep tables of
leap seconds in Ada runtimes.)
Randy.
|
Janus/Ada uses a version of the code from Claw.Time (which is generic in the
sense that it doesn't depend in any way on the representation of
Ada.Calendar.Time -- it only uses the functions defined in Ada.Calendar).
The code determines the "Julian day" for a Time value, adds the number of
days to it, and creates a new time value from the resulting "Julian day" and
the seconds value of the original.
Janus/Ada stores the results of Year, Day, Month, and Seconds in a Time
value (this is close to what MS-DOS and Windows return for time queries).
Time zones are only applied if a time zone parameter is involved. Note that
I have never fixed the library for AI12-0336-1 (which changed the meaning to
Time_Zone to what everyone [else!] thought it was as opposed to what it
actually said; that definition had the Time_Zone of local time = 0, which
means that Time never needs adjusting to produce local time); I don't know
how to do that accurately without completely changing the implementation of
all time operations (and worst of all, the representation of those values).
Anyway, the result of this is that the time zone is irrelevant for math
purposes in Janus/Ada (unless, of course, you explicitly provide one).
Randy.
…_____
From: CKWG ***@***.***
Sent: Friday, April 19, 2024 4:58 AM
To: Ada-Rapporteur-Group/User-Community-Input
Cc: Subscribed
Subject: Re: [Ada-Rapporteur-Group/User-Community-Input] What is one day?
(Issue #94)
What do other Ada compilers do?
-
Reply to this email directly, view
<#94 (comment)
ecomment-2066236226> it on GitHub, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AT65YNZPYPAICFH7LAEBHC3Y6
DTCFAVCNFSM6AAAAABGOQ5BXSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANRWGIZTM
MRSGY> .
You are receiving this because you are subscribed to this thread.
<https://github.com/notifications/beacon/AT65YN6SEBNLEPXPD4PK5XTY6DTCFA5CNFS
M6AAAAABGOQ5BXSWGG33NNVSW45C7OR4XAZNMJFZXG5LFINXW23LFNZ2KUY3PNVWWK3TUL5UWJTT
3FBBUE.gif> Message ID:
***@***.***>
|
We are moving away from the issue here... The real question here is:
Note that if option 1) is chosen, these operations become useless since the operations in Ada.Calendar provide the same functionnality, while option 2) is far from trivial to code by user. |
I am not sure how you reach that conclusion. In fact in package Calendar, the "+" and "-", and more generally the notion of Time, are described in the AARM as being implementation-defined as far as time-zone interactions (see AARM 9.6(24.b/3) and http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ai05s/ai05-0119-1.txt?rev=1.10). By contrast, when we defined the Ada.Calendar.Arithmetic and Ada.Calendar.Formatting, I believe we were trying to cleanly separate the notion of time from the notion of time zone, and operations where the time zone mattered were placed in Formatting and given a Time_Zone parameter (of type Time_Offset). If an operation did not have a Time_Zone parameter then it was intended to be independent of time zone. In particular, the Difference operation and the "+" and "-" operators in Ada.Calendar.Arithmetic are intended to be time zone independent. See http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ais/ai-00351.txt?rev=1.17 and http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ais/ai-00427.txt?rev=1.8 for the original rationale. In particular, Ada.Calendar.Arithmetic was designed to help determine the elapsed time between two values of type Time, so time zones should not affect the result at this level (even if they are somehow used behind the scenes). If you want to get the Time corresponding to the next day, at the same hours/minutes in the local time zone, and there is a chance the local time zone might have changed in that period, you will need to use Split, Time_Of, and Local_Time_Offset, and worry about the fact that there might actually be two different times (e.g. 1:30AM at the end of summer time) that satisfy your requirements (or none, such as 2:30AM at the beginning of summer time). This is another reason why expecting Calendar.Arithmetic."+" to do all of this seems inappropriate. |
Jean-Pierre Rosen writes:
We are moving away from the issue here...
No, we're not. When it comes to Time_Zones, the behavior of Time is
implementation-defined unless you use a operations with a Time_Zone
parameter. So when your original question asks "what is one day", it is an
undefined question unless you explain precisely which sequence of operations
you are talking about. My understanding of your question implies that you
are using the functions in Calendar to determine the result, and those are
always of an implementation-defined time zone (and not necessarily one that
makes sense!).
The real question here is:
1. Is "one day" in the "+" and "-" operations of
Ada.Calendar.Arithmetic equivalent to a duration of 86_400s.
Surely. What else could it mean? But you have to be careful that you are
using a consistent time zone in order to see that. Otherwise you are
comparing apples to oranges. If you are allowing the time zone to change,
it's hard to say anything sensible (and I don't see why anyone would want to
try).
or
2. Is "one day" the day before or after, with the same "hour" as the
original day
(and some tweaking if the time does not exist in the resulting time)
This operation is the same as (1) unless you are changing time zones as
well, and no one ought to be doing that. If that's really a requirement
(highly doubtful), you should do it yourself).
Note that if option 1) is chosen, these operations become useless since the
operations in Ada.Calendar provide the same functionality, ...
Why do you say that? The entire purpose of the operations in
Ada.Calendar.Arithmetic is to be able to add multiple days, which you cannot
do (portably) with the operations on Ada.Calendar (because the operations in
Ada.Calendar are based on values Duration, which is not guaranteed to have a
range of more than one day). (Writing a loop to add 5 days is an insane
requirement.) The operations *are* redundant if you are just adding one,
that's not the reason these operations were needed.
The whole point of Ada.Calendar.Arithmetic was to provide a mechanism for
dealing with time differences that might exceed one day (specifically, that
might exceed the range of Duration). I would expect the results to be
identical if the operations are less than the range of Duration. (But of
course if you are not controlling the output Time_Zone, it might be hard to
see that.)
(Note: Unlike Tucker's answer, I think that these operations always do the
exact mathematical result, but the time zone of the answer is likely to be
implementation-defined unless you specify it somewhere. For Janus/Ada, a
math operation won't change the time zone of the result, but that time zone
might no longer be the local time zone if Summer time started or ended in
between. If you care about those things, you need to use Time_Zones
consistently throughout.)
... while option 2) is far from trivial to code by user.
It is trivial, just output the result with the same time zone as the
original. Otherwise, you are talking about a nonsense operation, and I don't
see what use it could possibly be. The number of hours in a day doesn't
change just because your clock hands are jumped forward or back. :-)
Randy.
|
Here is the concrete problem that triggered that issue. |
Could you post the actual program, so we could test it on our own favorite Ada compiler or Ada compiler version? |
In any case, I trust you agree with my analysis that expecting the adding of one day to produce the same time in the potentially new timezone could produces cases where there were either two answers or zero answers. |
Ada.Calendar.Formatting provides several functions similar to GNAT.Calendar, including the Day_Of_Week function, and the splitting of Sub_Seconds from seconds. As noted in Issue #15, Day_Of_Week should have a time zone parameter (certainly for consistency with the rest of the Ada.Calendar.Formatting package), and a Day_In_Year function would need one, too, if included in Ada.Calendar.Formatting. With the appropriate time zone parameter, one could have a Day_In_Year function (otherwise similar to the GNAT.Calendar version) as well as a Split function to convert the other way. The Time_Zone parameter when converting from a Time type would resolve the ambiguity inherent in the problem. The function profiles could look like this:
with these functions, scanning through the days in the year, as @jprosen wants to do in his program would be unambiguous, and the Time_Of function with the time zone parameter would keep the time zone consistent (if used consistently)
... it's a little verbose, but no days would be missed, and time zone can be explicit, if provided in Time_Of. |
Could you post the actual program, so we could test it on our own
favorite Ada compiler or Ada compiler version?
It would be interesting indeed. I tried the following:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Calendar.Arithmetic; use Ada.Calendar.Arithmetic;
with Ada.Calendar.Formatting; use Ada.Calendar.Formatting;
use Ada.Calendar;
procedure Main is
T : Time := Ada.Calendar.Formatting.Time_Of (2024, 4, 1, 0.0);
begin
Put_Line (Image (T));
for J in 1 .. 4 loop
T := T - 1;
Put_Line (Image (T));
end loop;
end Main;
and the output (with GNAT Pro 20240108) looks correct and as expected to me:
2024-04-01 00:00:00
2024-03-31 00:00:00
2024-03-30 00:00:00
2024-03-29 00:00:00
2024-03-28 00:00:00
In our own code we have actually introduced a full dates package which
only manipulates dates, rather than using a special time of day like
Jean-Pierre chose to use. This has the benefit of making it clear in
the subprogram profiles that we do not care about the
time. Maybe that's something that Ada.Calendar could consider (though
there is of course always the backward compatibility to think of, so the
current questions definitely need answering).
I agree with Tucker that adding or removing one day could end up with
two possible times (in languages like python, there is an extra
parameter to indicate whether you would want the first or second in such
cases), or no time at all (in this case I guess we should raise an
exception). PostgreSQL simply skips the time in this case:
=> select '2024-04-01T02:30:00 Europe/Paris'::timestamptz - '1
day'::interval, '2024-03-31T02:30:00 Europe/Paris'::timestamptz - '2
day'::interval, '2024-03-30T02:30:00 Europe/Paris'::timestamptz - '3
day'::interval;
┌────────────────────────┬────────────────────────┬────────────────────────┐
│ ?column? │ ?column? │ ?column? │
├────────────────────────┼────────────────────────┼────────────────────────┤
│ 2024-03-31 03:30:00+02 │ 2024-03-29 03:30:00+01 │ 2024-03-27 02:30:00+01 │
└────────────────────────┴────────────────────────┴────────────────────────┘
|
Here is a simple program showing the problem. Run, and note that there is no 26/03/2023. And try to find a simple solution for avoiding this problem...with Jan_1st : constant Time := Time_Of (2023, 01, 01); function Image (T : Time) return String is function Normalize (Left : Time) return Time is begin |
Here is a simple program showing the problem. Run, and note that there is
no 26/03/2023.
I did this using Janus/Ada, and there is indeed a 26/03/2023. So...
And try to find a simple solution for avoiding this problem...
Use Janus/Ada??? ;-)
I've always thought that using a Unix epoch is an incorrect implementation
of Ada. I couldn't convince anyone else of that, thus we left it
unspecified.
But it really doesn't make sense to use local time for any long-running time
calculation, since it jumps around. (My web server reboots every November
because the heartbeat timer doesn't get serviced for an hour when the time
changes. Ada didn't have timezones when I wrote that timer manager. I've
never fixed it because it is only once per year, but...)
And your program specifically made it jump around by using Local_Image. Why
didn't you use Image using the UTC time zone instead for this purpose? It
doesn't jump around, and you don't care about the hours anyway. I'd guess
that using Image with Time_Zone => 0 would not exhibit this anomaly.
Randy.
|
...
I've always thought that using a Unix epoch is an incorrect
implementation of Ada.
----------------------^
I meant Ada.Calendar.Time.
|
The date that disappears depends on your local time zone. So in US Eastern Time, 03/12/2023 disappears. In US Central Time, it is probably some other date. I believe the main problem with your program is that you are mixing operations from Ada.Calendar with operations from Ada.Calendar.Formatting, where Ada.Calendar uses some implementation-defined timezone with no specified semantics, while Ada.Calendar.Formatting has a well-defined notion of timezone, UTC, etc. Here is a program that uses operations from Ada.Calendar.Formatting and Ada.Calendar.Time_Zones exclusively:
This program doesn't skip any dates. |
The date that disappears depends on your local time zone. So in
US Eastern Time, 03/12/2023 disappears. In US Central Time, it is
probably some other date.
To reiterate, in Janus/Ada, all of the dates appear. (At least all of them
in March, April, and May.)
That's what I would expect, since there is no timezone information in an
Ada.Calendar.Time value in Janus/Ada. So when one does math on such a value,
the result is in the same timezone it originally was in (unless, of course,
you use an explicit timezone value). To me, that is the only reasonable
implementation for Ada.Calendar.Time.
However, others didn't see it that way and thus we have an unspecified
implementation of Ada.Calendar.Time vis-a-vis time zones. Unless you
explicitly specify a timezone when you do any operation on a Time value, the
result is essentially implementation-defined. Thus you MUST use a timezone
value with Image if you want consistent results (I think *any* timezone
value will do for your program, so long as it is constant).
That's essentially what Tucker's program does; I don't think anything is
necessary other than replacing Local_Image(T) with Image(T, Time_Zone => 0);
(or just Image(T), since the default is 0).
It's always dubious to do any math on local time values, as they necessarily
jump back and forth as the year goes along. Janus/Ada's definition made them
work usefully for delays and the like, but unfortunately the change to the
meaning of timezone probably will make it impossible to keep that the case
(thus potentially breaking all existing Janus/Ada code and making timing
much less reliable [Ada.Calendar Split operations will be many times more
expensive]).
In hindsight, I think Ada.Calendar.Time should have been required to be in
UTC time, but (a) that wouldn't have been possible on old Oses like MS-DOS
and CP/M, and (b) it would be way too incompatible today.
The only reasonable workaround is to to NEVER mix math and
Split/Time_Of/Image without time zones. The versions without time zones
should be restricted to display purposes only.
Randy.
Randy.
|
I confess that I didn't get the subtilities between Calendar and its children wrt time zones, but still different compilers behave differently, and it would be strange to state that "one day" is implementation defined... There must be a decision if -1 day is the day before, or 86400s. before. |
The unambiguous answer is that one day is 86400 seconds. Sorry if that wasn't clear through all of the discussion. |
The unambiguous answer is that one day is 86400 seconds. Sorry if
that wasn't clear through all of the discussion.
I agree. Timezones can confuse the issue, but there never was any intent
that it had anything to do with days on a calendar or what Split does.
Randy.
|
Ada.Calendar.Arithmetic provides "+" and "-" operations between a Time and a Day_Count. The RM says "Adds (resp. subtracts) a number of days to a time value". This seems to imply that the resulting Time should be the same hour with one more/less day, However there is no definition of what a "day" is, and especially whether it should be understood as a Time or as a Duration.
GNAT treats these functions like adding/subtracting 86400 s. to the given time (i.e. considering that a "day" is the same as a duration of 86400s.). In the case where the time is the day before/after switching between regular time and DST, a day is 23 or 25 hours. For example, if the provided time is "2023-03-27 00:00:00" and one day is subtracted, GNAT results in "2023-03-25 23:00:00". Is this intended?
The text was updated successfully, but these errors were encountered: