e5788a473e2ed6acc7cdfb3e986a7d8735774cb5
[gcc.git] / gcc / ada / a-calend.adb
1 ------------------------------------------------------------------------------
2 -- --
3 -- GNAT RUN-TIME COMPONENTS --
4 -- --
5 -- A D A . C A L E N D A R --
6 -- --
7 -- B o d y --
8 -- --
9 -- Copyright (C) 1992-2004 Free Software Foundation, Inc. --
10 -- --
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. --
21 -- --
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. --
28 -- --
29 -- GNAT was originally developed by the GNAT team at New York University. --
30 -- Extensive contributions were provided by Ada Core Technologies Inc. --
31 -- --
32 ------------------------------------------------------------------------------
33
34 with Unchecked_Conversion;
35
36 with System.OS_Primitives;
37 -- used for Clock
38
39 package body Ada.Calendar is
40
41 ------------------------------
42 -- Use of Pragma Unsuppress --
43 ------------------------------
44
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.
52
53 ------------------------
54 -- Local Declarations --
55 ------------------------
56
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.
62
63 type tm is record
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
75 end record;
76
77 type tm_Pointer is access all tm;
78
79 subtype time_t is long;
80
81 type time_t_Pointer is access all time_t;
82
83 procedure localtime_r (C : time_t_Pointer; res : tm_Pointer);
84 pragma Import (C, localtime_r, "__gnat_localtime_r");
85
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.
90
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.
103
104 Unix_Year_Min : constant := 1970;
105 Unix_Year_Max : constant := 2026;
106
107 Ada_Year_Min : constant := 1901;
108 Ada_Year_Max : constant := 2099;
109
110 -- Some basic constants used throughout
111
112 Days_In_Month : constant array (Month_Number) of Day_Number :=
113 (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
114
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);
119
120 ---------
121 -- "+" --
122 ---------
123
124 function "+" (Left : Time; Right : Duration) return Time is
125 pragma Unsuppress (Overflow_Check);
126 begin
127 return (Left + Time (Right));
128
129 exception
130 when Constraint_Error =>
131 raise Time_Error;
132 end "+";
133
134 function "+" (Left : Duration; Right : Time) return Time is
135 pragma Unsuppress (Overflow_Check);
136 begin
137 return (Time (Left) + Right);
138
139 exception
140 when Constraint_Error =>
141 raise Time_Error;
142 end "+";
143
144 ---------
145 -- "-" --
146 ---------
147
148 function "-" (Left : Time; Right : Duration) return Time is
149 pragma Unsuppress (Overflow_Check);
150 begin
151 return Left - Time (Right);
152
153 exception
154 when Constraint_Error =>
155 raise Time_Error;
156 end "-";
157
158 function "-" (Left : Time; Right : Time) return Duration is
159 pragma Unsuppress (Overflow_Check);
160 begin
161 return Duration (Left) - Duration (Right);
162
163 exception
164 when Constraint_Error =>
165 raise Time_Error;
166 end "-";
167
168 ---------
169 -- "<" --
170 ---------
171
172 function "<" (Left, Right : Time) return Boolean is
173 begin
174 return Duration (Left) < Duration (Right);
175 end "<";
176
177 ----------
178 -- "<=" --
179 ----------
180
181 function "<=" (Left, Right : Time) return Boolean is
182 begin
183 return Duration (Left) <= Duration (Right);
184 end "<=";
185
186 ---------
187 -- ">" --
188 ---------
189
190 function ">" (Left, Right : Time) return Boolean is
191 begin
192 return Duration (Left) > Duration (Right);
193 end ">";
194
195 ----------
196 -- ">=" --
197 ----------
198
199 function ">=" (Left, Right : Time) return Boolean is
200 begin
201 return Duration (Left) >= Duration (Right);
202 end ">=";
203
204 -----------
205 -- Clock --
206 -----------
207
208 function Clock return Time is
209 begin
210 return Time (System.OS_Primitives.Clock);
211 end Clock;
212
213 ---------
214 -- Day --
215 ---------
216
217 function Day (Date : Time) return Day_Number is
218 DY : Year_Number;
219 DM : Month_Number;
220 DD : Day_Number;
221 DS : Day_Duration;
222
223 begin
224 Split (Date, DY, DM, DD, DS);
225 return DD;
226 end Day;
227
228 -----------
229 -- Month --
230 -----------
231
232 function Month (Date : Time) return Month_Number is
233 DY : Year_Number;
234 DM : Month_Number;
235 DD : Day_Number;
236 DS : Day_Duration;
237
238 begin
239 Split (Date, DY, DM, DD, DS);
240 return DM;
241 end Month;
242
243 -------------
244 -- Seconds --
245 -------------
246
247 function Seconds (Date : Time) return Day_Duration is
248 DY : Year_Number;
249 DM : Month_Number;
250 DD : Day_Number;
251 DS : Day_Duration;
252
253 begin
254 Split (Date, DY, DM, DD, DS);
255 return DS;
256 end Seconds;
257
258 -----------
259 -- Split --
260 -----------
261
262 procedure Split
263 (Date : Time;
264 Year : out Year_Number;
265 Month : out Month_Number;
266 Day : out Day_Number;
267 Seconds : out Day_Duration)
268 is
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.
273
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;
276
277 LowD : constant Duration := Duration (Low);
278 HighD : constant Duration := Duration (High);
279
280 -- Finally the actual variables used in the computation
281
282 D : Duration;
283 Frac_Sec : Duration;
284 Year_Val : Integer;
285 Adjusted_Seconds : aliased time_t;
286 Tm_Val : aliased tm;
287
288 begin
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.
291
292 D := Duration (Date);
293
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.
299
300 if D < LowD or else D > HighD then
301 raise Time_Error;
302 end if;
303
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.
308
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.
314
315 Year_Val := 0;
316
317 while D < 0.0 loop
318 D := D + Seconds_In_56_YearsD;
319 Year_Val := Year_Val - 56;
320 end loop;
321
322 while D >= Seconds_In_56_YearsD loop
323 D := D - Seconds_In_56_YearsD;
324 Year_Val := Year_Val + 56;
325 end loop;
326
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).
330
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
335
336 declare
337 type D_Int is range 0 .. 2 ** (Duration'Size - 1) - 1;
338 for D_Int'Size use Duration'Size;
339
340 Small_Div : constant D_Int := D_Int (1.0 / Duration'Small);
341 D_As_Int : D_Int;
342
343 function To_D_As_Int is new Unchecked_Conversion (Duration, D_Int);
344 function To_Duration is new Unchecked_Conversion (D_Int, Duration);
345
346 begin
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);
350 end;
351
352 localtime_r (Adjusted_Seconds'Unchecked_Access, Tm_Val'Unchecked_Access);
353
354 Year_Val := Tm_Val.tm_year + 1900 + Year_Val;
355 Month := Tm_Val.tm_mon + 1;
356 Day := Tm_Val.tm_mday;
357
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.
362
363 Seconds := Duration (Tm_Val.tm_hour * 3600 +
364 Tm_Val.tm_min * 60 +
365 Tm_Val.tm_sec)
366 + Frac_Sec;
367
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???.
371
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.
378
379 if Year_Val not in Ada_Year_Min .. Ada_Year_Max then
380 raise Time_Error;
381 else
382 Year := Year_Val;
383 end if;
384 end Split;
385
386 -------------
387 -- Time_Of --
388 -------------
389
390 function Time_Of
391 (Year : Year_Number;
392 Month : Month_Number;
393 Day : Day_Number;
394 Seconds : Day_Duration := 0.0)
395 return Time
396 is
397 Result_Secs : aliased time_t;
398 TM_Val : aliased tm;
399 Int_Secs : constant Integer := Integer (Seconds);
400
401 Year_Val : Integer := Year;
402 Duration_Adjust : Duration := 0.0;
403
404 begin
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).
410
411 if not Year 'Valid
412 or else not Month 'Valid
413 or else not Day 'Valid
414 or else not Seconds'Valid
415 then
416 raise Constraint_Error;
417 end if;
418
419 -- Check for Day value too large (one might expect mktime to do this
420 -- check, as well as the basic checks we did with 'Valid, but it seems
421 -- that at least on some systems, this built-in check is too weak).
422
423 if Day > Days_In_Month (Month)
424 and then (Day /= 29 or Month /= 2 or Year mod 4 /= 0)
425 then
426 raise Time_Error;
427 end if;
428
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;
434
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
439 -- not modified.
440
441 while Year_Val < Unix_Year_Min loop
442 Year_Val := Year_Val + 56;
443 Duration_Adjust := Duration_Adjust - Seconds_In_56_YearsD;
444 end loop;
445
446 while Year_Val >= Unix_Year_Max loop
447 Year_Val := Year_Val - 56;
448 Duration_Adjust := Duration_Adjust + Seconds_In_56_YearsD;
449 end loop;
450
451 TM_Val.tm_year := Year_Val - 1900;
452
453 -- Since we do not have information on daylight savings,
454 -- rely on the default information.
455
456 TM_Val.tm_isdst := -1;
457 Result_Secs := mktime (TM_Val'Unchecked_Access);
458
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.
464
465 return
466 Time (Duration (Result_Secs) +
467 Duration_Adjust +
468 (Seconds - Duration (Int_Secs)));
469
470 end Time_Of;
471
472 ----------
473 -- Year --
474 ----------
475
476 function Year (Date : Time) return Year_Number is
477 DY : Year_Number;
478 DM : Month_Number;
479 DD : Day_Number;
480 DS : Day_Duration;
481
482 begin
483 Split (Date, DY, DM, DD, DS);
484 return DY;
485 end Year;
486
487 end Ada.Calendar;