Standard library: Dates & Times

The standard library supports processing of dates and times using two approaches:

  • Calendar approach, which is suitable for handling dates and times in general;

  • Real-time approach, which is better suited for real-time applications that require enhanced precision — for example, by having access to an absolute clock and handling time spans. Note that this approach only supports times, not dates.

The following sections present these two approaches.

Date and time handling

The Ada.Calendar package supports handling of dates and times. Let's look at a simple example:

with Ada.Calendar; use Ada.Calendar; with Ada.Calendar.Formatting; use Ada.Calendar.Formatting; with Ada.Text_IO; use Ada.Text_IO; procedure Display_Current_Time is Now : Time := Clock; begin Put_Line ("Current time: " & Image (Now)); end Display_Current_Time;

This example displays the current date and time, which is retrieved by a call to the Clock function. We call the function Image from the Ada.Calendar.Formatting package to get a String for the current date and time. We could instead retrieve each component using the Split function. For example:

with Ada.Calendar; use Ada.Calendar; with Ada.Text_IO; use Ada.Text_IO; procedure Display_Current_Year is Now : Time := Clock; Now_Year : Year_Number; Now_Month : Month_Number; Now_Day : Day_Number; Now_Seconds : Day_Duration; begin Split (Now, Now_Year, Now_Month, Now_Day, Now_Seconds); Put_Line ("Current year is: " & Year_Number'Image (Now_Year)); Put_Line ("Current month is: " & Month_Number'Image (Now_Month)); Put_Line ("Current day is: " & Day_Number'Image (Now_Day)); end Display_Current_Year;

Here, we're retrieving each element and displaying it separately.

Delaying using date

You can delay an application so that it restarts at a specific date and time. We saw something similar in the chapter on tasking. You do this using a delay until statement. For example:

with Ada.Calendar; use Ada.Calendar; with Ada.Calendar.Formatting; use Ada.Calendar.Formatting; with Ada.Calendar.Time_Zones; use Ada.Calendar.Time_Zones; with Ada.Text_IO; use Ada.Text_IO; procedure Display_Delay_Next_Specific_Time is TZ : Time_Offset := UTC_Time_Offset; Next : Time := Ada.Calendar.Formatting.Time_Of (Year => 2018, Month => 5, Day => 1, Hour => 15, Minute => 0, Second => 0, Sub_Second => 0.0, Leap_Second => False, Time_Zone => TZ); -- Next = 2018-05-01 15:00:00.00 (local time-zone) begin Put_Line ("Let's wait until..."); Put_Line (Image (Next, True, TZ)); delay until Next; Put_Line ("Enough waiting!"); end Display_Delay_Next_Specific_Time;

In this example, we specify the date and time by initializing Next using a call to Time_Of, a function taking the various components of a date (year, month, etc) and returning an element of the Time type. Because the date specified is in the past, the delay until statement won't produce any noticeable effect. However, if we passed a date in the future, the program would wait until that specific date and time arrived.

Here we're converting the time to the local timezone. If we don't specify a timezone, Coordinated Universal Time (abbreviated to UTC) is used by default. By retrieving the time offset to UTC with a call to UTC_Time_Offset from the Ada.Calendar.Time_Zones package, we can initialize TZ and use it in the call to Time_Of. This is all we need do to make the information provided to Time_Of relative to the local time zone.

We could achieve a similar result by initializing Next with a String. We can do this with a call to Value from the Ada.Calendar.Formatting package. This is the modified code:

with Ada.Calendar; use Ada.Calendar; with Ada.Calendar.Formatting; use Ada.Calendar.Formatting; with Ada.Calendar.Time_Zones; use Ada.Calendar.Time_Zones; with Ada.Text_IO; use Ada.Text_IO; procedure Display_Delay_Next_Specific_Time is TZ : Time_Offset := UTC_Time_Offset; Next : Time := Ada.Calendar.Formatting.Value ("2018-05-01 15:00:00.00", TZ); -- Next = 2018-05-01 15:00:00.00 (local time-zone) begin Put_Line ("Let's wait until..."); Put_Line (Image (Next, True, TZ)); delay until Next; Put_Line ("Enough waiting!"); end Display_Delay_Next_Specific_Time;

In this example, we're again using TZ in the call to Value to adjust the input time to the current time zone.

In the examples above, we were delaying to a specific date and time. Just like we saw in the tasking chapter, we could instead specify the delay relative to the current time. For example, we could delay by 5 seconds, using the current time:

with Ada.Calendar; use Ada.Calendar; with Ada.Text_IO; use Ada.Text_IO; procedure Display_Delay_Next is D : Duration := 5.0; -- seconds Now : Time := Clock; Next : Time := Now + D; -- use duration to -- specify next point in time begin Put_Line ("Let's wait " & Duration'Image (D) & " seconds..."); delay until Next; Put_Line ("Enough waiting!"); end Display_Delay_Next;

Here, we're specifying a duration of 5 seconds in D, adding it to the current time from Now, and storing the sum in Next. We then use it in the delay until statement.

Real-time

In addition to Ada.Calendar, the standard library also supports time operations for real-time applications. These are included in the Ada.Real_Time package. This package also include a Time type. However, in the Ada.Real_Time package, the Time type is used to represent an absolute clock and handle a time span. This contrasts with the Ada.Calendar, which uses the Time type to represent dates and times.

In the previous section, we used the Time type from the Ada.Calendar and the delay until statement to delay an application by 5 seconds. We could have used the Ada.Real_Time package instead. Let's modify that example:

with Ada.Text_IO; use Ada.Text_IO; with Ada.Real_Time; use Ada.Real_Time; procedure Display_Delay_Next_Real_Time is D : Time_Span := Seconds (5); Next : Time := Clock + D; begin Put_Line ("Let's wait " & Duration'Image (To_Duration (D)) & " seconds..."); delay until Next; Put_Line ("Enough waiting!"); end Display_Delay_Next_Real_Time;

The main difference is that D is now a variable of type Time_Span, defined in the Ada.Real_Time package. We call the function Seconds to initialize D, but could have gotten a finer granularity by calling Nanoseconds instead. Also, we need to first convert D to the Duration type using To_Duration before we can display it.

Benchmarking

One interesting application using the Ada.Real_Time package is benchmarking. We've used that package before in a previous section when discussing tasking. Let's look at an example of benchmarking:

with Ada.Text_IO; use Ada.Text_IO; with Ada.Real_Time; use Ada.Real_Time; procedure Display_Benchmarking is procedure Computational_Intensive_App is begin delay 5.0; end Computational_Intensive_App; Start_Time, Stop_Time : Time; Elapsed_Time : Time_Span; begin Start_Time := Clock; Computational_Intensive_App; Stop_Time := Clock; Elapsed_Time := Stop_Time - Start_Time; Put_Line ("Elapsed time: " & Duration'Image (To_Duration (Elapsed_Time)) & " seconds"); end Display_Benchmarking;

This example defines a dummy Computational_Intensive_App implemented using a simple delay statement. We initialize Start_Time and Stop_Time from the then-current clock and calculate the elapsed time. By running this program, we see that the time is roughly 5 seconds, which is expected due to the delay statement.

A similar application is benchmarking of CPU time. We can implement this using the Execution_Time package. Let's modify the previous example to measure CPU time:

with Ada.Text_IO; use Ada.Text_IO; with Ada.Real_Time; use Ada.Real_Time; with Ada.Execution_Time; use Ada.Execution_Time; procedure Display_Benchmarking_CPU_Time is procedure Computational_Intensive_App is begin delay 5.0; end Computational_Intensive_App; Start_Time, Stop_Time : CPU_Time; Elapsed_Time : Time_Span; begin Start_Time := Clock; Computational_Intensive_App; Stop_Time := Clock; Elapsed_Time := Stop_Time - Start_Time; Put_Line ("CPU time: " & Duration'Image (To_Duration (Elapsed_Time)) & " seconds"); end Display_Benchmarking_CPU_Time;

In this example, Start_Time and Stop_Time are of type CPU_Time instead of Time. However, we still call the Clock function to initialize both variables and calculate the elapsed time in the same way as before. By running this program, we see that the CPU time is significantly lower than the 5 seconds we've seen before. This is because the delay statement doesn't require much CPU time. The results will be different if we change the implementation of Computational_Intensive_App to use a mathematical functions in a long loop. For example:

with Ada.Text_IO; use Ada.Text_IO; with Ada.Real_Time; use Ada.Real_Time; with Ada.Execution_Time; use Ada.Execution_Time; with Ada.Numerics.Generic_Elementary_Functions; procedure Display_Benchmarking_Math is procedure Computational_Intensive_App is package Funcs is new Ada.Numerics.Generic_Elementary_Functions (Float_Type => Long_Long_Float); use Funcs; X : Long_Long_Float; begin for I in 0 .. 1_000_000 loop X := Tan (Arctan (Tan (Arctan (Tan (Arctan (Tan (Arctan (Tan (Arctan (Tan (Arctan (0.577)))))))))))); end loop; end Computational_Intensive_App; procedure Benchm_Elapsed_Time is Start_Time, Stop_Time : Time; Elapsed_Time : Time_Span; begin Start_Time := Clock; Computational_Intensive_App; Stop_Time := Clock; Elapsed_Time := Stop_Time - Start_Time; Put_Line ("Elapsed time: " & Duration'Image (To_Duration (Elapsed_Time)) & " seconds"); end Benchm_Elapsed_Time; procedure Benchm_CPU_Time is Start_Time, Stop_Time : CPU_Time; Elapsed_Time : Time_Span; begin Start_Time := Clock; Computational_Intensive_App; Stop_Time := Clock; Elapsed_Time := Stop_Time - Start_Time; Put_Line ("CPU time: " & Duration'Image (To_Duration (Elapsed_Time)) & " seconds"); end Benchm_CPU_Time; begin Benchm_Elapsed_Time; Benchm_CPU_Time; end Display_Benchmarking_Math;

Now that our dummy Computational_Intensive_App involves mathematical operations requiring significant CPU time, the measured elapsed and CPU time are much closer to each other than before.