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.Text_IO; use Ada.Text_IO; with Ada.Calendar; use Ada.Calendar; with Ada.Calendar.Formatting; use Ada.Calendar.Formatting; 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.Text_IO; use Ada.Text_IO; with Ada.Calendar; use Ada.Calendar; 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.Text_IO; use Ada.Text_IO; with Ada.Calendar; use Ada.Calendar; with Ada.Calendar.Formatting; use Ada.Calendar.Formatting; with Ada.Calendar.Time_Zones; use Ada.Calendar.Time_Zones; 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.Text_IO; use Ada.Text_IO; with Ada.Calendar; use Ada.Calendar; with Ada.Calendar.Formatting; use Ada.Calendar.Formatting; with Ada.Calendar.Time_Zones; use Ada.Calendar.Time_Zones; 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 function 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.