!standard 9.06(11) 04-12-05 AI95-00351/07 !standard 9.06(24) !standard 9.06.01(01) !standard 1.2(8/1) !class amendment 03-09-22 !status Amendment 200Y 04-03-26 !status WG9 Approved 04-06-18 !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 The type Time in package Calendar 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 Hours: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. There have been cases of profilers, loggers, and other components failing 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 Three child packages of Ada.Calendar are proposed, namely Ada.Calendar.Time_Zones, Ada.Calendar.Arithmetic, and Ada.Calendar.Formatting. These provide types and operations to give greater control over the manipulation of time as outlined in the problem. !wording Add after 1.2(8): ISO 8601:2004 - Data elements and interchange formats - Information interchange - Representation of dates and times. Change Year_Number in 9.6(11): subtype Year_Number is Integer range 1901 .. 2399; Correct the spelling of "timezone" to "time zone" in 9.6(24). 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 : 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.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 : Time; Time_Zone : Time_Zones.Time_Offset := 0) return Hour_Number; function Minute (Date : Time; Time_Zone : Time_Zones.Time_Offset := 0) return Minute_Number; function Second (Date : Time; Time_Zone : Time_Zones.Time_Offset := 0) return Second_Number; function Sub_Second (Date : Time; Time_Zone : Time_Zones.Time_Offset := 0) return Second_Duration; function Seconds_Of (Hour : Hour_Number; Minute : Minute_Number; Second : Second_Number := 0; Sub_Second : 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; Time_Zone : Time_Zones.Time_Offset := 0) return String; function Value (Date : String; Time_Zone : Time_Zones.Time_Offset := 0) 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 : 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 of package Calendar may, 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 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; Adds 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; Subtracts 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; Subtracts two time values, and returns 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 : Time; Time_Zone : Time_Zones.Time_Offset := 0) return Hour_Number; Returns the hour for Date, as appropriate for the specified time zone offset. function Minute (Date : Time; Time_Zone : 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 : Time; Time_Zone : 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 : Time; Time_Zone : 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 for the specified time zone offset. function Seconds_Of (Hour : Hour_Number; Minute : Minute_Number; Second : Second_Number := 0; Sub_Second : 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); Splits 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); Splits 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; Time_Zone : Time_Zones.Time_Offset := 0) return String; Returns a string form of the Date relative to the given Time_Zone. 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; Time_Zone : Time_Zones.Time_Offset := 0) return Time; Returns a Time value for the image given as Date, relative to the given time zone. 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 "Hour: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 '.'. If Elapsed_Time < 0.0, the result is Image (abs Elapsed_Time, Include_Time_Fraction) prefixed with "-". If Abs Elapsed_Time represents 100 hours or more, the result is implementation-defined. AARM Notes: 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. If a Duration value can represent more then 100 hours, the implementation will need to define a format for the return of Image. 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, Difference should return zero for Leap_Seconds, Split should return False for Leap_Second, 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 are child packages, 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. These packages provide 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 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 and 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 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 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 used "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 1.2(8/1) @dinsa ISO/IEC 10646-1:1993, @i, supplemented by Technical Corrigendum 1:1996. @dinst ISO 8601:2004 - @i. !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