Version 1.2 of ai05s/ai05-0119-1.txt

Unformatted version of ai05s/ai05-0119-1.txt version 1.2
Other versions for file ai05s/ai05-0119-1.txt

!standard 9.6(24)          08-10-17 AI05-0119-1/00
!standard 9.6.1(41/2)
!standard 9.6.1(42/2)
!class Amendment 08-10-17
!status work item 08-10-17
!status received 08-08-05
!priority Medium
!difficulty Hard
!subject Package Calendar, Daylight Savings Time, and UTC_Offset
!summary
(See proposal.)
!problem
Ada.Calendar time is not well-defined in the presence of daylight saving time adjustments.
1. Ada.Calendar.Time_Of is ill-defined within the hour when the clock is adjusted backwards. The same year-month-day-seconds quadruple may denote two different dates. For example:
year => 2008, month => 10, day => 26, seconds => 2.5 * 3600.0
may refer in Central Europe to 02:30 before the clock adjustment (02:30 CEST) or else a date 1 hour later, after the clock adjustment (02:30 CET).
2. The semantics of Ada.Calendar.Time is not defined. The following example illustrates the case:
T0, T1, T2 : Time;
begin
T0 := Time_Of - 01:30 CEST ( Year => 2008, Day => 26, Month => 10, Seconds => 1.5 * 3600.0 ); T1 := T0 + 3600.0; -- +1h T2 := T0 + 2.0 * 3600.0; -- +2h
The Standard does not clarify whether T1 = T2 or not. A similar question can be asked about the time retrieved by Ada.Calendar.Clock at the times T1 and T2. (The answer may not be the same.)
3. Ada.Calendar.Time_Zones.UTC_Offset function is ill-defined because of the issue of 2. If T1 = T2, then UTC_Offset is broken because T1 is CEST and T2 is CET. These have different offsets to the UTC time, +2h and +1h correspondingly.
!proposal
** TBD **
!wording
** TBD **
!discussion
[Editor's note: To think about this in the US, use the time November 02, 2008 01:30 AM as the clock will be adjusted at 2:00 AM that day.]
The basic problem is that a number of characteristics of Ada.Calendar were left unspecified. This is problematical, because it means that applications that depend on the clock (especially those that require UTC time) are not portable and may break occassionally when the clock is adjusted (depending on the implementation model.
A number of fixes are possible:
(1) Confirm the standard that these things are undefined. This surely won't break anything, but it also means that there is no way to write some applications in Ada (Ada.Real_Time does not have sufficient range to be used as a replacement).
(2) Change the definition of Ada.Calendar.Time to ensure that some indication of the time zone and adjustment is included. Then operations like these can be defined unambigously. But that changes the representation of Ada.Calendar.Time on many implementations.
(3) Define that Ada.Calendar.Time is UTC_Time, and provide a function to get local time. That's what Ada 83 ought to have done, but it seems too incompatable to change now.
(4) Add wording to 9.6.1(41/2) to specify which result is returned in ambiguous cases. But that really doesn't help much, because there is then an hour of UTC time that can never occur.
(5) Create a parallel universe of a UTC_Time package which works right. But that seems amazingly heavy.
(6) A simplified proposal like the one found in the e-mail attached here. But that means that for most operations, UTC.Time has to be converted to Calendar.Time (since UTC.Time does not have constructors or splitters), which would clutter programs.
There probably are even more approaches necessary. But first we have to decide whether to actually fix this problem.
!example
!ACATS test
!appendix

From: Dmitry A. Kazakov
Date: Tuesday, August 5, 2008  3:57 AM

1. What is the result (according to the language reference manual) of:

   UTC_Time_Offset (Ada.Calendar.Time_Of (2008, 26, 10, 2.5*60.0*60.0));

called on a machine with the time zone Amsterdam, Berlin, Bern...

Note. 26.10.2008 is the day when the clock will be adjusted 1h backward at 03:00.

2. What is the result of Ada.Calendar.Split applied to Ada.Calendar.Clock
called:

2.a. at 26.10.2008 02:30 before clock adjustment; 2.b. one hour later
(at 26.10.2008 02:30 after clock adjustment).

-------------------
UTC_Time_Offset and Time_Of are ambiguous as defined in the RM.

RM 9.6.1 42/2:

"Returns, as a number of minutes, the difference between the
implementation-defined time zone of Calendar, and UTC time, at the time Date."

It is not a function.

RM 9.6 24/2:

"The functions Year, Month, Day, and Seconds return the corresponding values
for a given value of the type Time, as appropriate to an
implementation-defined time zone; the procedure Split returns all four
corresponding values. Conversely, the function Time_Of combines a year number,
a month number, a day number, and a duration, into a value of type Time."

Split cannot be "converse," because it is irreversible.

****************************************************************

From: Jeffrey R. Carter
Date: Wednesday, August 6, 2008  2:16 PM

> 1. What is the result (according to the language reference manual) of:
> 
>    UTC_Time_Offset (Ada.Calendar.Time_Of (2008, 26, 10, 
> 2.5*60.0*60.0));
> 
> called on a machine with the time zone Amsterdam, Berlin, Bern...

This depends on the definition of the user-defined procedure UTC_Time_Offset.

Presuming "use Ada.Calendar.Time_Zones;" and no user-defined procedure
UTC_Time_Offset, a compiler error message, since
Ada.Calendar.Time_Zones.UTC_Time_Offset is a function, and this is a
procedure call.

Presuming further that this is a fragment and does represent a function call,
Constraint_Error should be raised at the point of the call, because 26
is not a value of Ada.Calendar.Month_Number.

****************************************************************

From: Christoph Grein
Date: Wednesday, August 6, 2008  11:47 PM

I think your answer is a bit saucy, at least a bad joke.
The question is absolute clear for any non-malevolent person :-(

The RM is unclear about this, and the AARM (42.b/2, c/2) admits this.

****************************************************************

From: Jeffery R. Carter
Date: Thursday, August 7, 2008  2:09 PM

> I think your answer is a bit saucy, at least a bad joke.
> The question is absolute clear for any non-malevolent person :-(

Perhaps. Is it unreasonable to expect precision from someone who is
asking the ARG for precision?

> The RM is unclear about this, and the AARM (42.b/2, c/2) admits this.

I'm working on an application that deals with times in different time
zones, though we never have to deal with times when the clock changes.
I've found that understanding and reasoning about the definitions for
dealing with time zones in the ARM requires careful attention, but the
conclusions I've reached have agreed with the GNAT implementation so far.

****************************************************************

From: Pascal Leroy
Date: Thursday, August 7, 2008  3:06 AM

> RM 9.6.1 42/2:

> "Returns, as a number of minutes, the difference between the
> implementation-defined time zone of Calendar, and UTC time, at the time
> Date."
>
> It is not a function.

I think that 42/2 is fine because UTC_Time_Offset *is* a function.
But 44/2 could be worded better because Difference is *not* a function.
Perhaps "Computes the difference between...".
 
> RM 9.6 24/2:
> 
> "The functions Year, Month, Day, and Seconds return the corresponding
> values for a given value of the type Time, as appropriate to an
> implementation-defined time zone; the procedure Split returns all four
> corresponding values. Conversely, the function Time_Of combines a year
> number, a month number, a day number, and a duration, into a value of type
> Time."
>
> Split cannot be "converse," because it is irreversible.

I think you are reading too much in the word "conversely".  I don't
believe that it was intended to imply reversibility.

****************************************************************

From: Dmitry A. Kazakov
Date: Thursday, August 7, 2008  3:30 PM

> I think that 42/2 is fine because UTC_Time_Offset *is* a function.  
> But 44/2 could be worded better because Difference is *not* a 
> function.  Perhaps "Computes the difference between...".

That does not compute either. Because "the difference" implies that
there is exactly one result for each argument, which is equivalent of
being a mathematical function.

...

> I think you are reading too much in the word "conversely".  I don't 
> believe that it was intended to imply reversibility.

If that is true, then the text becomes meaningless: "four corresponding
values" of what? How they correspond etc.

Basically the texts are inconsistent. Either:

1. Ada.Calendar.Time is defined to have different values (in the sense
of the operation "=") at 26.10.2008 02:30 before the clock skew and after
it. In this case Time_Of cannot be defined as it was, without specifying
with of two dates is meant.

2. Ada.Calendar.Time has same values at both times, then Time_Of is
well-defined but UTC_Time_Offset is not a function of the argument.

Now which variant is mandatory? And what is the semantics of ill-defined
things?

P.S. As for existing implementations. GNAT under Windows seems to use the
variant 1, with Time_Of returning the time after the skew.

****************************************************************

From: Adam Beneschan
Date: Thursday, August 7, 2008  7:01 PM

> > I think that 42/2 is fine because UTC_Time_Offset *is* a function.  
> > But 44/2 could be worded better because Difference is *not* a 
> > function.  Perhaps "Computes the difference between...".
> 
> That does not compute either. Because "the difference" implies that 
> there is exactly one result for each argument, which is equivalent of 
> being a mathematical function.

But there *is* one "difference".  The part that returns "the difference"
is a mathematical function.  The result of this function is then broken
into three parts and returned as three OUT parameters.
I don't see the problem---it seems clear to me.  And Ada-Comment regulars
are aware that I'm a major nit-picker whenever I see wording in the RM
that I think doesn't say what it means, or is ambiguous.
But this paragraph doesn't bother me at all, although I can see why
"Returns the difference" appears a little odd since Difference isn't an
Ada function.

...
> > I think you are reading too much in the word "conversely".  I don't 
> > believe that it was intended to imply reversibility.
> 
> If that is true, then the text becomes meaningless: "four 
> corresponding values" of what? How they correspond etc.

It means that Split returns four OUT parameters, "Year", "Month", "Day",
and "Seconds"; and these OUT parameters are set to the same values that
the functions of the same names would return.  Thus, Split (Date, Y, M, D, S)
sets Y := Year(Date), M := Month(Date), and so on.
That's what "corresponding" means.  It has nothing whatsoever to do with the
second sentence of paragraph 24, nor of the Time_Of function. 

Your issues regarding how this package works around Daylight Savings Time
changes point out a real problem.  But the questions about wording that you're
asking above don't have anything to do with this issue. 

****************************************************************

From: Dmitry A. Kazakov
Date: Friday, August 8, 2008  3:09 AM

...
>> That does not compute either. Because "the difference" implies that 
>> there is exactly one result for each argument, which is equivalent of 
>> being a mathematical function.
> 
> But there *is* one "difference".

No, I meant UTC_Time_Offset, which depends on the definition of the
political time. The customary, naive, definition seen in texts refers
to "clock adjustment." This reads to me as 26.10.2008 02:30 would be
exactly the same Berlin time before and after the clock skew. Otherwise,
I don't know what the [political] clock does show. In my previous post
it was denoted as the variant 2. The variant 1 referred to a hidden UTC
clock which is inconsistent with Time_Of.

[As well as with political time, in general. I greatly welcome GNAT's
implementation dumping political time, but was it the intention of RM?
I thought it wished to preserve political time...]

...
> It means that Split returns four OUT parameters, "Year", "Month", 
> "Day", and "Seconds"; and these OUT parameters are set to the same 
> values that the functions of the same names would return.  Thus, Split 
> (Date, Y, M, D, S) sets Y := Year(Date), M := Month(Date), and so on.
> That's what "corresponding" means.  It has nothing whatsoever to do 
> with the second sentence of paragraph 24, nor of the Time_Of function.

How so? The wording is:

   "The functions Year, Month, Day, and Seconds return the corresponding
   values for a given value of the type Time ..." -- 9.6 24/2

I.e. the "correspondence" is to be between Year, Month, Day, Seconds and
Time. Now, do the quadruple (y, m, d, s) unambiguously denote a political
time date or not? RM is silent about that, though the wording let suggest
that it does [=variant 2!]. Observe, that the quadruple does not [always]
denote an UTC time [because seconds there are "political"], which is
inconsistent with "the difference" allegedly returned by UTC_Time_Offset,
etc.

> Your issues regarding how this package works around Daylight Savings 
> Time changes point out a real problem.  But the questions about 
> wording that you're asking above don't have anything to do with this 
> issue.

My question was about the semantics. Actually, I don't care much about
wording so long I can understand the meaning. Reading RM I was perplexed
what is the expected behavior of UTC_Time_Offset, Time_Of and Split.

P.S. The problem is quite technical. How to convert Time to UTC time and
reverse?

****************************************************************

From: Adam Beneschan
Date: Friday, August 8, 2008  2:24 PM

> How so? The wording is:
> 
>    "The functions Year, Month, Day, and Seconds return the 
> corresponding values for a given value of the type Time ..." -- 9.6 
> 24/2
> 
> I.e. the "correspondence" is to be between Year, Month, Day, Seconds 
> and Time. Now, do the quadruple (y, m, d, s) unambiguously denote a 
> political time date or not?

OK, I see.  The word "corresponding" appears twice in this paragraph,
and I thought you were asking about the use of the word in the phrase
"four corresponding values", not the other use of the word.  I'm
beginning to think the RM is using this word too much, especially since
a different issue that came up on this list today also involves how
that same word is interpreted.  I suggest that "corresponding" be added
to the Glossary (Annex N), with the definition "We assume you know what
we mean".  :) :)

Anyway, I was answering the question I thought you were asking, but I
don't have an answer for the question you really wanted to know about.
I'll have to leave that to others, for now.

****************************************************************

From: Dmitry A. Kazakov
Date: Thursday, August 14, 2008  10:31 AM

!summary

The proposal is to define the semantics of political time and to
add UTC time. The proposal reuses as much as possible of the existing
packages descending from Ada.Calendar. Ada.Calendar.Clock can be
derived from UTC clock.

!problem

In presence of daylight saving:

1. Ada.Calendar.Time_Of is ill-defined within the hour when the clock
is adjusted backwards. The same year-month-day-seconds quadruple may
denote two different dates. For example:

year => 2008,
month => 10,
day => 26,
seconds => 2.5 * 3600.0

may refer in Central Europe to 02:30 before the clock adjustment (02:30
CEST) or else a date 1 hour later, after the clock adjustment (02:30 CET).

2. The semantics of Ada.Calendar.Time is not defined. The following
example illustrates the case:

   T0, T1, T2 : Time;
begin
   T0 := Time_Of  01:30 CEST
         (  Year    => 2008,
            Day     => 26,
            Month   => 10,
            Seconds => 1.5 * 3600.0
         );
   T1 := T0 + 3600.0; -- +1h
   T2 := T0 + 2.0 * 3600.0; -- +2h

RM does not clarify whether T1 = T2 or not.

3. Ada.Calendar.Time_Zones.UTC_Offset function is ill-defined because
of the issue of 2. If T1 = T2, then UTC_Offset is broken because T1 is
CEST and T2 is CET. These have different offsets to the UTC time, +2h
and +1h correspondingly.

!proposal

1. Ada.Calendar.Time is defined as a political time. In the example:

   T0, T1, T2 : Time;
begin
   T0 := Time_Of -- 01:30 CEST
         (  Year    => 2008,
            Day     => 26,
            Month   => 10,
            Seconds => 1.5 * 3600.0
         );
   T1 := T0 + 3600.0; -- +1h
   T2 := T0 + 2.0 * 3600.0; -- +2h

It is required T1 = T2. The time arithmetic is defined to satisfy this
requirement.

In particular, if a difference is calculated between two times of which
one belongs to the overlapping hour, then this time is treated as having
the same UTC offset as another. For example:

   T0, T1, T2 : Time;
begin
   T0 := Time_Of  01:30 CEST
         (  Year    => 2008,
            Day     => 26,
            Month   => 10,
            Seconds => 1.5 * 3600.0
         );
   T1 := Time_Of  02:30 CEST or CET
         (  Year    => 2008,
            Day     => 26,
            Month   => 10,
            Seconds => 2.5 * 3600.0
         );
   T2 := Time_Of  03:30 CET
         (  Year    => 2008,
            Day     => 26,
            Month   => 10,
            Seconds => 3.5 * 3600.0
         );

T2  T1 = 3600.0, here T1 is considered CET as T2 is. T1  T0 = 3600.0,
T1 here is considered CEST.

2. Ada.Calendar.Time_Zones.UTC_Offset propagates Time_Error when the
argument is ambiguous, like:

         Time_Of  02:30 CEST or CET?
         (  Year    => 2008,
            Day     => 26,
            Month   => 10,
            Seconds => 2.5 * 3600.0
         );

3. The following package is introduced:

package Ada.UTC is
   type Time is private;
   function Clock return Time;
   function "+" (Left : Time; Right : Duration) return Time;
   function "+" (Left : Duration; Right : Time) return Time;
   function "-" (Left : Time; Right : Duration) return Time;
   function "-" (Left : Time; Time : Time) return Duration;
   function "<" (Left, Right : Time) return Boolean;
   function "<="(Left, Right : Time) return Boolean;
   function ">" (Left, Right : Time) return Boolean;
   function ">="(Left, Right : Time) return Boolean; end Ada.UTC;

The package provides UTC clock, time and operations on it.

4. The package Ada.Calendar.Time_Zones contains four new functions:

4.a. function UTC_Time_Offset
        (Date : Ada.UTC.Time := Ada.UTC.Clock)
           return Time_Offset;

This function returns the difference between the political time and the
UTC time for its argument. The function is well-defined for each its
argument.

4.b. function To_Calendar_Time
       (Time : Ada.UTC.Time; Time_Zone : Time_Offset)
           return Time;

This function returns Ada.Calendar.Time corresponding to the argument
for the time zone offset defined by the parameter Time_Zone.

4.c. function To_UTC (Time : Ada.UTC.Time)
        return Time;

This function is equivalent to:

     To_UTC (Time, UTC_Time_Offset (Time));

4.d. function To_UTC
        (Time : Ada.UTC.Time; Time_Zone : Time_Offset)
            return Time;

This function converts UTC time to the political time with the time offset
specified by the parameter Time_Zone.

5. Implementation. Ada.Calendar.Time and Ada.UTC.Time can be implemented
as the same type. In which case Ada.Calendar.Clock could be directly
derived from Ada.UTC.Clock:

   function Ada.Calendar.Clock return Ada.Calendar.Time is
      T : Ada.UTC.Time := Ada.UTC.Clock;
   begin
      return
         Ada.Calendar.Time
            (T + Ada.Calendar.Time_Zones.UTC_Time_Offset (T));
   end Ada.Calendar.Clock;

!examples

1. Conversion to UTC time:

   T : Ada.Calendar.Time :=
          Ada.Calendar.Time_Of  02:30 CEST or CET
          (  Year    => 2008,
             Day     => 26,
             Month   => 10,
             Seconds => 2.5 * 3600.0
          );
   T_CEST : Ada.UTC.Time :=
               Ada.Calendar.Time_Zones.To_UTC (T, 60);
   T_CET  : Ada.UTC.Time :=
               Ada.Calendar.Time_Zones.To_UTC (T, 2*60);

2. Conversion from UTC. Assuming the declarations above:
   
Ada.Calendar.Time_Zones.To_Calendar_Time (T_CEST) = T
Ada.Calendar.Time_Zones.To_Calendar_Time (T_CET) = T

3. Composition of UTC time from year-month-day-seconds:

Ada.Calendar.Time_Zones.To_UTC
(  Ada.Calendar.Formatting.Time_Of
   (  Year        => 2008,
      Day         => 26,
      Month       => 10,
      Seconds     => 2.5 * 3600.0,
      Time_Offset => 0
   ),
   Time_Offset => 0
);

4. Splitting UTC time into year-month-day-seconds:

Ada.Calendar.Formatting.Split
(  Date        => Ada.Calendar.Time_Zones.To_Calendar_Time (T, 0),
   Year        => Year,
   Month       => Month,
   Day         => Day,
   Seconds     => Seconds,
   Leap_Second => Leap_Second,
   Time_Zone   => 0
);

****************************************************************

From: Pascal Leroy
Date: Thursday, August 28, 2008  6:02 AM

>1. Ada.Calendar.Time is defined as a political time. In the example:

>  T0, T1, T2 : Time;
>begin
>  T0 := Time_Of -- 01:30 CEST
>        (  Year    => 2008,
>           Day     => 26,
>           Month   => 10,
>           Seconds => 1.5 * 3600.0
>        );
>  T1 := T0 + 3600.0; -- +1h
>  T2 := T0 + 2.0 * 3600.0; -- +2h
>
>It is required T1 = T2. The time arithmetic is defined to satisfy this
>requirement.

Taken at face value, this statement doesn't make any sense. If
T1 = T2 then presumably T1 + 3600.0 = T2 + 3600.0, but since T1 + 3600.0
is T2, then T2 = T2 + 3600.0.  I don't think that you imply this, but
then it would be good to present an implementation model for your
proposal, since it seems to require that the time values "remember"
how they were obtained.  (And that seems fairly horrendous.)
 
> In particular, if a difference is calculated between two times of which one
> belongs to the overlapping hour, then this time is treated as having the
> same UTC offset as another. For example:

This doesn't help if both times belong to the overlapping hour.
And it is not clear that this is what you want in the other cases anyway.
It seems to me that you are just trying to infer a timezone that was never
provided to Time_Of in the first place. You might guess right some of the
time, but that's not better than the current state of affairs.
 
The only sound way to address the issues you are mentioning would be to
have variants of Time_Of et al. that take the designation of a timezone
(not only a time offset) and keep track of all the time changes that take
place in that timezone.  That was rejected long ago, because it seems
absurd to have the Ada runtime know about all this timezone stuff, which
changes periodically anyway.  This is especially true in an embedded
environment, where no-one cares about timezones.

****************************************************************

From: Dmitry A. Kazakov
Date: Friday, August 29, 2008  4:16 AM

...
> Taken at face value, this statement doesn't make any sense.  If T1 = 
> T2 then presumably T1 + 3600.0 = T2 + 3600.0, but since T1 + 3600.0 is 
> T2,

No, T1 + 3600.0 /= T2.

You assumed associativity of "+", which does not hold for the
political time. In general:

(T + D1) + D2 /= T + (D1 + D2)

This is the case when T = T0 and D1, D2 = 3600.0.

> then T2 = T2 + 3600.0.

T1 + 3600.0 = T2 + 3600.0

> I don't think that you imply this, but then it would be good to 
> present an implementation model for your proposal, since it seems to 
> require that the time values "remember" how they were obtained.  (And 
> that seems fairly horrendous.)

The semantics of Time + Duration is well-defined:

A straightforward implementation of is:

1. Time is converted to UTC. This operation is unambiguous when Time
is outside the overlapping hours. When Time is in such an hour, then
for positive Duration Time is converted to the UTC after time skew.
When Duration is negative it is converted to the UTC before it.

2. Duration is added

3. Result is converted back to the political time. This operation is always
unambiguous.

>> In particular, if a difference is calculated between two times of 
>> which one belongs to the overlapping hour, then this time is treated 
>> as having the same UTC offset as another. For example:
> 
> This doesn't help if both times belong to the overlapping hour.

In this case the difference is calculated directly considering the UTC
offsets equal.

> And it is
> not clear that this is what you want in the other cases anyway.  It 
> seems to me that you are just trying to infer a timezone that was 
> never provided to Time_Of in the first place.

It is true that arithmetic of political time cannot be consistently defined
without knowing the timezone. It should not surprise anyone. Political time
is garbage. The problem is not in the timezone, it is whether the time zone
offset should be derivable from the values of Time. My proposal (T1=T2) is
that it should not. Yet, an implementation is free to choose UTC, internally
and then mangle +, -, =, in accordance to the "political will" (:-))...

> The only sound way to address the issues you are mentioning would be 
> to have variants of Time_Of et al. that take the designation of a 
> timezone (not only a time offset) and keep track of all the time 
> changes that take place in that timezone.

That would not define the semantics of Time. My proposal does it, by defining
the political time in terms of UTC. This is how the political time is defined
in the real world.

> That was rejected long ago, because it seems absurd to have the Ada 
> runtime know about all this timezone stuff, which changes periodically 
> anyway.  This is especially true in an embedded environment, where 
> no-one cares about timezones.

Right, my proposal provides a way to define Time on the platform where UTC
is unavailable. On these Time_Offset is considered constant 0.

****************************************************************

Questions? Ask the ACAA Technical Agent