OSDN Git Service

2010-10-08 Robert Dewar <dewar@adacore.com>
[pf3gnuchains/gcc-fork.git] / gcc / ada / a-calend-vms.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-2009, 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 3,  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.                                     --
17 --                                                                          --
18 -- As a special exception under Section 7 of GPL version 3, you are granted --
19 -- additional permissions described in the GCC Runtime Library Exception,   --
20 -- version 3.1, as published by the Free Software Foundation.               --
21 --                                                                          --
22 -- You should have received a copy of the GNU General Public License and    --
23 -- a copy of the GCC Runtime Library Exception along with this program;     --
24 -- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
25 -- <http://www.gnu.org/licenses/>.                                          --
26 --                                                                          --
27 -- GNAT was originally developed  by the GNAT team at  New York University. --
28 -- Extensive contributions were provided by Ada Core Technologies Inc.      --
29 --                                                                          --
30 ------------------------------------------------------------------------------
31
32 --  This is the Alpha/VMS version
33
34 with Ada.Unchecked_Conversion;
35
36 with System.Aux_DEC;       use System.Aux_DEC;
37 with System.OS_Primitives; use System.OS_Primitives;
38
39 package body Ada.Calendar is
40
41    --------------------------
42    -- Implementation Notes --
43    --------------------------
44
45    --  Variables of type Ada.Calendar.Time have suffix _S or _M to denote
46    --  units of seconds or milis.
47
48    --  Because time is measured in different units and from different origins
49    --  on various targets, a system independent model is incorporated into
50    --  Ada.Calendar. The idea behind the design is to encapsulate all target
51    --  dependent machinery in a single package, thus providing a uniform
52    --  interface to all existing and any potential children.
53
54    --     package Ada.Calendar
55    --        procedure Split (5 parameters) -------+
56    --                                              | Call from local routine
57    --     private                                  |
58    --        package Formatting_Operations         |
59    --           procedure Split (11 parameters) <--+
60    --        end Formatting_Operations             |
61    --     end Ada.Calendar                         |
62    --                                              |
63    --     package Ada.Calendar.Formatting          | Call from child routine
64    --        procedure Split (9 or 10 parameters) -+
65    --     end Ada.Calendar.Formatting
66
67    --  The behaviour of the interfacing routines is controlled via various
68    --  flags. All new Ada 2005 types from children of Ada.Calendar are
69    --  emulated by a similar type. For instance, type Day_Number is replaced
70    --  by Integer in various routines. One ramification of this model is that
71    --  the caller site must perform validity checks on returned results.
72    --  The end result of this model is the lack of target specific files per
73    --  child of Ada.Calendar (a-calfor, a-calfor-vms, a-calfor-vxwors, etc).
74
75    -----------------------
76    -- Local Subprograms --
77    -----------------------
78
79    procedure Check_Within_Time_Bounds (T : OS_Time);
80    --  Ensure that a time representation value falls withing the bounds of Ada
81    --  time. Leap seconds support is taken into account.
82
83    procedure Cumulative_Leap_Seconds
84      (Start_Date    : OS_Time;
85       End_Date      : OS_Time;
86       Elapsed_Leaps : out Natural;
87       Next_Leap_Sec : out OS_Time);
88    --  Elapsed_Leaps is the sum of the leap seconds that have occurred on or
89    --  after Start_Date and before (strictly before) End_Date. Next_Leap_Sec
90    --  represents the next leap second occurrence on or after End_Date. If
91    --  there are no leaps seconds after End_Date, End_Of_Time is returned.
92    --  End_Of_Time can be used as End_Date to count all the leap seconds that
93    --  have occurred on or after Start_Date.
94    --
95    --  Note: Any sub seconds of Start_Date and End_Date are discarded before
96    --  the calculations are done. For instance: if 113 seconds is a leap
97    --  second (it isn't) and 113.5 is input as an End_Date, the leap second
98    --  at 113 will not be counted in Leaps_Between, but it will be returned
99    --  as Next_Leap_Sec. Thus, if the caller wants to know if the End_Date is
100    --  a leap second, the comparison should be:
101    --
102    --     End_Date >= Next_Leap_Sec;
103    --
104    --  After_Last_Leap is designed so that this comparison works without
105    --  having to first check if Next_Leap_Sec is a valid leap second.
106
107    function To_Duration (T : Time) return Duration;
108    function To_Relative_Time (D : Duration) return Time;
109    --  It is important to note that duration's fractional part denotes nano
110    --  seconds while the units of Time are 100 nanoseconds. If a regular
111    --  Unchecked_Conversion was employed, the resulting values would be off
112    --  by 100.
113
114    --------------------------
115    -- Leap seconds control --
116    --------------------------
117
118    Flag : Integer;
119    pragma Import (C, Flag, "__gl_leap_seconds_support");
120    --  This imported value is used to determine whether the compilation had
121    --  binder flag "-y" present which enables leap seconds. A value of zero
122    --  signifies no leap seconds support while a value of one enables the
123    --  support.
124
125    Leap_Support : constant Boolean := Flag = 1;
126    --  The above flag controls the usage of leap seconds in all Ada.Calendar
127    --  routines.
128
129    Leap_Seconds_Count : constant Natural := 24;
130
131    ---------------------
132    -- Local Constants --
133    ---------------------
134
135    --  The range of Ada time expressed as milis since the VMS Epoch
136
137    Ada_Low  : constant OS_Time :=  (10 * 366 +  32 * 365 + 45) * Milis_In_Day;
138    Ada_High : constant OS_Time := (131 * 366 + 410 * 365 + 45) * Milis_In_Day;
139
140    --  Even though the upper bound of time is 2399-12-31 23:59:59.9999999
141    --  UTC, it must be increased to include all leap seconds.
142
143    Ada_High_And_Leaps : constant OS_Time :=
144                           Ada_High + OS_Time (Leap_Seconds_Count) * Mili;
145
146    --  Two constants used in the calculations of elapsed leap seconds.
147    --  End_Of_Time is later than Ada_High in time zone -28. Start_Of_Time
148    --  is earlier than Ada_Low in time zone +28.
149
150    End_Of_Time   : constant OS_Time := Ada_High + OS_Time (3) * Milis_In_Day;
151    Start_Of_Time : constant OS_Time := Ada_Low  - OS_Time (3) * Milis_In_Day;
152
153    --  The following table contains the hard time values of all existing leap
154    --  seconds. The values are produced by the utility program xleaps.adb.
155
156    Leap_Second_Times : constant array (1 .. Leap_Seconds_Count) of OS_Time :=
157      (35855136000000000,
158       36014112010000000,
159       36329472020000000,
160       36644832030000000,
161       36960192040000000,
162       37276416050000000,
163       37591776060000000,
164       37907136070000000,
165       38222496080000000,
166       38695104090000000,
167       39010464100000000,
168       39325824110000000,
169       39957408120000000,
170       40747104130000000,
171       41378688140000000,
172       41694048150000000,
173       42166656160000000,
174       42482016170000000,
175       42797376180000000,
176       43271712190000000,
177       43744320200000000,
178       44218656210000000,
179       46427904220000000,
180       47374848230000000);
181
182    ---------
183    -- "+" --
184    ---------
185
186    function "+" (Left : Time; Right : Duration) return Time is
187       pragma Unsuppress (Overflow_Check);
188    begin
189       return Left + To_Relative_Time (Right);
190    exception
191       when Constraint_Error =>
192          raise Time_Error;
193    end "+";
194
195    function "+" (Left : Duration; Right : Time) return Time is
196       pragma Unsuppress (Overflow_Check);
197    begin
198       return Right + Left;
199    exception
200       when Constraint_Error =>
201          raise Time_Error;
202    end "+";
203
204    ---------
205    -- "-" --
206    ---------
207
208    function "-" (Left : Time; Right : Duration) return Time is
209       pragma Unsuppress (Overflow_Check);
210    begin
211       return Left - To_Relative_Time (Right);
212    exception
213       when Constraint_Error =>
214          raise Time_Error;
215    end "-";
216
217    function "-" (Left : Time; Right : Time) return Duration is
218       pragma Unsuppress (Overflow_Check);
219
220       --  The bound of type Duration expressed as time
221
222       Dur_High : constant OS_Time :=
223                    OS_Time (To_Relative_Time (Duration'Last));
224       Dur_Low  : constant OS_Time :=
225                    OS_Time (To_Relative_Time (Duration'First));
226
227       Res_M : OS_Time;
228
229    begin
230       Res_M := OS_Time (Left) - OS_Time (Right);
231
232       --  Due to the extended range of Ada time, "-" is capable of producing
233       --  results which may exceed the range of Duration. In order to prevent
234       --  the generation of bogus values by the Unchecked_Conversion, we apply
235       --  the following check.
236
237       if Res_M < Dur_Low
238         or else Res_M >= Dur_High
239       then
240          raise Time_Error;
241
242       --  Normal case, result fits
243
244       else
245          return To_Duration (Time (Res_M));
246       end if;
247
248    exception
249       when Constraint_Error =>
250          raise Time_Error;
251    end "-";
252
253    ---------
254    -- "<" --
255    ---------
256
257    function "<" (Left, Right : Time) return Boolean is
258    begin
259       return OS_Time (Left) < OS_Time (Right);
260    end "<";
261
262    ----------
263    -- "<=" --
264    ----------
265
266    function "<=" (Left, Right : Time) return Boolean is
267    begin
268       return OS_Time (Left) <= OS_Time (Right);
269    end "<=";
270
271    ---------
272    -- ">" --
273    ---------
274
275    function ">" (Left, Right : Time) return Boolean is
276    begin
277       return OS_Time (Left) > OS_Time (Right);
278    end ">";
279
280    ----------
281    -- ">=" --
282    ----------
283
284    function ">=" (Left, Right : Time) return Boolean is
285    begin
286       return OS_Time (Left) >= OS_Time (Right);
287    end ">=";
288
289    ------------------------------
290    -- Check_Within_Time_Bounds --
291    ------------------------------
292
293    procedure Check_Within_Time_Bounds (T : OS_Time) is
294    begin
295       if Leap_Support then
296          if T < Ada_Low or else T > Ada_High_And_Leaps then
297             raise Time_Error;
298          end if;
299       else
300          if T < Ada_Low or else T > Ada_High then
301             raise Time_Error;
302          end if;
303       end if;
304    end Check_Within_Time_Bounds;
305
306    -----------
307    -- Clock --
308    -----------
309
310    function Clock return Time is
311       Elapsed_Leaps : Natural;
312       Next_Leap_M   : OS_Time;
313       Res_M         : constant OS_Time := OS_Clock;
314
315    begin
316       --  Note that on other targets a soft-link is used to get a different
317       --  clock depending whether tasking is used or not. On VMS this isn't
318       --  needed since all clock calls end up using SYS$GETTIM, so call the
319       --  OS_Primitives version for efficiency.
320
321       --  If the target supports leap seconds, determine the number of leap
322       --  seconds elapsed until this moment.
323
324       if Leap_Support then
325          Cumulative_Leap_Seconds
326            (Start_Of_Time, Res_M, Elapsed_Leaps, Next_Leap_M);
327
328          --  The system clock may fall exactly on a leap second
329
330          if Res_M >= Next_Leap_M then
331             Elapsed_Leaps := Elapsed_Leaps + 1;
332          end if;
333
334       --  The target does not support leap seconds
335
336       else
337          Elapsed_Leaps := 0;
338       end if;
339
340       return Time (Res_M + OS_Time (Elapsed_Leaps) * Mili);
341    end Clock;
342
343    -----------------------------
344    -- Cumulative_Leap_Seconds --
345    -----------------------------
346
347    procedure Cumulative_Leap_Seconds
348      (Start_Date    : OS_Time;
349       End_Date      : OS_Time;
350       Elapsed_Leaps : out Natural;
351       Next_Leap_Sec : out OS_Time)
352    is
353       End_Index   : Positive;
354       End_T       : OS_Time := End_Date;
355       Start_Index : Positive;
356       Start_T     : OS_Time := Start_Date;
357
358    begin
359       pragma Assert (Leap_Support and then End_Date >= Start_Date);
360
361       Next_Leap_Sec := End_Of_Time;
362
363       --  Make sure that the end date does not exceed the upper bound
364       --  of Ada time.
365
366       if End_Date > Ada_High then
367          End_T := Ada_High;
368       end if;
369
370       --  Remove the sub seconds from both dates
371
372       Start_T := Start_T - (Start_T mod Mili);
373       End_T   := End_T   - (End_T   mod Mili);
374
375       --  Some trivial cases:
376       --                     Leap 1 . . . Leap N
377       --  ---+========+------+############+-------+========+-----
378       --     Start_T  End_T                       Start_T  End_T
379
380       if End_T < Leap_Second_Times (1) then
381          Elapsed_Leaps := 0;
382          Next_Leap_Sec := Leap_Second_Times (1);
383          return;
384
385       elsif Start_T > Leap_Second_Times (Leap_Seconds_Count) then
386          Elapsed_Leaps := 0;
387          Next_Leap_Sec := End_Of_Time;
388          return;
389       end if;
390
391       --  Perform the calculations only if the start date is within the leap
392       --  second occurrences table.
393
394       if Start_T <= Leap_Second_Times (Leap_Seconds_Count) then
395
396          --    1    2                  N - 1   N
397          --  +----+----+--  . . .  --+-------+---+
398          --  | T1 | T2 |             | N - 1 | N |
399          --  +----+----+--  . . .  --+-------+---+
400          --         ^                   ^
401          --         | Start_Index       | End_Index
402          --         +-------------------+
403          --             Leaps_Between
404
405          --  The idea behind the algorithm is to iterate and find two closest
406          --  dates which are after Start_T and End_T. Their corresponding
407          --  index difference denotes the number of leap seconds elapsed.
408
409          Start_Index := 1;
410          loop
411             exit when Leap_Second_Times (Start_Index) >= Start_T;
412             Start_Index := Start_Index + 1;
413          end loop;
414
415          End_Index := Start_Index;
416          loop
417             exit when End_Index > Leap_Seconds_Count
418               or else Leap_Second_Times (End_Index) >= End_T;
419             End_Index := End_Index + 1;
420          end loop;
421
422          if End_Index <= Leap_Seconds_Count then
423             Next_Leap_Sec := Leap_Second_Times (End_Index);
424          end if;
425
426          Elapsed_Leaps := End_Index - Start_Index;
427
428       else
429          Elapsed_Leaps := 0;
430       end if;
431    end Cumulative_Leap_Seconds;
432
433    ---------
434    -- Day --
435    ---------
436
437    function Day (Date : Time) return Day_Number is
438       Y : Year_Number;
439       M : Month_Number;
440       D : Day_Number;
441       S : Day_Duration;
442       pragma Unreferenced (Y, M, S);
443    begin
444       Split (Date, Y, M, D, S);
445       return D;
446    end Day;
447
448    -------------
449    -- Is_Leap --
450    -------------
451
452    function Is_Leap (Year : Year_Number) return Boolean is
453    begin
454       --  Leap centennial years
455
456       if Year mod 400 = 0 then
457          return True;
458
459       --  Non-leap centennial years
460
461       elsif Year mod 100 = 0 then
462          return False;
463
464       --  Regular years
465
466       else
467          return Year mod 4 = 0;
468       end if;
469    end Is_Leap;
470
471    -----------
472    -- Month --
473    -----------
474
475    function Month (Date : Time) return Month_Number is
476       Y : Year_Number;
477       M : Month_Number;
478       D : Day_Number;
479       S : Day_Duration;
480       pragma Unreferenced (Y, D, S);
481    begin
482       Split (Date, Y, M, D, S);
483       return M;
484    end Month;
485
486    -------------
487    -- Seconds --
488    -------------
489
490    function Seconds (Date : Time) return Day_Duration is
491       Y : Year_Number;
492       M : Month_Number;
493       D : Day_Number;
494       S : Day_Duration;
495       pragma Unreferenced (Y, M, D);
496    begin
497       Split (Date, Y, M, D, S);
498       return S;
499    end Seconds;
500
501    -----------
502    -- Split --
503    -----------
504
505    procedure Split
506      (Date    : Time;
507       Year    : out Year_Number;
508       Month   : out Month_Number;
509       Day     : out Day_Number;
510       Seconds : out Day_Duration)
511    is
512       H  : Integer;
513       M  : Integer;
514       Se : Integer;
515       Ss : Duration;
516       Le : Boolean;
517
518    begin
519       --  Use UTC as the local time zone on VMS, the status of flag Is_Ada_05
520       --  is irrelevant in this case.
521
522       Formatting_Operations.Split
523         (Date      => Date,
524          Year      => Year,
525          Month     => Month,
526          Day       => Day,
527          Day_Secs  => Seconds,
528          Hour      => H,
529          Minute    => M,
530          Second    => Se,
531          Sub_Sec   => Ss,
532          Leap_Sec  => Le,
533          Is_Ada_05 => False,
534          Time_Zone => 0);
535
536       --  Validity checks
537
538       if not Year'Valid
539         or else not Month'Valid
540         or else not Day'Valid
541         or else not Seconds'Valid
542       then
543          raise Time_Error;
544       end if;
545    end Split;
546
547    -------------
548    -- Time_Of --
549    -------------
550
551    function Time_Of
552      (Year    : Year_Number;
553       Month   : Month_Number;
554       Day     : Day_Number;
555       Seconds : Day_Duration := 0.0) return Time
556    is
557       --  The values in the following constants are irrelevant, they are just
558       --  placeholders; the choice of constructing a Day_Duration value is
559       --  controlled by the Use_Day_Secs flag.
560
561       H  : constant Integer := 1;
562       M  : constant Integer := 1;
563       Se : constant Integer := 1;
564       Ss : constant Duration := 0.1;
565
566    begin
567       if not Year'Valid
568         or else not Month'Valid
569         or else not Day'Valid
570         or else not Seconds'Valid
571       then
572          raise Time_Error;
573       end if;
574
575       --  Use UTC as the local time zone on VMS, the status of flag Is_Ada_05
576       --  is irrelevant in this case.
577
578       return
579         Formatting_Operations.Time_Of
580           (Year         => Year,
581            Month        => Month,
582            Day          => Day,
583            Day_Secs     => Seconds,
584            Hour         => H,
585            Minute       => M,
586            Second       => Se,
587            Sub_Sec      => Ss,
588            Leap_Sec     => False,
589            Use_Day_Secs => True,
590            Is_Ada_05    => False,
591            Time_Zone    => 0);
592    end Time_Of;
593
594    -----------------
595    -- To_Duration --
596    -----------------
597
598    function To_Duration (T : Time) return Duration is
599       function Time_To_Duration is
600         new Ada.Unchecked_Conversion (Time, Duration);
601    begin
602       return Time_To_Duration (T * 100);
603    end To_Duration;
604
605    ----------------------
606    -- To_Relative_Time --
607    ----------------------
608
609    function To_Relative_Time (D : Duration) return Time is
610       function Duration_To_Time is
611         new Ada.Unchecked_Conversion (Duration, Time);
612    begin
613       return Duration_To_Time (D / 100.0);
614    end To_Relative_Time;
615
616    ----------
617    -- Year --
618    ----------
619
620    function Year (Date : Time) return Year_Number is
621       Y : Year_Number;
622       M : Month_Number;
623       D : Day_Number;
624       S : Day_Duration;
625       pragma Unreferenced (M, D, S);
626    begin
627       Split (Date, Y, M, D, S);
628       return Y;
629    end Year;
630
631    --  The following packages assume that Time is a Long_Integer, the units
632    --  are 100 nanoseconds and the starting point in the VMS Epoch.
633
634    ---------------------------
635    -- Arithmetic_Operations --
636    ---------------------------
637
638    package body Arithmetic_Operations is
639
640       ---------
641       -- Add --
642       ---------
643
644       function Add (Date : Time; Days : Long_Integer) return Time is
645          pragma Unsuppress (Overflow_Check);
646          Date_M : constant OS_Time := OS_Time (Date);
647       begin
648          return Time (Date_M + OS_Time (Days) * Milis_In_Day);
649       exception
650          when Constraint_Error =>
651             raise Time_Error;
652       end Add;
653
654       ----------------
655       -- Difference --
656       ----------------
657
658       procedure Difference
659         (Left         : Time;
660          Right        : Time;
661          Days         : out Long_Integer;
662          Seconds      : out Duration;
663          Leap_Seconds : out Integer)
664       is
665          Diff_M        : OS_Time;
666          Diff_S        : OS_Time;
667          Earlier       : OS_Time;
668          Elapsed_Leaps : Natural;
669          Later         : OS_Time;
670          Negate        : Boolean := False;
671          Next_Leap     : OS_Time;
672          Sub_Seconds   : Duration;
673
674       begin
675          --  This classification is necessary in order to avoid a Time_Error
676          --  being raised by the arithmetic operators in Ada.Calendar.
677
678          if Left >= Right then
679             Later   := OS_Time (Left);
680             Earlier := OS_Time (Right);
681          else
682             Later   := OS_Time (Right);
683             Earlier := OS_Time (Left);
684             Negate  := True;
685          end if;
686
687          --  If the target supports leap seconds, process them
688
689          if Leap_Support then
690             Cumulative_Leap_Seconds
691               (Earlier, Later, Elapsed_Leaps, Next_Leap);
692
693             if Later >= Next_Leap then
694                Elapsed_Leaps := Elapsed_Leaps + 1;
695             end if;
696
697          --  The target does not support leap seconds
698
699          else
700             Elapsed_Leaps := 0;
701          end if;
702
703          Diff_M := Later - Earlier - OS_Time (Elapsed_Leaps) * Mili;
704
705          --  Sub second processing
706
707          Sub_Seconds := Duration (Diff_M mod Mili) / Mili_F;
708
709          --  Convert to seconds. Note that his action eliminates the sub
710          --  seconds automatically.
711
712          Diff_S := Diff_M / Mili;
713
714          Days := Long_Integer (Diff_S / Secs_In_Day);
715          Seconds := Duration (Diff_S mod Secs_In_Day) + Sub_Seconds;
716          Leap_Seconds := Integer (Elapsed_Leaps);
717
718          if Negate then
719             Days    := -Days;
720             Seconds := -Seconds;
721
722             if Leap_Seconds /= 0 then
723                Leap_Seconds := -Leap_Seconds;
724             end if;
725          end if;
726       end Difference;
727
728       --------------
729       -- Subtract --
730       --------------
731
732       function Subtract (Date : Time; Days : Long_Integer) return Time is
733          pragma Unsuppress (Overflow_Check);
734          Date_M : constant OS_Time := OS_Time (Date);
735       begin
736          return Time (Date_M - OS_Time (Days) * Milis_In_Day);
737       exception
738          when Constraint_Error =>
739             raise Time_Error;
740       end Subtract;
741    end Arithmetic_Operations;
742
743    ---------------------------
744    -- Conversion_Operations --
745    ---------------------------
746
747    package body Conversion_Operations is
748
749       Epoch_Offset : constant OS_Time := 35067168000000000;
750       --  The difference between 1970-1-1 UTC and 1858-11-17 UTC expressed in
751       --  100 nanoseconds.
752
753       -----------------
754       -- To_Ada_Time --
755       -----------------
756
757       function To_Ada_Time (Unix_Time : Long_Integer) return Time is
758          pragma Unsuppress (Overflow_Check);
759          Unix_Rep : constant OS_Time := OS_Time (Unix_Time) * Mili;
760       begin
761          return Time (Unix_Rep + Epoch_Offset);
762       exception
763          when Constraint_Error =>
764             raise Time_Error;
765       end To_Ada_Time;
766
767       -----------------
768       -- To_Ada_Time --
769       -----------------
770
771       function To_Ada_Time
772         (tm_year  : Integer;
773          tm_mon   : Integer;
774          tm_day   : Integer;
775          tm_hour  : Integer;
776          tm_min   : Integer;
777          tm_sec   : Integer;
778          tm_isdst : Integer) return Time
779       is
780          pragma Unsuppress (Overflow_Check);
781
782          Year_Shift  : constant Integer := 1900;
783          Month_Shift : constant Integer := 1;
784
785          Year   : Year_Number;
786          Month  : Month_Number;
787          Day    : Day_Number;
788          Second : Integer;
789          Leap   : Boolean;
790          Result : OS_Time;
791
792       begin
793          --  Input processing
794
795          Year  := Year_Number (Year_Shift + tm_year);
796          Month := Month_Number (Month_Shift + tm_mon);
797          Day   := Day_Number (tm_day);
798
799          --  Step 1: Validity checks of input values
800
801          if not Year'Valid
802            or else not Month'Valid
803            or else not Day'Valid
804            or else tm_hour not in 0 .. 24
805            or else tm_min not in 0 .. 59
806            or else tm_sec not in 0 .. 60
807            or else tm_isdst not in -1 .. 1
808          then
809             raise Time_Error;
810          end if;
811
812          --  Step 2: Potential leap second
813
814          if tm_sec = 60 then
815             Leap   := True;
816             Second := 59;
817          else
818             Leap   := False;
819             Second := tm_sec;
820          end if;
821
822          --  Step 3: Calculate the time value
823
824          Result :=
825            OS_Time
826              (Formatting_Operations.Time_Of
827                (Year         => Year,
828                 Month        => Month,
829                 Day          => Day,
830                 Day_Secs     => 0.0,      --  Time is given in h:m:s
831                 Hour         => tm_hour,
832                 Minute       => tm_min,
833                 Second       => Second,
834                 Sub_Sec      => 0.0,      --  No precise sub second given
835                 Leap_Sec     => Leap,
836                 Use_Day_Secs => False,    --  Time is given in h:m:s
837                 Is_Ada_05    => True,     --  Force usage of explicit time zone
838                 Time_Zone    => 0));      --  Place the value in UTC
839          --  Step 4: Daylight Savings Time
840
841          if tm_isdst = 1 then
842             Result := Result + OS_Time (3_600) * Mili;
843          end if;
844
845          return Time (Result);
846       exception
847          when Constraint_Error =>
848             raise Time_Error;
849       end To_Ada_Time;
850
851       -----------------
852       -- To_Duration --
853       -----------------
854
855       function To_Duration
856         (tv_sec  : Long_Integer;
857          tv_nsec : Long_Integer) return Duration
858       is
859          pragma Unsuppress (Overflow_Check);
860       begin
861          return Duration (tv_sec) + Duration (tv_nsec) / Mili_F;
862       end To_Duration;
863
864       ------------------------
865       -- To_Struct_Timespec --
866       ------------------------
867
868       procedure To_Struct_Timespec
869         (D       : Duration;
870          tv_sec  : out Long_Integer;
871          tv_nsec : out Long_Integer)
872       is
873          pragma Unsuppress (Overflow_Check);
874          Secs      : Duration;
875          Nano_Secs : Duration;
876
877       begin
878          --  Seconds extraction, avoid potential rounding errors
879
880          Secs   := D - 0.5;
881          tv_sec := Long_Integer (Secs);
882
883          --  100 Nanoseconds extraction
884
885          Nano_Secs := D - Duration (tv_sec);
886          tv_nsec := Long_Integer (Nano_Secs * Mili);
887       end To_Struct_Timespec;
888
889       ------------------
890       -- To_Struct_Tm --
891       ------------------
892
893       procedure To_Struct_Tm
894         (T       : Time;
895          tm_year : out Integer;
896          tm_mon  : out Integer;
897          tm_day  : out Integer;
898          tm_hour : out Integer;
899          tm_min  : out Integer;
900          tm_sec  : out Integer)
901       is
902          pragma Unsuppress (Overflow_Check);
903          Year      : Year_Number;
904          Month     : Month_Number;
905          Second    : Integer;
906          Day_Secs  : Day_Duration;
907          Sub_Sec   : Duration;
908          Leap_Sec  : Boolean;
909
910       begin
911          --  Step 1: Split the input time
912
913          Formatting_Operations.Split
914            (T, Year, Month, tm_day, Day_Secs,
915             tm_hour, tm_min, Second, Sub_Sec, Leap_Sec, True, 0);
916
917          --  Step 2: Correct the year and month
918
919          tm_year := Year - 1900;
920          tm_mon  := Month - 1;
921
922          --  Step 3: Handle leap second occurrences
923
924          tm_sec := (if Leap_Sec then 60 else Second);
925       end To_Struct_Tm;
926
927       ------------------
928       -- To_Unix_Time --
929       ------------------
930
931       function To_Unix_Time (Ada_Time : Time) return Long_Integer is
932          pragma Unsuppress (Overflow_Check);
933          Ada_OS_Time : constant OS_Time := OS_Time (Ada_Time);
934       begin
935          return Long_Integer ((Ada_OS_Time - Epoch_Offset) / Mili);
936       exception
937          when Constraint_Error =>
938             raise Time_Error;
939       end To_Unix_Time;
940    end Conversion_Operations;
941
942    ---------------------------
943    -- Formatting_Operations --
944    ---------------------------
945
946    package body Formatting_Operations is
947
948       -----------------
949       -- Day_Of_Week --
950       -----------------
951
952       function Day_Of_Week (Date : Time) return Integer is
953          Y : Year_Number;
954          M : Month_Number;
955          D : Day_Number;
956          S : Day_Duration;
957
958          Day_Count     : Long_Integer;
959          Midday_Date_S : Time;
960
961       begin
962          Split (Date, Y, M, D, S);
963
964          --  Build a time value in the middle of the same day and convert the
965          --  time value to seconds.
966
967          Midday_Date_S := Time_Of (Y, M, D, 43_200.0) / Mili;
968
969          --  Count the number of days since the start of VMS time. 1858-11-17
970          --  was a Wednesday.
971
972          Day_Count := Long_Integer (Midday_Date_S / Secs_In_Day) + 2;
973
974          return Integer (Day_Count mod 7);
975       end Day_Of_Week;
976
977       -----------
978       -- Split --
979       -----------
980
981       procedure Split
982         (Date      : Time;
983          Year      : out Year_Number;
984          Month     : out Month_Number;
985          Day       : out Day_Number;
986          Day_Secs  : out Day_Duration;
987          Hour      : out Integer;
988          Minute    : out Integer;
989          Second    : out Integer;
990          Sub_Sec   : out Duration;
991          Leap_Sec  : out Boolean;
992          Is_Ada_05 : Boolean;
993          Time_Zone : Long_Integer)
994       is
995          --  The flag Is_Ada_05 is present for interfacing purposes
996
997          pragma Unreferenced (Is_Ada_05);
998
999          procedure Numtim
1000            (Status : out Unsigned_Longword;
1001             Timbuf : out Unsigned_Word_Array;
1002             Timadr : Time);
1003
1004          pragma Interface (External, Numtim);
1005
1006          pragma Import_Valued_Procedure
1007            (Numtim, "SYS$NUMTIM",
1008            (Unsigned_Longword, Unsigned_Word_Array, Time),
1009            (Value, Reference, Reference));
1010
1011          Status : Unsigned_Longword;
1012          Timbuf : Unsigned_Word_Array (1 .. 7);
1013
1014          Ada_Min_Year : constant := 1901;
1015          Ada_Max_Year : constant := 2399;
1016
1017          Date_M        : OS_Time;
1018          Elapsed_Leaps : Natural;
1019          Next_Leap_M   : OS_Time;
1020
1021       begin
1022          Date_M := OS_Time (Date);
1023
1024          --  Step 1: Leap seconds processing
1025
1026          if Leap_Support then
1027             Cumulative_Leap_Seconds
1028               (Start_Of_Time, Date_M, Elapsed_Leaps, Next_Leap_M);
1029
1030             Leap_Sec := Date_M >= Next_Leap_M;
1031
1032             if Leap_Sec then
1033                Elapsed_Leaps := Elapsed_Leaps + 1;
1034             end if;
1035
1036          --  The target does not support leap seconds
1037
1038          else
1039             Elapsed_Leaps := 0;
1040             Leap_Sec      := False;
1041          end if;
1042
1043          Date_M := Date_M - OS_Time (Elapsed_Leaps) * Mili;
1044
1045          --  Step 2: Time zone processing
1046
1047          if Time_Zone /= 0 then
1048             Date_M := Date_M + OS_Time (Time_Zone) * 60 * Mili;
1049          end if;
1050
1051          --  After the leap seconds and time zone have been accounted for,
1052          --  the date should be within the bounds of Ada time.
1053
1054          if Date_M < Ada_Low
1055            or else Date_M > Ada_High
1056          then
1057             raise Time_Error;
1058          end if;
1059
1060          --  Step 3: Sub second processing
1061
1062          Sub_Sec := Duration (Date_M mod Mili) / Mili_F;
1063
1064          --  Drop the sub seconds
1065
1066          Date_M := Date_M - (Date_M mod Mili);
1067
1068          --  Step 4: VMS system call
1069
1070          Numtim (Status, Timbuf, Time (Date_M));
1071
1072          if Status mod 2 /= 1
1073            or else Timbuf (1) not in Ada_Min_Year .. Ada_Max_Year
1074          then
1075             raise Time_Error;
1076          end if;
1077
1078          --  Step 5: Time components processing
1079
1080          Year   := Year_Number (Timbuf (1));
1081          Month  := Month_Number (Timbuf (2));
1082          Day    := Day_Number (Timbuf (3));
1083          Hour   := Integer (Timbuf (4));
1084          Minute := Integer (Timbuf (5));
1085          Second := Integer (Timbuf (6));
1086
1087          Day_Secs := Day_Duration (Hour   * 3_600) +
1088                      Day_Duration (Minute *    60) +
1089                      Day_Duration (Second)         +
1090                                    Sub_Sec;
1091       end Split;
1092
1093       -------------
1094       -- Time_Of --
1095       -------------
1096
1097       function Time_Of
1098         (Year         : Year_Number;
1099          Month        : Month_Number;
1100          Day          : Day_Number;
1101          Day_Secs     : Day_Duration;
1102          Hour         : Integer;
1103          Minute       : Integer;
1104          Second       : Integer;
1105          Sub_Sec      : Duration;
1106          Leap_Sec     : Boolean := False;
1107          Use_Day_Secs : Boolean := False;
1108          Is_Ada_05    : Boolean := False;
1109          Time_Zone    : Long_Integer := 0) return Time
1110       is
1111          procedure Cvt_Vectim
1112            (Status         : out Unsigned_Longword;
1113             Input_Time     : Unsigned_Word_Array;
1114             Resultant_Time : out Time);
1115
1116          pragma Interface (External, Cvt_Vectim);
1117
1118          pragma Import_Valued_Procedure
1119            (Cvt_Vectim, "LIB$CVT_VECTIM",
1120            (Unsigned_Longword, Unsigned_Word_Array, Time),
1121            (Value, Reference, Reference));
1122
1123          Status : Unsigned_Longword;
1124          Timbuf : Unsigned_Word_Array (1 .. 7);
1125
1126          Y  : Year_Number  := Year;
1127          Mo : Month_Number := Month;
1128          D  : Day_Number   := Day;
1129          H  : Integer      := Hour;
1130          Mi : Integer      := Minute;
1131          Se : Integer      := Second;
1132          Su : Duration     := Sub_Sec;
1133
1134          Elapsed_Leaps : Natural;
1135          Int_Day_Secs  : Integer;
1136          Next_Leap_M   : OS_Time;
1137          Res           : Time;
1138          Res_M         : OS_Time;
1139          Rounded_Res_M : OS_Time;
1140
1141       begin
1142          --  No validity checks are performed on the input values since it is
1143          --  assumed that the called has already performed them.
1144
1145          --  Step 1: Hour, minute, second and sub second processing
1146
1147          if Use_Day_Secs then
1148
1149             --  A day seconds value of 86_400 designates a new day
1150
1151             if Day_Secs = 86_400.0 then
1152                declare
1153                   Adj_Year  : Year_Number := Year;
1154                   Adj_Month : Month_Number := Month;
1155                   Adj_Day   : Day_Number   := Day;
1156
1157                begin
1158                   if Day < Days_In_Month (Month)
1159                     or else (Month = 2
1160                                and then Is_Leap (Year))
1161                   then
1162                      Adj_Day := Day + 1;
1163
1164                   --  The day adjustment moves the date to a new month
1165
1166                   else
1167                      Adj_Day := 1;
1168
1169                      if Month < 12 then
1170                         Adj_Month := Month + 1;
1171
1172                      --  The month adjustment moves the date to a new year
1173
1174                      else
1175                         Adj_Month := 1;
1176                         Adj_Year  := Year + 1;
1177                      end if;
1178                   end if;
1179
1180                   Y  := Adj_Year;
1181                   Mo := Adj_Month;
1182                   D  := Adj_Day;
1183                   H  := 0;
1184                   Mi := 0;
1185                   Se := 0;
1186                   Su := 0.0;
1187                end;
1188
1189             --  Normal case (not exactly one day)
1190
1191             else
1192                --  Sub second extraction
1193
1194                Int_Day_Secs :=
1195                  (if Day_Secs > 0.0
1196                   then Integer (Day_Secs - 0.5)
1197                   else Integer (Day_Secs));
1198
1199                H  := Int_Day_Secs / 3_600;
1200                Mi := (Int_Day_Secs / 60) mod 60;
1201                Se := Int_Day_Secs mod 60;
1202                Su := Day_Secs - Duration (Int_Day_Secs);
1203             end if;
1204          end if;
1205
1206          --  Step 2: System call to VMS
1207
1208          Timbuf (1) := Unsigned_Word (Y);
1209          Timbuf (2) := Unsigned_Word (Mo);
1210          Timbuf (3) := Unsigned_Word (D);
1211          Timbuf (4) := Unsigned_Word (H);
1212          Timbuf (5) := Unsigned_Word (Mi);
1213          Timbuf (6) := Unsigned_Word (Se);
1214          Timbuf (7) := 0;
1215
1216          Cvt_Vectim (Status, Timbuf, Res);
1217
1218          if Status mod 2 /= 1 then
1219             raise Time_Error;
1220          end if;
1221
1222          --  Step 3: Sub second adjustment
1223
1224          Res_M := OS_Time (Res) + OS_Time (Su * Mili_F);
1225
1226          --  Step 4: Bounds check
1227
1228          Check_Within_Time_Bounds (Res_M);
1229
1230          --  Step 5: Time zone processing
1231
1232          if Time_Zone /= 0 then
1233             Res_M := Res_M - OS_Time (Time_Zone) * 60 * Mili;
1234          end if;
1235
1236          --  Step 6: Leap seconds processing
1237
1238          if Leap_Support then
1239             Cumulative_Leap_Seconds
1240               (Start_Of_Time, Res_M, Elapsed_Leaps, Next_Leap_M);
1241
1242             Res_M := Res_M + OS_Time (Elapsed_Leaps) * Mili;
1243
1244             --  An Ada 2005 caller requesting an explicit leap second or an
1245             --  Ada 95 caller accounting for an invisible leap second.
1246
1247             if Leap_Sec
1248               or else Res_M >= Next_Leap_M
1249             then
1250                Res_M := Res_M + OS_Time (1) * Mili;
1251             end if;
1252
1253             --  Leap second validity check
1254
1255             Rounded_Res_M := Res_M - (Res_M mod Mili);
1256
1257             if Is_Ada_05
1258               and then Leap_Sec
1259               and then Rounded_Res_M /= Next_Leap_M
1260             then
1261                raise Time_Error;
1262             end if;
1263          end if;
1264
1265          return Time (Res_M);
1266       end Time_Of;
1267    end Formatting_Operations;
1268
1269    ---------------------------
1270    -- Time_Zones_Operations --
1271    ---------------------------
1272
1273    package body Time_Zones_Operations is
1274
1275       ---------------------
1276       -- UTC_Time_Offset --
1277       ---------------------
1278
1279       function UTC_Time_Offset (Date : Time) return Long_Integer is
1280          --  Formal parameter Date is here for interfacing, but is never
1281          --  actually used.
1282
1283          pragma Unreferenced (Date);
1284
1285          function get_gmtoff return Long_Integer;
1286          pragma Import (C, get_gmtoff, "get_gmtoff");
1287
1288       begin
1289          --  VMS is not capable of determining the time zone in some past or
1290          --  future point in time denoted by Date, thus the current time zone
1291          --  is retrieved.
1292
1293          return get_gmtoff;
1294       end UTC_Time_Offset;
1295    end Time_Zones_Operations;
1296 end Ada.Calendar;