From: Tonu Naks <n...@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 --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 -- 2.45.2