1 ------------------------------------------------------------------------------
3 -- GNAT RUN-TIME COMPONENTS --
5 -- A D A . C A L E N D A R --
9 -- Copyright (C) 1992-2004 Free Software Foundation, Inc. --
11 -- GNAT is free software; you can redistribute it and/or modify it under --
12 -- terms of the GNU General Public License as published by the Free Soft- --
13 -- ware Foundation; either version 2, or (at your option) any later ver- --
14 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
15 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
16 -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
17 -- for more details. You should have received a copy of the GNU General --
18 -- Public License distributed with GNAT; see file COPYING. If not, write --
19 -- to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, --
20 -- MA 02111-1307, USA. --
22 -- As a special exception, if other files instantiate generics from this --
23 -- unit, or you link this unit with other files to produce an executable, --
24 -- this unit does not by itself cause the resulting executable to be --
25 -- covered by the GNU General Public License. This exception does not --
26 -- however invalidate any other reasons why the executable file might be --
27 -- covered by the GNU Public License. --
29 -- GNAT was originally developed by the GNAT team at New York University. --
30 -- Extensive contributions were provided by Ada Core Technologies Inc. --
32 ------------------------------------------------------------------------------
34 with Unchecked_Conversion
;
36 with System
.OS_Primitives
;
39 package body Ada
.Calendar
is
41 ------------------------------
42 -- Use of Pragma Unsuppress --
43 ------------------------------
45 -- This implementation of Calendar takes advantage of the permission in
46 -- Ada 95 of using arithmetic overflow checks to check for out of bounds
47 -- time values. This means that we must catch the constraint error that
48 -- results from arithmetic overflow, so we use pragma Unsuppress to make
49 -- sure that overflow is enabled, using software overflow checking if
50 -- necessary. That way, compiling Calendar with options to suppress this
51 -- checking will not affect its correctness.
53 ------------------------
54 -- Local Declarations --
55 ------------------------
57 type Char_Pointer
is access Character;
58 subtype int
is Integer;
59 subtype long
is Long_Integer;
60 -- Synonyms for C types. We don't want to get them from Interfaces.C
61 -- because there is no point in loading that unit just for calendar.
64 tm_sec
: int
; -- seconds after the minute (0 .. 60)
65 tm_min
: int
; -- minutes after the hour (0 .. 59)
66 tm_hour
: int
; -- hours since midnight (0 .. 24)
67 tm_mday
: int
; -- day of the month (1 .. 31)
68 tm_mon
: int
; -- months since January (0 .. 11)
69 tm_year
: int
; -- years since 1900
70 tm_wday
: int
; -- days since Sunday (0 .. 6)
71 tm_yday
: int
; -- days since January 1 (0 .. 365)
72 tm_isdst
: int
; -- Daylight Savings Time flag (-1 .. +1)
73 tm_gmtoff
: long
; -- offset from CUT in seconds
74 tm_zone
: Char_Pointer
; -- timezone abbreviation
77 type tm_Pointer
is access all tm
;
79 subtype time_t
is long
;
81 type time_t_Pointer
is access all time_t
;
83 procedure localtime_r
(C
: time_t_Pointer
; res
: tm_Pointer
);
84 pragma Import
(C
, localtime_r
, "__gnat_localtime_r");
86 function mktime
(TM
: tm_Pointer
) return time_t
;
87 pragma Import
(C
, mktime
);
88 -- mktime returns -1 in case the calendar time given by components of
89 -- TM.all cannot be represented.
91 -- The following constants are used in adjusting Ada dates so that they
92 -- fit into a 56 year range that can be handled by Unix (1970 included -
93 -- 2026 excluded). Dates that are not in this 56 year range are shifted
94 -- by multiples of 56 years to fit in this range
95 -- The trick is that the number of days in any four year period in the Ada
96 -- range of years (1901 - 2099) has a constant number of days. This is
97 -- because we have the special case of 2000 which, contrary to the normal
98 -- exception for centuries, is a leap year after all.
99 -- 56 has been chosen, because it is not only a multiple of 4, but also
100 -- a multiple of 7. Thus two dates 56 years apart fall on the same day of
101 -- the week, and the Daylight Saving Time change dates are usually the same
102 -- for these two years.
104 Unix_Year_Min
: constant := 1970;
105 Unix_Year_Max
: constant := 2026;
107 Ada_Year_Min
: constant := 1901;
108 Ada_Year_Max
: constant := 2099;
110 -- Some basic constants used throughout
112 Days_In_Month
: constant array (Month_Number
) of Day_Number
:=
113 (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
115 Days_In_4_Years
: constant := 365 * 3 + 366;
116 Seconds_In_4_Years
: constant := 86_400
* Days_In_4_Years
;
117 Seconds_In_56_Years
: constant := Seconds_In_4_Years
* 14;
118 Seconds_In_56_YearsD
: constant := Duration (Seconds_In_56_Years
);
124 function "+" (Left
: Time
; Right
: Duration) return Time
is
125 pragma Unsuppress
(Overflow_Check
);
127 return (Left
+ Time
(Right
));
130 when Constraint_Error
=>
134 function "+" (Left
: Duration; Right
: Time
) return Time
is
135 pragma Unsuppress
(Overflow_Check
);
137 return (Time
(Left
) + Right
);
140 when Constraint_Error
=>
148 function "-" (Left
: Time
; Right
: Duration) return Time
is
149 pragma Unsuppress
(Overflow_Check
);
151 return Left
- Time
(Right
);
154 when Constraint_Error
=>
158 function "-" (Left
: Time
; Right
: Time
) return Duration is
159 pragma Unsuppress
(Overflow_Check
);
161 return Duration (Left
) - Duration (Right
);
164 when Constraint_Error
=>
172 function "<" (Left
, Right
: Time
) return Boolean is
174 return Duration (Left
) < Duration (Right
);
181 function "<=" (Left
, Right
: Time
) return Boolean is
183 return Duration (Left
) <= Duration (Right
);
190 function ">" (Left
, Right
: Time
) return Boolean is
192 return Duration (Left
) > Duration (Right
);
199 function ">=" (Left
, Right
: Time
) return Boolean is
201 return Duration (Left
) >= Duration (Right
);
208 function Clock
return Time
is
210 return Time
(System
.OS_Primitives
.Clock
);
217 function Day
(Date
: Time
) return Day_Number
is
224 Split
(Date
, DY
, DM
, DD
, DS
);
232 function Month
(Date
: Time
) return Month_Number
is
239 Split
(Date
, DY
, DM
, DD
, DS
);
247 function Seconds
(Date
: Time
) return Day_Duration
is
254 Split
(Date
, DY
, DM
, DD
, DS
);
264 Year
: out Year_Number
;
265 Month
: out Month_Number
;
266 Day
: out Day_Number
;
267 Seconds
: out Day_Duration
)
269 -- The following declare bounds for duration that are comfortably
270 -- wider than the maximum allowed output result for the Ada range
271 -- of representable split values. These are used for a quick check
272 -- that the value is not wildly out of range.
274 Low
: constant := (Ada_Year_Min
- Unix_Year_Min
- 2) * 365 * 86_400
;
275 High
: constant := (Ada_Year_Max
- Unix_Year_Min
+ 2) * 365 * 86_400
;
277 LowD
: constant Duration := Duration (Low
);
278 HighD
: constant Duration := Duration (High
);
280 -- Finally the actual variables used in the computation
285 Adjusted_Seconds
: aliased time_t
;
289 -- For us a time is simply a signed duration value, so we work with
290 -- this duration value directly. Note that it can be negative.
292 D
:= Duration (Date
);
294 -- First of all, filter out completely ludicrous values. Remember
295 -- that we use the full stored range of duration values, which may
296 -- be significantly larger than the allowed range of Ada times. Note
297 -- that these checks are wider than required to make absolutely sure
298 -- that there are no end effects from time zone differences.
300 if D
< LowD
or else D
> HighD
then
304 -- The unix localtime_r function is more or less exactly what we need
305 -- here. The less comes from the fact that it does not support the
306 -- required range of years (the guaranteed range available is only
307 -- EPOCH through EPOCH + N seconds). N is in practice 2 ** 31 - 1.
309 -- If we have a value outside this range, then we first adjust it
310 -- to be in the required range by adding multiples of 56 years.
311 -- For the range we are interested in, the number of days in any
312 -- consecutive 56 year period is constant. Then we do the split
313 -- on the adjusted value, and readjust the years value accordingly.
318 D
:= D
+ Seconds_In_56_YearsD
;
319 Year_Val
:= Year_Val
- 56;
322 while D
>= Seconds_In_56_YearsD
loop
323 D
:= D
- Seconds_In_56_YearsD
;
324 Year_Val
:= Year_Val
+ 56;
327 -- Now we need to take the value D, which is now non-negative, and
328 -- break it down into seconds (to pass to the localtime_r function)
329 -- and fractions of seconds (for the adjustment below).
331 -- Surprisingly there is no easy way to do this in Ada, and certainly
332 -- no easy way to do it and generate efficient code. Therefore we
333 -- do it at a low level, knowing that it is really represented as
334 -- an integer with units of Small
337 type D_Int
is range 0 .. 2 ** (Duration'Size - 1) - 1;
338 for D_Int
'Size use Duration'Size;
340 Small_Div
: constant D_Int
:= D_Int
(1.0 / Duration'Small);
343 function To_D_As_Int
is new Unchecked_Conversion
(Duration, D_Int
);
344 function To_Duration
is new Unchecked_Conversion
(D_Int
, Duration);
347 D_As_Int
:= To_D_As_Int
(D
);
348 Adjusted_Seconds
:= time_t
(D_As_Int
/ Small_Div
);
349 Frac_Sec
:= To_Duration
(D_As_Int
rem Small_Div
);
352 localtime_r
(Adjusted_Seconds
'Unchecked_Access, Tm_Val
'Unchecked_Access);
354 Year_Val
:= Tm_Val
.tm_year
+ 1900 + Year_Val
;
355 Month
:= Tm_Val
.tm_mon
+ 1;
356 Day
:= Tm_Val
.tm_mday
;
358 -- The Seconds value is a little complex. The localtime function
359 -- returns the integral number of seconds, which is what we want,
360 -- but we want to retain the fractional part from the original
361 -- Time value, since this is typically stored more accurately.
363 Seconds
:= Duration (Tm_Val
.tm_hour
* 3600 +
368 -- Note: the above expression is pretty horrible, one of these days
369 -- we should stop using time_of and do everything ourselves to avoid
370 -- these unnecessary divides and multiplies???.
372 -- The Year may still be out of range, since our entry test was
373 -- deliberately crude. Trying to make this entry test accurate is
374 -- tricky due to time zone adjustment issues affecting the exact
375 -- boundary. It is interesting to note that whether or not a given
376 -- Calendar.Time value gets Time_Error when split depends on the
377 -- current time zone setting.
379 if Year_Val
not in Ada_Year_Min
.. Ada_Year_Max
then
392 Month
: Month_Number
;
394 Seconds
: Day_Duration
:= 0.0)
397 Result_Secs
: aliased time_t
;
399 Int_Secs
: constant Integer := Integer (Seconds
);
401 Year_Val
: Integer := Year
;
402 Duration_Adjust
: Duration := 0.0;
405 -- The following checks are redundant with respect to the constraint
406 -- error checks that should normally be made on parameters, but we
407 -- decide to raise Constraint_Error in any case if bad values come
408 -- in (as a result of checks being off in the caller, or for other
409 -- erroneous or bounded error cases).
412 or else not Month 'Valid
413 or else not Day
'Valid
414 or else not Seconds'Valid
416 raise Constraint_Error;
419 -- Check for Day value too large (one might expect mktime to do this
420 -- check, as well as the basi checks we did with 'Valid
, but it seems
421 -- that at least on some systems, this built-in check is too weak).
423 if Day
> Days_In_Month
(Month
)
424 and then (Day
/= 29 or Month
/= 2 or Year
mod 4 /= 0)
429 TM_Val
.tm_sec
:= Int_Secs
mod 60;
430 TM_Val
.tm_min
:= (Int_Secs
/ 60) mod 60;
431 TM_Val
.tm_hour
:= (Int_Secs
/ 60) / 60;
432 TM_Val
.tm_mday
:= Day
;
433 TM_Val
.tm_mon
:= Month
- 1;
435 -- For the year, we have to adjust it to a year that Unix can handle.
436 -- We do this in 56 year steps, since the number of days in 56 years
437 -- is constant, so the timezone effect on the conversion from local
438 -- time to GMT is unaffected; also the DST change dates are usually
441 while Year_Val
< Unix_Year_Min
loop
442 Year_Val
:= Year_Val
+ 56;
443 Duration_Adjust
:= Duration_Adjust
- Seconds_In_56_YearsD
;
446 while Year_Val
>= Unix_Year_Max
loop
447 Year_Val
:= Year_Val
- 56;
448 Duration_Adjust
:= Duration_Adjust
+ Seconds_In_56_YearsD
;
451 TM_Val
.tm_year
:= Year_Val
- 1900;
453 -- Since we do not have information on daylight savings,
454 -- rely on the default information.
456 TM_Val
.tm_isdst
:= -1;
457 Result_Secs
:= mktime
(TM_Val
'Unchecked_Access);
459 -- That gives us the basic value in seconds. Two adjustments are
460 -- needed. First we must undo the year adjustment carried out above.
461 -- Second we put back the fraction seconds value since in general the
462 -- Day_Duration value we received has additional precision which we
463 -- do not want to lose in the constructed result.
466 Time
(Duration (Result_Secs
) +
468 (Seconds
- Duration (Int_Secs
)));
476 function Year
(Date
: Time
) return Year_Number
is
483 Split
(Date
, DY
, DM
, DD
, DS
);