diff mbox series

[COMMITTED,13/26] ada: Add leap second support to conversion of Unix_Time

Message ID 20240802071210.413366-13-poulhies@adacore.com
State New
Headers show
Series [COMMITTED,01/26] ada: Fix detection of suspicious loop patterns | expand

Commit Message

Marc Poulhiès Aug. 2, 2024, 7:11 a.m. UTC
From: Tonu Naks <naks@adacore.com>

Unix timestamp jumps one second back when a leap second
is applied and doesn't count cumulative leap seconds.
This was not taken into account in conversions between
Unix time and Ada time. Now fixed.

gcc/ada/

	* libgnat/a-calend.adb: Modify unix time handling.

Tested on x86_64-pc-linux-gnu, committed on master.

---
 gcc/ada/libgnat/a-calend.adb | 135 ++++++++++++++++++-----------------
 1 file changed, 70 insertions(+), 65 deletions(-)
diff mbox series

Patch

diff --git a/gcc/ada/libgnat/a-calend.adb b/gcc/ada/libgnat/a-calend.adb
index 1083ece44d2..c28042d13c4 100644
--- a/gcc/ada/libgnat/a-calend.adb
+++ b/gcc/ada/libgnat/a-calend.adb
@@ -110,6 +110,17 @@  is
      new Ada.Unchecked_Conversion (Duration, Time_Rep);
    --  Convert a duration value into a time representation value
 
+   function Elapsed_Leaps (Start_Time, End_Time : Time_Rep) return Natural
+      with Pre => (End_Time >= Start_Time);
+   --  If the target supports leap seconds, determine the number of leap
+   --  seconds elapsed between start_time and end_time.
+   --
+   --  NB! This function assumes that End_Time is not smaller than
+   --  Start_Time. There are usages of the function that correct the time
+   --  by passed leap seconds and use the results for another seach.
+   --  If negative leap seconds are introduced eventually, then such
+   --  calls should be revised as the correction can go to either direction.
+
    function Time_Rep_To_Duration is
      new Ada.Unchecked_Conversion (Time_Rep, Duration);
    --  Convert a time representation value into a duration value
@@ -355,13 +366,34 @@  is
       end if;
    end Check_Within_Time_Bounds;
 
+   -------------------
+   -- Elapsed_Leaps --
+   -------------------
+
+   function Elapsed_Leaps (Start_Time, End_Time : Time_Rep) return Natural
+   is
+      Elapsed       : Natural := 0;
+      Next_Leap_N   : Time_Rep;
+   begin
+      if Leap_Support then
+         Cumulative_Leap_Seconds
+           (Start_Time, End_Time, Elapsed, Next_Leap_N);
+
+         --  The system clock may fall exactly on a leap second
+
+         if End_Time >= Next_Leap_N then
+            Elapsed := Elapsed + 1;
+         end if;
+      end if;
+
+      return Elapsed;
+   end Elapsed_Leaps;
+
    -----------
    -- Clock --
    -----------
 
    function Clock return Time is
-      Elapsed_Leaps : Natural;
-      Next_Leap_N   : Time_Rep;
 
       --  The system clock returns the time in UTC since the Unix Epoch of
       --  1970-01-01 00:00:00.0. We perform an origin shift to the Ada Epoch
@@ -371,26 +403,7 @@  is
         Duration_To_Time_Rep (System.OS_Primitives.Clock) + Unix_Min;
 
    begin
-      --  If the target supports leap seconds, determine the number of leap
-      --  seconds elapsed until this moment.
-
-      if Leap_Support then
-         Cumulative_Leap_Seconds
-           (Start_Of_Time, Res_N, Elapsed_Leaps, Next_Leap_N);
-
-         --  The system clock may fall exactly on a leap second
-
-         if Res_N >= Next_Leap_N then
-            Elapsed_Leaps := Elapsed_Leaps + 1;
-         end if;
-
-      --  The target does not support leap seconds
-
-      else
-         Elapsed_Leaps := 0;
-      end if;
-
-      Res_N := Res_N + Time_Rep (Elapsed_Leaps) * Nano;
+      Res_N := Res_N + Time_Rep (Elapsed_Leaps (Start_Of_Time, Res_N)) * Nano;
 
       return Time (Res_N);
    end Clock;
@@ -806,10 +819,8 @@  is
       is
          Res_Dur       : Time_Dur;
          Earlier       : Time_Rep;
-         Elapsed_Leaps : Natural;
          Later         : Time_Rep;
          Negate        : Boolean := False;
-         Next_Leap_N   : Time_Rep;
          Sub_Secs      : Duration;
          Sub_Secs_Diff : Time_Rep;
 
@@ -825,22 +836,6 @@  is
             Negate  := True;
          end if;
 
-         --  If the target supports leap seconds, process them
-
-         if Leap_Support then
-            Cumulative_Leap_Seconds
-              (Earlier, Later, Elapsed_Leaps, Next_Leap_N);
-
-            if Later >= Next_Leap_N then
-               Elapsed_Leaps := Elapsed_Leaps + 1;
-            end if;
-
-         --  The target does not support leap seconds
-
-         else
-            Elapsed_Leaps := 0;
-         end if;
-
          --  Sub seconds processing. We add the resulting difference to one
          --  of the input dates in order to account for any potential rounding
          --  of the difference in the next step.
@@ -856,12 +851,14 @@  is
          --  either add or drop a second. We compensate for this issue in the
          --  previous step.
 
+         Leap_Seconds := Elapsed_Leaps (Earlier, Later);
+
          Res_Dur :=
-           Time_Dur (Later / Nano - Earlier / Nano) - Time_Dur (Elapsed_Leaps);
+           Time_Dur (Later / Nano - Earlier / Nano) -
+           Time_Dur (Leap_Seconds);
 
          Days         := Long_Integer (Res_Dur / Secs_In_Day);
          Seconds      := Duration (Res_Dur mod Secs_In_Day) + Sub_Secs;
-         Leap_Seconds := Integer (Elapsed_Leaps);
 
          if Negate then
             Days    := -Days;
@@ -901,9 +898,33 @@  is
 
       function To_Ada_Time (Unix_Time : Long_Integer) return Time is
          pragma Unsuppress (Overflow_Check);
-         Unix_Rep : constant Time_Rep := Time_Rep (Unix_Time) * Nano;
+         Ada_Rep : Time_Rep := Time_Rep (Unix_Time * Nano) - Epoch_Offset;
+
+         --  Count leaps passed until the converted time.
+
+         Leaps : constant Natural :=
+            Elapsed_Leaps (Start_Of_Time, Ada_Rep);
       begin
-         return Time (Unix_Rep - Epoch_Offset);
+
+         --  If leap seconds were found then update the result accordingly
+
+         if Leaps /= 0 then
+            declare
+               --  adjust the time by the number of leap seconds
+               Corrected_Ada_Rep : constant Time_Rep :=
+                  Ada_Rep + Time_Rep ((Leaps) * Nano);
+
+               --  Check if the corrected time passed the boundary
+               --  of another leap second
+               Extra_Leaps : constant Natural :=
+                  Elapsed_Leaps (Ada_Rep, Corrected_Ada_Rep);
+            begin
+               Ada_Rep := Corrected_Ada_Rep + Time_Rep (Extra_Leaps * Nano);
+            end;
+         end if;
+
+         return Time (Ada_Rep);
+
       exception
          when Constraint_Error =>
             raise Time_Error;
@@ -1085,7 +1106,8 @@  is
          pragma Unsuppress (Overflow_Check);
          Ada_Rep : constant Time_Rep := Time_Rep (Ada_Time);
       begin
-         return Long_Integer ((Ada_Rep + Epoch_Offset) / Nano);
+         return Long_Integer ((Ada_Rep + Epoch_Offset) / Nano) -
+            Long_Integer (Elapsed_Leaps (Start_Of_Time, Ada_Rep));
       exception
          when Constraint_Error =>
             raise Time_Error;
@@ -1112,9 +1134,7 @@  is
          --  failure. To prevent this, the function returns the "safe" end of
          --  time (roughly 2219) which is still distant enough.
 
-         Elapsed_Leaps : Natural;
-         Next_Leap_N   : Time_Rep;
-         Res_N         : Time_Rep;
+         Res_N : Time_Rep;
 
       begin
          Res_N := Time_Rep (Date);
@@ -1122,23 +1142,8 @@  is
          --  Step 1: If the target supports leap seconds, remove any leap
          --  seconds elapsed up to the input date.
 
-         if Leap_Support then
-            Cumulative_Leap_Seconds
-              (Start_Of_Time, Res_N, Elapsed_Leaps, Next_Leap_N);
-
-            --  The input time value may fall on a leap second occurrence
-
-            if Res_N >= Next_Leap_N then
-               Elapsed_Leaps := Elapsed_Leaps + 1;
-            end if;
-
-         --  The target does not support leap seconds
-
-         else
-            Elapsed_Leaps := 0;
-         end if;
-
-         Res_N := Res_N - Time_Rep (Elapsed_Leaps) * Nano;
+         Res_N := Res_N -
+            Time_Rep (Elapsed_Leaps (Start_Of_Time, Res_N)) * Nano;
 
          --  Step 2: Perform a shift in origins to obtain a Unix equivalent of
          --  the input. Guard against very large delay values such as the end