9.6.1 Formatting, Time Zones, and other operations for Time
Static Semantics
1/2
2/2
package Ada.Calendar.Time_Zones is
3/2
-- Time zone manipulation:
4/2
type Time_Offset is range -28*60 .. 28*60;
4.a/2
Reason: We want
to be able to specify the difference between any two arbitrary time zones.
You might think that 1440 (24 hours) would be enough, but there are places
(like Tonga, which is UTC+13hr) which are more than 12 hours than UTC.
Combined with summer time (known as daylight saving time in some parts
of the world) – which switches opposite in the northern and souther
hemispheres – and even greater differences are possible. We know
of cases of a 26 hours difference, so we err on the safe side by selecting
28 hours as the limit.
5/2
Unknown_Zone_Error : exception;
6/2
function UTC_Time_Offset (Date : Time := Clock) return Time_Offset;
7/2
end Ada.Calendar.Time_Zones;
8/2
package Ada.Calendar.Arithmetic is
9/2
-- Arithmetic on days:
10/2
type Day_Count is range
-366*(1+Year_Number'Last - Year_Number'First)
..
366*(1+Year_Number'Last - Year_Number'First);
11/2
subtype Leap_Seconds_Count is Integer range -2047 .. 2047;
11.a/2
Reason: The maximum
number of leap seconds is likely to be much less than this, but we don't
want to reach the limit too soon if the earth's behavior suddenly changes.
We believe that the maximum number is 1612, based on the current rules,
but that number is too weird to use here.
12/2
procedure Difference (Left, Right : in Time;
Days : out Day_Count;
Seconds : out Duration;
Leap_Seconds : out Leap_Seconds_Count);
13/2
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;
14/2
end Ada.Calendar.Arithmetic;
15/2
with Ada.Calendar.Time_Zones;
package Ada.Calendar.Formatting is
16/2
-- Day of the week:
17/2
type Day_Name is (Monday, Tuesday, Wednesday, Thursday,
Friday, Saturday, Sunday);
18/2
function Day_of_Week (Date : Time) return Day_Name;
19/2
-- Hours:Minutes:Seconds access:
20/2
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;
21/2
function Year (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Year_Number;
22/2
function Month (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Month_Number;
23/2
function Day (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Day_Number;
24/2
function Hour (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Hour_Number;
25/2
function Minute (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Minute_Number;
26/2
function Second (Date : Time)
return Second_Number;
27/2
function Sub_Second (Date : Time)
return Second_Duration;
28/2
function Seconds_Of (Hour : Hour_Number;
Minute : Minute_Number;
Second : Second_Number := 0;
Sub_Second : Second_Duration := 0.0)
return Day_Duration;
29/2
procedure Split (Seconds : in Day_Duration;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Sub_Second : out Second_Duration);
30/2
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;
31/2
function Time_Of (Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
Seconds : Day_Duration := 0.0;
Leap_Second: Boolean := False;
Time_Zone : Time_Zones.Time_Offset := 0)
return Time;
32/2
procedure Split (Date : in Time;
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;
Time_Zone : in Time_Zones.Time_Offset := 0);
33/2
procedure Split (Date : in Time;
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;
Time_Zone : in Time_Zones.Time_Offset := 0);
34/2
procedure Split (Date : in Time;
Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Seconds : out Day_Duration;
Leap_Second: out Boolean;
Time_Zone : in Time_Zones.Time_Offset := 0);
35/2
-- Simple image and value:
function Image (Date : Time;
Include_Time_Fraction : Boolean := False;
Time_Zone : Time_Zones.Time_Offset := 0) return String;
36/2
function Value (Date : String;
Time_Zone : Time_Zones.Time_Offset := 0) return Time;
37/2
function Image (Elapsed_Time : Duration;
Include_Time_Fraction : Boolean := False) return String;
38/2
function Value (Elapsed_Time : String) return Duration;
39/2
end Ada.Calendar.Formatting;
40/2
{
AI95-00351-01}
Type Time_Offset represents the number of minutes
difference between the implementation-defined time zone used by Calendar
and another time zone.
41/2
function UTC_Time_Offset (Date : Time := Clock) return Time_Offset;
42/3
{
AI95-00351-01}
{
AI05-0119-1}
Returns, as a number of minutes, the result
of subtracting 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.
42.a.1/3
Ramification: {
AI05-0119-1}
In North America, the result will be negative;
in Europe, the result will be zero or positive.
42.a/2
Discussion: The
Date parameter is needed to take into account time differences caused
by daylight-savings time and other time changes. This parameter is measured
in the time zone of Calendar, if any, not necessarily the UTC time zone.
42.b/2
Other time zones can be
supported with a child package. We don't define one because of the lack
of agreement on the definition of a time zone.
42.c/2
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.
43/2
procedure Difference (Left, Right : in Time;
Days : out Day_Count;
Seconds : out Duration;
Leap_Seconds : out Leap_Seconds_Count);
44/2
{
AI95-00351-01}
{
AI95-00427-01}
Returns the difference between Left and Right.
Days is the number of days of difference, Seconds is the remainder seconds
of difference excluding leap seconds, 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 nonnegative. The
absolute value of Seconds is always less than 86_400.0. For the returned
values, if Days = 0, then Seconds + Duration(Leap_Seconds) = Calendar."–"
(Left, Right).
44.a/2
Discussion: Leap_Seconds,
if any, are not included in Seconds. However, Leap_Seconds should be
included in calculations using the operators defined in Calendar, as
is specified for "–" above.
45/2
function "+" (Left : Time; Right : Day_Count) return Time;
function "+" (Left : Day_Count; Right : Time) return Time;
46/2
{
AI95-00351-01}
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.
47/2
function "-" (Left : Time; Right : Day_Count) return Time;
48/2
{
AI95-00351-01}
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.
49/2
function "-" (Left, Right : Time) return Day_Count;
50/2
{
AI95-00351-01}
Subtracts two time values, and returns the number
of days between them. This is the same value that Difference would return
in Days.
51/2
function Day_of_Week (Date : Time) return Day_Name;
52/2
{
AI95-00351-01}
Returns the day of the week for Time. This is based
on the Year, Month, and Day values of Time.
53/2
function Year (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Year_Number;
54/2
{
AI95-00427-01}
Returns the year for Date, as appropriate for the
specified time zone offset.
55/2
function Month (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Month_Number;
56/2
{
AI95-00427-01}
Returns the month for Date, as appropriate for
the specified time zone offset.
57/2
function Day (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Day_Number;
58/2
{
AI95-00427-01}
Returns the day number for Date, as appropriate
for the specified time zone offset.
59/2
function Hour (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Hour_Number;
60/2
{
AI95-00351-01}
Returns the hour for Date, as appropriate for the
specified time zone offset.
61/2
function Minute (Date : Time;
Time_Zone : Time_Zones.Time_Offset := 0)
return Minute_Number;
62/2
{
AI95-00351-01}
Returns the minute within the hour for Date, as
appropriate for the specified time zone offset.
63/2
function Second (Date : Time)
return Second_Number;
64/2
65/2
function Sub_Second (Date : Time)
return Second_Duration;
66/2
{
AI95-00351-01}
{
AI95-00427-01}
Returns the fraction of second for Date (this has
the same accuracy as Day_Duration). The value returned is always less
than 1.0.
67/2
function Seconds_Of (Hour : Hour_Number;
Minute : Minute_Number;
Second : Second_Number := 0;
Sub_Second : Second_Duration := 0.0)
return Day_Duration;
68/2
{
AI95-00351-01}
{
AI95-00427-01}
Returns a Day_Duration value for the combination
of the given Hour, Minute, Second, and Sub_Second. This value can be
used in Calendar.Time_Of as well as the argument to Calendar."+"
and Calendar."–". If Seconds_Of is called with a Sub_Second
value of 1.0, the value returned is equal to the value of Seconds_Of
for the next second with a Sub_Second value of 0.0.
69/2
procedure Split (Seconds : in Day_Duration;
Hour : out Hour_Number;
Minute : out Minute_Number;
Second : out Second_Number;
Sub_Second : out Second_Duration);
70/3
{
AI95-00351-01}
{
AI95-00427-01}
{
AI05-0238-1}
Splits Seconds into Hour, Minute, Second and Sub_Second
in such a way that the resulting values all belong to their respective
subtypes. The value returned in the Sub_Second parameter is always less
than 1.0. If Seconds = 86400.0, Split propagates
Time_Error.
70.a/2
Ramification: There
is only one way to do the split which meets all of the requirements.
70.b/3
Reason: {
AI05-0238-1}
If Seconds = 86400.0, one of the returned values
would have to be out of its defined range (either Sub_Second = 1.0 or
Hour = 24 with the other value being 0). This doesn't seem worth breaking
the invariants.
71/2
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;
72/2
{
AI95-00351-01}
{
AI95-00427-01}
If Leap_Second is False, returns a Time built from
the date and time values, relative to the specified time zone offset.
If Leap_Second is True, returns the Time that represents the time within
the leap second that is one second later than the time specified by the
other parameters. Time_Error is raised if the parameters do not form
a proper date or time. If Time_Of is called with a Sub_Second value of
1.0, the value returned is equal to the value of Time_Of for the next
second with a Sub_Second value of 0.0.
72.a/2
Discussion: Time_Error
should be raised if Leap_Second is True, and the date and time values
do not represent the second before a leap second. A leap second always
occurs at midnight UTC, and is 23:59:60 UTC in ISO notation. So, if the
time zone is UTC and Leap_Second is True, 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.
73/2
function Time_Of (Year : Year_Number;
Month : Month_Number;
Day : Day_Number;
Seconds : Day_Duration := 0.0;
Leap_Second: Boolean := False;
Time_Zone : Time_Zones.Time_Offset := 0)
return Time;
74/2
{
AI95-00351-01}
{
AI95-00427-01}
If Leap_Second is False, returns a Time built from
the date and time values, relative to the specified time zone offset.
If Leap_Second is True, returns the Time that represents the time within
the leap second that is one second later than the time specified by the
other parameters. Time_Error is raised if the parameters do not form
a proper date or time. If Time_Of is called with a Seconds value of 86_400.0,
the value returned is equal to the value of Time_Of for the next day
with a Seconds value of 0.0.
75/2
procedure Split (Date : in Time;
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;
Time_Zone : in Time_Zones.Time_Offset := 0);
76/2
{
AI95-00351-01}
{
AI95-00427-01}
If Date does not represent a time within a leap
second, splits Date into its constituent parts (Year, Month, Day, Hour,
Minute, Second, Sub_Second), relative to the specified time zone offset,
and sets Leap_Second to False. If Date represents a time within a leap
second, set the constituent parts to values corresponding to a time one
second earlier than that given by Date, relative to the specified time
zone offset, and sets Leap_Seconds to True. The value returned in the
Sub_Second parameter is always less than 1.0.
77/2
procedure Split (Date : in Time;
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;
Time_Zone : in Time_Zones.Time_Offset := 0);
78/2
{
AI95-00351-01}
{
AI95-00427-01}
Splits Date into its constituent parts (Year, Month,
Day, Hour, Minute, Second, Sub_Second), relative to the specified time
zone offset. The value returned in the Sub_Second parameter is always
less than 1.0.
79/2
procedure Split (Date : in Time;
Year : out Year_Number;
Month : out Month_Number;
Day : out Day_Number;
Seconds : out Day_Duration;
Leap_Second: out Boolean;
Time_Zone : in Time_Zones.Time_Offset := 0);
80/2
{
AI95-00351-01}
{
AI95-00427-01}
If Date does not represent a time within a leap
second, splits Date into its constituent parts (Year, Month, Day, Seconds),
relative to the specified time zone offset, and sets Leap_Second to False.
If Date represents a time within a leap second, set the constituent parts
to values corresponding to a time one second earlier than that given
by Date, relative to the specified time zone offset, and sets Leap_Seconds
to True. The value returned in the Seconds parameter is always less than
86_400.0.
81/2
function Image (Date : Time;
Include_Time_Fraction : Boolean := False;
Time_Zone : Time_Zones.Time_Offset := 0) return String;
82/2
{
AI95-00351-01}
Returns a string form of the Date relative to the
given Time_Zone. The format is "Year-Month-Day Hour:Minute:Second",
where the Year is a 4-digit value, and all others are 2-digit values,
of the functions defined in Calendar and Calendar.Formatting, including
a leading zero, if needed. The separators between the values are a minus,
another minus, a colon, and a single space between the Day and Hour.
If Include_Time_Fraction is True, the integer part of Sub_Seconds*100
is suffixed to the string as a point followed by a 2-digit value.
82.a/2
Discussion: The
Image provides a string in ISO 8601 format, the international standard
time format. Alternative representations allowed in ISO 8601 are not
supported here.
82.b/2
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).
82.c/2
Ramification: The
fractional part is truncated, not rounded. It would be quite hard to
define the result with proper rounding, as it can change all of the values
of the image. Values can be rounded up by adding an appropriate constant
(0.5 if Include_Time_Fraction is False, 0.005 otherwise) to the time
before taking the image.
83/2
function Value (Date : String;
Time_Zone : Time_Zones.Time_Offset := 0) return Time;
84/2
{
AI95-00351-01}
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.
84.a/3
Discussion: {
AI05-0005-1}
The intent is that the implementation enforce the
same range rules on the string as the appropriate function Time_Of, except
for the hour, so “cannot interpret the given string as a Time value”
happens when one of the values is out of the required range. For example,
"2005-08-31 24:00:00 24:0:0 "
should raise Constraint_Error (the hour is out of range).
85/2
function Image (Elapsed_Time : Duration;
Include_Time_Fraction : Boolean := False) return String;
86/2
{
AI95-00351-01}
Returns a string form of the Elapsed_Time. The
format is "Hour:Minute:Second", where all values are 2-digit
values, including a leading zero, if needed. The separators between the
values are colons. If Include_Time_Fraction is True, the integer part
of Sub_Seconds*100 is suffixed to the string as a point followed by a
2-digit value. If Elapsed_Time < 0.0, the result is Image (abs
Elapsed_Time, Include_Time_Fraction) prefixed with a minus sign. If abs
Elapsed_Time represents 100 hours or more, the result is implementation-defined.
86.a/2
Implementation defined:
The result of Calendar.Formating.Image
if its argument represents more than 100 hours.
86.b/2
Implementation Note:
This cannot be implemented (directly) by calling 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.
86.c
If a Duration value can
represent more then 100 hours, the implementation will need to define
a format for the return of Image.
87/2
function Value (Elapsed_Time : String) return Duration;
88/2
{
AI95-00351-01}
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.
88.a/2
Discussion: The
intent is that the implementation enforce the same range rules on the
string as the appropriate function Time_Of, except for the hour, so “cannot
interpret the given string as a Time value” happens when one of
the values is out of the required range. For example, "10:23:60"
should raise Constraint_Error (the seconds value is out of range).
Implementation Advice
89/2
{
AI95-00351-01}
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.
89.a/2
Implementation Advice:
Leap seconds should be supported if
the target system supports them. Otherwise, operations in Calendar.Formatting
should return results consistent with no leap seconds.
89.b/2
Discussion: An
implementation can always support leap seconds when the target system
does not; indeed, this isn't particularly hard (all that is required
is a table of when leap seconds were inserted). As such, leap second
support isn't “impossible or impractical” in the sense of
1.1.3. However, for some purposes, it may
be important to follow the target system's lack of leap second support
(if the target is a GPS satellite, which does not use leap seconds, leap
second support would be a handicap to work around). Thus, this Implementation
Advice should be read as giving permission to not support leap seconds
on target systems that don't support leap seconds. Implementers should
use the needs of their customers to determine whether or not support
leap seconds on such targets.
90/2
38 {
AI95-00351-01}
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).
90.a/2
Discussion: {
AI95-00351-01}
The time in the time zone known as Greenwich Mean
Time (GMT) is generally very close to UTC time; for most purposes they
can be treated the same. GMT is the time based on the rotation of the
Earth; UTC is the time based on atomic clocks, with leap seconds periodically
inserted to realign with GMT (because most human activities depend on
the rotation of the Earth). At any point in time, there will be a sub-second
difference between GMT and UTC.
91/2
39 {
AI95-00351-01}
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 United States, for example, UTC_Time_Offset will generally
be negative.
91.a/2
Discussion: 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.
Extensions to Ada 95
91.b/2
Inconsistencies With Ada 2005
91.c/3
{
AI05-0238-1}
Correction: Defined
that Split for Seconds raises Time_Error for a value of exactly 86400.0,
rather than breaking some invariant or raising some other exception.
Ada 2005 left this unspecified; a program that depended on what some
implementation does might break, but such a program is not portable anyway.
Wording Changes from Ada 2005
91.d/3
{
AI05-0119-1}
Correction: Clarified the sign of UTC_Time_Offset.
Ada 2005 and 2012 Editions sponsored in part by Ada-Europe