Skip to content
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

Open
jprosen opened this issue Apr 19, 2024 · 20 comments
Open

What is one day? #94

jprosen opened this issue Apr 19, 2024 · 20 comments
Labels
Question A request for clarification or interpretation of an aspect of Ada or the Ada Reference Manual.

Comments

@jprosen
Copy link

jprosen commented Apr 19, 2024

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?

@CKWG
Copy link

CKWG commented Apr 19, 2024

What do other Ada compilers do?

@sttaft
Copy link

sttaft commented Apr 19, 2024

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 ... ;-)

@ARG-Editor
Copy link
Collaborator

ARG-Editor commented Apr 20, 2024 via email

@ARG-Editor
Copy link
Collaborator

ARG-Editor commented Apr 20, 2024 via email

@jprosen
Copy link
Author

jprosen commented Apr 22, 2024

We are moving away from the issue here... The real question here is:

  1. Is "one day" in the "+" and "-" operations of Ada.Calendar.Arithmetic equivalent to a duration of 86_400s.
    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)

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.

@sttaft
Copy link

sttaft commented Apr 22, 2024

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.

@ARG-Editor
Copy link
Collaborator

ARG-Editor commented Apr 23, 2024 via email

@jprosen
Copy link
Author

jprosen commented Apr 28, 2024

Here is the concrete problem that triggered that issue.
I had a program that scanned through the whole year, starting at Dec. 31, back to January 1st. Since I was not interested in the time of day part, I normalized all the dates to 00:00:00, and subtracted 1 day in the loop. I had a hard time understanding why March 26th was skipped...
My understanding was that subtracting one day would give me the day before. Otherwise, that very simple need (scanning through the days in a year) becomes quite complicated...
(for the story: I solved the problem by normalizing times to 12:00:00 instead of 00:00:00 - but that's more of a kludge than a solution)

@sttaft
Copy link

sttaft commented Apr 28, 2024

Could you post the actual program, so we could test it on our own favorite Ada compiler or Ada compiler version?

@sttaft
Copy link

sttaft commented Apr 28, 2024

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.

@joshua-c-fletcher
Copy link

joshua-c-fletcher commented Apr 28, 2024

Ada.Calendar.Formatting provides several functions similar to GNAT.Calendar, including the Day_Of_Week function, and the splitting of Sub_Seconds from seconds.
The Language package added the time zone parameters, which GNAT.Calendar didn't have,
but GNAT.Calendar has a Day_In_Year function which only calculates one way.

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:

function Day_In_Year
  (Date      : Time;
   Time_Zone : Time_Zones.Time_Offset := 0) return Day_In_Year_Number;

function Day_In_Year
  (Year        : Year_Number;
   Month       : Month_Number;
   Day         : Day_Number) return Day_In_Year_Number;

procedure Split
  (Year        : in  Year_Number;
   Day_In_Year : in  Day_In_Year_Number;
   Month       : out Month_Number;
   Day         : out Day_Number);

-- optionally a version of the Time_Of and Split subprograms
-- from Ada.Calendar.Formatting could use Day_In_Year in place
-- of Month and Day, and they'd have the optional Time_Zone parameter
-- to resolve ambiguity.
-- However, the above functions could also be used with the existing ones.

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)

declare
   Year : constant Year_Number := Year (Clock);
   Month : Month_Number;
   Day : Day_Number;
   Last_Day : constant Day_In_Year_Number := Day_In_Year (Year => Year, Month => 12, Day => 31);
   Date : Ada.Calendar.Time;
begin
   for Day_In_Year in reverse 1 .. Last_Day loop
      Split (Year => Year, Day_In_Year => Day_In_Year, Month => Month, Day => Day);
      Date := Time_Of (Year => Year, Month => Month, Day => Day); -- other params defaulted
      -- do some processing for this date.
   end loop;
end;

... it's a little verbose, but no days would be missed, and time zone can be explicit, if provided in Time_Of.
(note existing Time_Of function is the one from Ada.Calendar.Formatting...)

@briot
Copy link

briot commented Apr 29, 2024 via email

@jprosen
Copy link
Author

jprosen commented May 1, 2024

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
Ada.Calendar,
Ada.Calendar.Arithmetic,
Ada.Calendar.Formatting,
Ada.Text_IO;
procedure Date_Issue is
use Ada.Calendar, Ada.Calendar.Arithmetic, Ada.Text_IO;

Jan_1st : constant Time := Time_Of (2023, 01, 01);
Generated_Date : Time := Time_Of (2023, 12, 31);

function Image (T : Time) return String is
Iso_Date : constant String := Ada.Calendar.Formatting.Local_Image (T);
-- YYYY-MM-DD HH:MM:SS
-- 1234 67 910
begin
return Iso_Date (9 .. 10) & '/' & Iso_Date (6..7) & '/' & Iso_Date (1..4);
end Image;

function Normalize (Left : Time) return Time is
-- Reset time of date to 00:00:00.0 to make comparisons on date only
begin
return Time_Of (Year (Left), Month (Left), Day (Left), 0.0);
end Normalize;


begin
while Generated_Date >= Jan_1st loop
Put_Line (Image (Generated_Date));
Generated_Date := Normalize (Generated_Date - 1);
end loop;
end Date_Issue;

@ARG-Editor
Copy link
Collaborator

ARG-Editor commented May 1, 2024 via email

@ARG-Editor
Copy link
Collaborator

ARG-Editor commented May 1, 2024 via email

@sttaft
Copy link

sttaft commented May 1, 2024

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:

with Ada.Calendar,
Ada.Calendar.Arithmetic,
Ada.Calendar.Formatting,
Ada.Text_IO;
with Ada.Calendar.Time_Zones;
procedure Date_Issue_2 is
   use Ada.Calendar.Arithmetic, Ada.Calendar.Formatting, Ada.Text_IO;
   use Ada.Calendar.Time_Zones;
   subtype Time is Ada.Calendar.Time;
   use type Time;

   Jan_1st : constant Time := Time_Of (2023, 01, 01);
   Generated_Date : Time := Time_Of (2023, 12, 31);

   function My_Image (T : Time) return String is
      Iso_Date : constant String := Ada.Calendar.Formatting.Image (T);
      -- YYYY-MM-DD HH:MM:SS
      -- 1234 67 910
   begin
      return Iso_Date (9 .. 10) & '/' & Iso_Date (6..7) &
        '/' & Iso_Date (1..4);
   end My_Image;

   function Normalize (Left : Time) return Time is
   -- Reset time of date to 00:00:00.0 to make comparisons on date only
   begin
      return Time_Of (Year (Left), Month (Left), Day (Left), 0.0);
   end Normalize;

begin
   while Generated_Date >= Jan_1st loop
      Put_Line (My_Image (Generated_Date));
      Generated_Date := Normalize (Generated_Date - 1);
   end loop;
end Date_Issue_2;

This program doesn't skip any dates.

@ARG-Editor
Copy link
Collaborator

ARG-Editor commented May 2, 2024 via email

@jprosen
Copy link
Author

jprosen commented May 2, 2024

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.

@sttaft
Copy link

sttaft commented May 2, 2024

The unambiguous answer is that one day is 86400 seconds. Sorry if that wasn't clear through all of the discussion.

@ARG-Editor
Copy link
Collaborator

ARG-Editor commented May 3, 2024 via email

@ARG-Editor ARG-Editor added the Question A request for clarification or interpretation of an aspect of Ada or the Ada Reference Manual. label Oct 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question A request for clarification or interpretation of an aspect of Ada or the Ada Reference Manual.
Projects
None yet
Development

No branches or pull requests

6 participants