Introduction

While messing around with Ada.Calendar, a need to output the return value of Ada.Calendar.Clock as a String, took me on an adventure in the land of pragma Assert, the 'Address attribute, the 'Size attribute, Ada.Unchecked_Conversion and the fact that not all Ada compilers are created equal.

Caveat

The following are examples of how to do something. It's not meant to be used in any real capacity. If you need a portable String representation of Ada.Calendar.Clock, then you're better off using the many nice features of Ada.Calendar.Formatting.

The Problem

The problem of outputting the return value from Ada.Calendar.Clock as a string is one of privacy. The returned type, Time, is declared private. Also it's implementation defined:

The time base associated with the type Time of package Calendar is implementation defined. The function Clock of package Calendar returns a value representing the current time for this time base. The implementation-defined value of the named number System.Tick (see 13.7) is an approximation of the length of the real-time interval during which the value of Calendar.Clock remains constant.

So we can't see it, and we don't know how it's implemented. But there are ways around these issues.

One Solution

In this first solution, we're going to make use of the 'Address attribute. For my experiment I've used the GNAT GPL 2010 compiler, and in this compiler the Time type is declared as:

type Time_Rep is range -2 ** 63 .. +2 ** 63 - 1;
type Time is new Time_Rep;

A big number indeed. A 64 bit signed integer. Knowing this, we can create a function that converts a 64 bit Time type to a String. Or rather we can fake a conversion. This is how it's done:

function Time_Image_One
  (Item : in Ada.Calendar.Time)
   return String
is
   type New_Time is mod 2 ** 64;
   pragma Assert (New_Time'Size = Item'Size);
 
   Time_Stamp : New_Time;
   for Time_Stamp'Address use Item'Address;
begin
   return Trim (Source => New_Time'Image (Time_Stamp),
                Side   => Ada.Strings.Left);
end Time_Image_One;

Amazingly simple, yes?

Lets take a closer look at the inner workings of the Time_Image function:

type New_Time is mod 2 ** 64;

We declare a new Time type with the same size as the Ada.Calendar.Time type (as defined by the GNAT GPL 2010 compiler). I've used mod 2 ** 64 instead of range -2 ** 63 .. +2 ** 63 -1, but that's mostly because I don't care about the sign. You can just as well go with range -2 ** 63 .. +2 ** 63 -1. The important thing to bear in mind, is that the two types should have the same size. Which brings us to this:

pragma Assert (New_Time'Size = Item'Size);

Here we assert that the two types are of equal size. If for some reason they don't match, an Assertion_Error exception is raised. It bears mentioning that the behavior of pragma Assert depends on the pragma Assertion_Policy setting.

So while the Time declaration destroys any hope of portability between compilers and systems, the pragma Assert at least makes sure we can catch any problems and act accordingly.

Next we have:

Time_Stamp : New_Time;
for Time_Stamp'Address use Item'Address;

This is where the “magic” happens. We declare the Time_Stamp variable and then immediately thereafter we tell the program to use the machine address of the Item object for the Time_Stamp object. What this basically means, is that when we read the Time_Stamp variable, we're actually reading the contents of the Item object.

Outputting the String representation of Time_Stamp is now a simple matter of using the 'Image attribute and the Trim function (to pretty it up):

return Trim (Source => New_Time'Image (Time_Stamp),
             Side   => Ada.Strings.Left);

And voila! We have a neat String representation of the Ada.Calendar.Clock return value. Whether or not this is useful for anything, well, I'll leave that decision to the reader.

Another Solution

The above solution is not the only way to accomplish the goal of outputting the return value of Ada.Calendar.Clock. There's another, and probably better, method: Using Ada.Unchecked_Conversion. Here's how the above Time_Image_One function would look if we used Ada.Unchecked_Conversion instead:

function Time_Image_Two
  (Item : in Ada.Calendar.Time)
   return String
is
   type New_Time is mod 2 ** 64;
   pragma Assert (New_Time'Size = Ada.Calendar.Time'Size);
 
   function To_Time
   is new Ada.Unchecked_Conversion (Source => Ada.Calendar.Time, 
                                    Target => New_Time);
 
   Time_Stamp : constant New_Time := To_Time (Item);
begin
   return Trim (Source => New_Time'Image (Time_Stamp),
                Side   => Ada.Strings.Left);
end Time_Image_Two;

A great boon of this method over the first one, is this fantastic warning message the GNAT compiler will dump on you when you compile the program:

warning: representation of Time values may change between GNAT versions

You're not going to get that with the first method, and in my humble opinion that warning alone makes it worthwhile to use Ada.Unchecked_Conversion instead of the 'Address attribute used in the first solution.

The Full Program

Lets end with a little program that utilizes both solutions to output the value of Ada.Calendar.Clock:

with Ada.Calendar;
with Ada.Strings.Fixed;
with Ada.Text_IO;
with Ada.Unchecked_Conversion;
 
procedure Time_Image_Test is
   use Ada.Strings.Fixed;
   use Ada.Text_IO;
 
   ----------------------
   --  Time_Image_One  --
   ----------------------
 
   function Time_Image_One
     (Item : in Ada.Calendar.Time)
      return String
   is
      type New_Time is mod 2 ** 64;
      pragma Assert (New_Time'Size = Item'Size);
 
      Time_Stamp : New_Time;
      for Time_Stamp'Address use Item'Address;
   begin
      return Trim (Source => New_Time'Image (Time_Stamp),
                   Side   => Ada.Strings.Left);
   end Time_Image_One;
 
   ----------------------
   --  Time_Image_Two  --
   ----------------------
 
   function Time_Image_Two
     (Item : in Ada.Calendar.Time)
      return String
   is
      type New_Time is mod 2 ** 64;
      pragma Assert (New_Time'Size = Ada.Calendar.Time'Size);
 
      function To_Time
      is new Ada.Unchecked_Conversion (Source => Ada.Calendar.Time, 
                                       Target => New_Time);
 
      Time_Stamp : constant New_Time := To_Time (Item);
   begin
      return Trim (Source => New_Time'Image (Time_Stamp),
                   Side   => Ada.Strings.Left);
   end Time_Image_Two;
 
   Now : Ada.Calendar.Time := Ada.Calendar.Clock;
begin
   Put_Line (Time_Image_One (Now));
   Put_Line (Time_Image_Two (Now));
end Time_Image_Test;

Elegant stuff!

Thanks

I'd like to extend my thanks to Jacob Sparre for gently pushing me in the right direction in regards to solution one, and Simon J. Wright for giving me the second solution. As usual the freenode #ada IRC channel and the identi.ca Ada group proved an invaluable help in learning something new.


Navigation