!standard 9.06(11) 04-03-26 AI95-00351/04
!standard 9.06(24)
!standard 9.06.01(01)
!class amendment 03-09-22
!status Amendment 200Y 04-03-26
!status ARG Approved 9-0-0 04-03-07
!status work item 03-09-22
!status received 03-09-22
!priority Medium
!difficulty Easy
!subject Time operations
!summary
This proposal adds a number of useful operations for Calendar.Time.
!problem
Calendar.Time is missing a number of operations commonly needed by applications.
First, there is no day-of-the-week function. It is often necessary to do a task
on a particular day of the week (maintenance tasks in servers often are
executed on Sundays when the load is minimal). Determining the day of the week
for an Ada.Calendar.Time value is complex.
Second, determining the Hour:Minutes:Seconds representation of a time value is
not trivial. This representation is needed for most output of time values.
Similarly, values often need to be converted from Hours:Minutes:Seconds to
time values.
Third, determining elapsed time is difficult if more than Duration'Last seconds
have elapsed. This makes it more likely that Ada programs and libraries will
fail if they run longer than anticipated. We've had profilers, loggers, and
other components fail due to this shortcoming.
Fourth, displaying a time value is complex. This is needed not only in
carefully formatted output, but also in logs and debugging code.
Finally, determining the UTC time is not possible. This is needed in many
applications, such as internet communication. Since Ada does not define
whether the time value represents UTC time, local time, or some other time,
it isn't possible to use Calendar.Time in such applications.
!proposal
(See wording.)
!wording
Change Year_Number in 9.6(11):
subtype Year_Number is Integer range 1901 .. 2399;
Add a new clause following 9.6:
9.6.1 Formatting, Time Zones, and other operations for Time
The following language-defined library packages exist:
package Ada.Calendar.Time_Zones is
-- Time zone manipulation:
type Time_Offset is range -1440 .. 1440;
Unknown_Zone_Error : exception;
function UTC_Time_Offset (Date : in Time := Clock) return Time_Offset;
end Ada.Calendar.Time_Zones;
package Ada.Calendar.Arithmetic is
-- Arithmetic on days:
type Day_Count is range
-366*(1+Year_Number'last - Year_Number'first)
..
366*(1+Year_Number'last - Year_Number'first);
subtype Leap_Seconds_Count is Integer range -999 .. 999;
procedure Difference (Left, Right : in Time;
Days : out Day_Count;
Seconds : out Duration;
Leap_Seconds : out Leap_Seconds_Count);
function "+" (Left : Time; Right : Day_Count) return Time;
function "+" (Left : Day_Count; Right : Time) return Time;
function "-" (Left : Time; Right : Day_Count) return Time;
function "-" (Left, Right : Time) return Day_Count;
end Ada.Calendar.Arithmetic;
with Ada.Calendar.Arithmetic, Ada.Calendar.Time_Zones;
package Ada.Calendar.Formatting is
-- Day of the week:
type Day_Name is (Monday, Tuesday, Wednesday, Thursday,
Friday, Saturday, Sunday);
function Day_of_Week (Date : Time) return Day_Name;
-- Hours:Minutes:Seconds access:
subtype Hour_Number is Natural range 0 .. 23;
subtype Minute_Number is Natural range 0 .. 59;
subtype Second_Number is Natural range 0 .. 59;
subtype Second_Duration is Day_Duration range 0.0 .. 1.0;
function Hour (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0)
return Hour_Number;
function Minute (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0)
return Minute_Number;
function Second (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0)
return Second_Number;
function Sub_Second (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0)
return Second_Duration;
function Seconds_Of (Hour : in Hour_Number;
Minute : in Minute_Number;
Second : in Second_Number := 0;
Sub_Second : in Second_Duration := 0.0)
return Day_Duration;
procedure Split (Seconds : in Day_Duration;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Sub_Second : out Second_Duration);
procedure Split (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0;
Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Sub_Second : out Second_Duration);
function Time_Of (Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
Hour : Hour_Number;
Minute : Minute_Number;
Second : Second_Number;
Sub_Second : Second_Duration := 0.0;
Leap_Second: Boolean := False;
Time_Zone : Time_Zones.Time_Offset := 0)
return Time;
function Time_Of (Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
Seconds : Day_Duration;
Leap_Second: Boolean := False;
Time_Zone : Time_Zones.Time_Offset := 0)
return Time;
procedure Split (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0;
Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Sub_Second : out Second_Duration;
Leap_Second: out Boolean);
procedure Split (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0;
Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Seconds : out Day_Duration;
Leap_Second: out Boolean);
-- Simple image and value:
function Image (Date : Time;
Include_Time_Fraction : Boolean := False) return String;
function Value (Date : String) return Time;
function Image (Elapsed_Time : Duration;
Include_Time_Fraction : Boolean := False) return String;
function Value (Elapsed_Time : String) return Duration;
end Ada.Calendar.Formatting;
Type Time_Offset represents the number of minutes difference between the
implementation-defined time zone used by Ada.Calendar and another time zone.
function UTC_Time_Offset (Date : in Time := Clock) return Time_Offset;
Returns, as a number of minutes, the difference between the
implementation-defined time zone of Calendar, and UTC time,
at the time Date. If the time zone of the Calendar implementation is
unknown, then Unknown_Zone_Error is raised.
Notes: The time in the time zone known as Greenwich Mean Time (GMT)
is generally equivalent to UTC time.
The implementation-defined time zone used for type Time may be, but need
not be, the local time zone. UTC_Time_Offset always returns the difference
relative to the implementation-defined time zone of package Calendar. If
UTC_Time_Offset does not raise Unknown_Zone_Error, UTC time can be
safely calculated (within the accuracy of the underlying time-base).
AARM Notes: The Date parameter is needed to take into account time
differences caused by daylight-savings time and other time changes.
Other time zones can be supported with a child package.
The accuracy of this routine is not specified; the intent is that the
facilities of the underlying target operating system are used to implement
it.
Calling Split on the results of subtracting Duration(UTC_Time_Offset*60)
from Clock provides the components (hours, minutes, and so on) of the UTC
time. In the US, for example, UTC_Time_Offset will generally be negative.
AARM Note
This is an illustration to help specify the value of
UTC_Time_Offset. A user should pass UTC_Time_Offset as the Time_Zone
parameter of Split, rather than trying to make the above calculation.
procedure Difference (Left, Right : in Time;
Days : out Day_Count;
Seconds : out Duration;
Leap_Seconds : out Leap_Seconds_Count);
Returns the difference between Left and Right.
Days is the number of days of difference, Seconds is the
remainder seconds of difference, and Leap_Seconds is the number
of leap seconds.
If Left < Right, then Seconds <= 0.0, Days <= 0, and Leap_Seconds <= 0.
Otherwise, all values are non-negative.
For the returned values, if Days = 0, then
If Days = 0, then Seconds + Duration(Leap_Seconds) =
Calendar."-" (Left, Right).
AARM Notes: The number of days is calculated midnight-to-midnight.
Leap_Seconds, if any, are not included in Seconds.
Leap_Seconds should be included in calculations using the operators
defined in Calendar, as is specified for "-" above.
function "+" (Left : Time; Right : Day_Count) return Time;
function "+" (Left : Day_Count; Right : Time) return Time;
Add a number of days to a time value. Time_Error is raised
if the result is not representable as a value of type Time.
function "-" (Left : Time; Right : Day_Count) return Time;
Subtract a number of days from a time value. Time_Error is raised
if the result is not representable as a value of type Time.
function "-" (Left, Right : Time) return Day_Count;
Subtract two time values, and return the number of days between them.
This is the same value that Difference would return in Days.
function Day_of_Week (Date : Time) return Day_Name;
Returns the day of the week for Time. This is based on the Year, Month, and
Day values of Time.
function Hour (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0)
return Hour_Number;
Returns the hour for Date, as appropriate for the specified time zone
offset.
function Minute (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0)
return Minute_Number;
Returns the minute within the hour for Date, as appropriate for the
specified time zone offset.
function Second (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0)
return Second_Number;
Returns the second within the hour and minute for Date, as appropriate for
the specified time zone offset.
function Sub_Second (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0)
return Second_Duration;
Returns the fraction of second for Date. (This has the same accuracy as
Day_Duration), as appropriate to the specified time zone offset.
function Seconds_Of (Hour : in Hour_Number;
Minute : in Minute_Number;
Second : in Second_Number := 0;
Sub_Second : in Second_Duration := 0.0)
return Day_Duration;
Returns a Day_Duration value for the Hour:Minute:Second.Sub_Second.
This value can be used in Calendar.Time_Of as well as the argument to
Calendar."+" and Calendar."-".
procedure Split (Seconds : in Day_Duration;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Sub_Second : out Second_Duration);
Split Seconds into Hour:Minute:Second.Sub_Second.
procedure Split (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0;
Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Sub_Second : out Second_Duration);
Split Date into its constituent parts (Year, Month, Day, Hour, Minute,
Second, Sub_Second), relative to the specified time zone offset.
function Time_Of (Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
Hour : Hour_Number;
Minute : Minute_Number;
Second : Second_Number;
Sub_Second : Second_Duration := 0.0;
Leap_Second: Boolean := False;
Time_Zone : Time_Zones.Time_Offset := 0)
return Time;
Returns a Time built from the date and time values, relative to the
specified time zone offset. Time_Error is raised if Leap_Second is True,
and Hour, Minute, and Second are not appropriate for a Leap_Second.
AARM Note: A leap second always occurs at midnight UTC, and is 23:59:60
UTC in ISO notation. So, if the time zone is UTC, if any of Hour /= 23,
Minute /= 59, or Second /= 59, then Time_Error should be raised.
However, we do not say that, because other time zones will have different
values where a leap second is allowed.
function Time_Of (Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
Seconds : Day_Duration;
Leap_Second: Boolean := False;
Time_Zone : Time_Zones.Time_Offset := 0)
return Time;
Returns a Time built from the date and time values, relative to the
specified time zone offset. Time_Error is raised if Leap_Second is True,
and Seconds is not appropriate for a Leap_Second.
procedure Split (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0;
Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Sub_Second : out Second_Duration;
Leap_Second: out Boolean);
Split Date into its constituent parts (Year, Month, Day, Hour, Minute,
Second, Sub_Second), relative to the specified time zone offset.
Leap_Second is true if Date identifies a leap second.
procedure Split (Date : in Time;
Time_Zone : in Time_Zones.Time_Offset := 0;
Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Seconds : out Day_Duration;
Leap_Second: out Boolean);
Split Date into its constituent parts (Year, Month, Day, Seconds), relative
to the specified time zone offset. Leap_Second is true if Date identifies
a leap second.
function Image (Date : Time;
Include_Time_Fraction : Boolean := False) return String;
Returns a string form of the Date.
The format is "Year-Month-Day Hour:Minute:Second", where each value
other than Year is a 2-digit form of the value of the functions
defined in Calendar and Calendar.Formatting, including a leading '0',
if needed. Year is a 4-digit value.
If Include_Time_Fraction is True, Sub_Seconds*100 is suffixed to the
string as a 2-digit value following a '.'.
AARM Note: The Image provides a string in ISO 8601 format, the
international standard time format. Alternative representations allowed
in ISO 8601 are not supported here.
ISO 8601 allows 24:00:00 for midnight; and a seconds value of 60 for
leap seconds. These are not allowed here (the routines mentioned above
cannot produce those results).
function Value (Date : String) return Time;
Returns a Time value for the image given as Date.
Constraint_Error is raised if the string is not formatted as described for
Image, or the function cannot interpret the given string as a Time value.
function Image (Elapsed_Time : Duration;
Include_Time_Fraction : Boolean := False) return String;
Returns a string form of the Elapsed_Time.
The format is "Hours:Minute:Second", where each value
is a 2-digit form of the value, including a leading '0', if needed.
If Include_Time_Fraction is True, Sub_Seconds*100 is suffixed to the
string as a 2-digit value following a '.'.
AARM Note: This cannot be implemented (directly) by calling
Ada.Calendar.Formatting.Split, since it may be out of the range of
Day_Duration, and thus the number of hours may be out of the range of
Hour_Number.
function Value (Elapsed_Time : String) return Duration;
Returns a Duration value for the image given as Elapsed_Time.
Constraint_Error is raised if the string is not formatted as described for
Image, or the function cannot interpret the given string as a Duration
value.
Implementation Advice
An implementation should support leap seconds if the target system supports
them. If leap seconds are not supported, functions returning leap seconds
should return zero, and Time_Of should raise Time_Error if Leap_Second is true.
!discussion
The proposal is taken from the operations provided in GNAT.Calendar and
Claw.Time.
The packages are defined as children of Ada.Calendar. While the operations
could be added to Ada.Calendar itself, doing so would potentially add
ambiguities to many existing programs. (There are many new overloadings of
Split and Time_Of.) In addition, while Ada.Calendar may be included in many
programs, these operations are less likely to be needed and their overhead
should not burden existing programs. The packages should be a child, and not
stand-alone packages, so that their implementation can take advantage of the
internal representation of type Time.
These operations are most valuable for wall clock time and date values. Thus,
we have chosen not to provide similar operations for Ada.Real_Time.
The rather vague descriptions of the Hour:Minute:Second:Sub_Second functions
is similar to the existing description of Ada.Calendar.
This package provides mechanisms to handle leap seconds. This is critical if
the target is connected to a time-base. Otherwise, programs could potentially
wait too long when a leap second is inserted.
We have chosen to keep the invariant that there are 86400.0 seconds in a day.
This is important, since existing Ada programs assume that, and changing it
could break many programs. Therefore, we have adopted the model that Split
ignores leap seconds - it will return 23:59:59 for a leap second. Similarly,
"-" ignores leap seconds. The difference between 23:00:00 and 01:00:00 the next
day is always 7200.0 seconds. Difference will return a count of leap seconds
in case that information is needed.
The time of day of a leap second depends on the time zone in use. (They always
happen at midnight UTC). Thus, they can occur at any local time of day.
The result of procedure Difference is a bit weird. We define the remainder
this way so that the Seconds value would be the same as the result "-" if
the two times are less than one day apart.
Image is defined to provide a human readable output compatible with ISO 8601.
The ISO 8601 form was used to allow us to provide a simple, useful form without
providing extensive formatting and localization facilities. In this sense,
Image is similar to 'Image of types: a quick and useful image string where
format is not critical.
The format has been chosen to maximize human readability without compromising
machine readability. The more compact ISO forms are not used here.
More complex formatting routines have been suggested. These were not included
in the proposal because they are complex or lack capability for
non-English-speaking locales.
Three kinds of formatting routines have been proposed. The simplest is the
'many default parameter form'. This would look similar to the Image function,
just with many more parameters. Claw.Time contains a function like this.
However, experience with the Claw function shows that not only is the large
number of parameters daunting (even to the designer!), but it still lacks
in flexibility (limited number of orderings, no non-English words). Renamings
of the function help in ease of use, but nothing can help the flexibility.
A second approach is the generic signature & generic package scheme. This
provides good localization, but at the cost of a generic signature package.
The good news is that this package would not change often, and a predefined
one could be provided for English and other common locales. But using it
still requires an instantiation with many parameters. This is similar to the
renaming, and it too lacks in flexibility in some cases.
A third approach is the format string approach. GNAT provides a package like
this; Windows provides a function like this in its API. This provides a lot of
flexibility in ordering, but little in terms of localization. (Windows does
localization elsewhere.) Moreover, it would require several pages of RM
description.
These possibilities are all quite complex, none covers everything desired, and
none would really allow a Value function. Thus, we do not provide a formatted
output function, preferring instead to provide the building blocks (such as the
Hour:Minute:Second splitter) which can be used to write whatever is needed.
Ada 95 defines the results of Time_Of and Split relative to an
"implementation-defined timezone" (9.6(24)). We extend this by providing a
Time_Offset type, and providing a function to return the offset of the
"implementation-defined time zone" to UTC time. We also provide defaulted
parameters to Split and Time_Of so that a specified time zone can be used
with those routines.
(Research has shown the the use of "timezone" in the Ada 95 Standard is
incorrect. For instance, see http://aa.usno.navy.mil/faq/docs/us_tzones.html,
a page of the US Naval Observatory, which is charged with timekeeping in the
US. Therefore, we've use "time zone" for this AI and also correct 9.6(24).)
Supporting more time zones other than local and UTC is much more difficult. An
implementation can add such support with child packages.
We expect the time zone functions will use the underlying operating system
facilities. If those are inaccurate (say determining the starting date of
daylight savings time in 1960 in Italy), we do not expect the Ada
implementation to try to correct them.
The upper bound of the subtype Ada.Calendar.Year_Number is increased in order
to eliminate Ada's Y2.1K problem. The lower bound is unchanged, because a
lot of Ada 95 code uses Year_Number'First to represent No_Time or Unknown_Time,
and dates in the distant past aren't usually interesting. The change in range
is purposely kept small, so that we are not requiring a lot of extra bits
in representations of Ada.Calendar.Time.
!corrigendum 9.06(11)
@drepl
@xcode< @b Year_Number @b Integer @b 1901 .. 2099;
@b Month_Number @b Integer @b 1 .. 12;
@b Day_Number @b Integer @b 1 .. 31;
@b Day_Duration @b Duration @b 0.0 .. 86_400.0;>
@dby
@xcode< @b Year_Number @b Integer @b 1901 .. 2399;
@b Month_Number @b Integer @b 1 .. 12;
@b Day_Number @b Integer @b 1 .. 31;
@b Day_Duration @b Duration @b 0.0 .. 86_400.0;>
!corrigendum 9.06(24)
@drepl
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
timezone; 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. The operators "+" and "-"
for addition and subtraction of times and durations, and the relational
operators for times, have the conventional meaning.
@dby
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. The operators "+" and "-"
for addition and subtraction of times and durations, and the relational
operators for times, have the conventional meaning.
!corrigendum 9.06.01(01)
@dinsc
@i<@s8>
The following language-defined library packages exist:
@xcode<@b Ada.Calendar.Time_Zones @b
-- @ft<@i