Standard Time as IBM sees it


There is much in nature, friend Horatio, That our sages never dreamed of. (c) William Shakespeare


I have been developing for IBM i (formerly AS/400) for more than 6 years. Basically, of course, this involves working with the database and various business logic, but sometimes you have to write something low-level.

Not long ago I was developing a convenient and easy-to-use API for working with User Queue (there is such a system object on the AS – the *USRQ queue). And in the header of the message retrieved from the queue there is such a parameter – message enqueue time (the time the message was placed in the queue). Which is described as _MI_Time. Which, in turn, is defined as

    /* The standard definition for time in the MI library:           */
    typedef char _MI_Time[8];

MI in this case is Machine Instructions. What is on AS instead of assembler (at the top level). A set of low-level commands for various operations. For many MIs, the C library has corresponding wrappers. In particular, this same _MI_Time can be obtained using the function

    /* Materialize Time of Day   */
    void mattod  ( _MI_Time );           /* Time-of-day template       */

Or more universal (and, oddly enough, faster)

   /* Materialize Machine Data  */
   void matmdata ( _SPCPTR,             /* Machine data template  @A3C*/
                   short );             /* Options                    */

with the corresponding flag in the Options field.

But the question is – what is it and what to do with it? How do you get some meaningful time out of this?

There are, of course, several system APIs that convert this time into something more digestible, but they are not very productive (when, for example, you call them 1,000,000 or more times). Well, that's interesting…

Documentation from IBM is not MSDN where everything is on the shelves and with examples… Thoughtfulness is needed here. And a creative approach.

Ultimately, we managed to establish that in the documentation this is called Standard Time and represents the number of microseconds (yes, yes, yes, exactly microseconds) from some beginning of the epoch. And this is not char[8]but quite a normal uint64.

But the beginning of an era… Try to guess 🙂

Hidden text

08/23/1928 12:03:06.314752

Just in case, in words – August 23, 1928, 12 hours, 3 minutes, 6 seconds and another 314752 microseconds…

As they say, “what were you smoking?”

And there is no “2038 problem” like in the rest of the world.

Hidden text

But there is a problem 06/08/2062 16:27:16.974591

I tell you the truth – on June 8, 2062, at exactly 16 hours, 27 minutes, 16 seconds and 974591 microseconds, the Earth will fly onto the celestial axis…

True, if you try to find at least some logic, then it suddenly becomes clear that the “epoch” here is tied not to the beginning or end, but to the middle. Because the value 0x8000000000000000 here corresponds to the beginning of the millennium – 01/01/2000 00:00:00.000000

Well, one more subtlety – only 52 high-order bits are used to store the time itself. And the 12 youngest are the so-called. “uniqueness bits” which are used (as conceived by IBM) to create unique time-stamped marks (with microsecond accuracy). Those. “pulling” this same time several times, even within a microsecond, each time you will receive a unique value containing the time and “sequence” – the number “inside” the microsecond. Overall, it’s a nice solution (if needed somewhere).

By the way, several options are defined for matmdata that return this same time – local or UTC, with or without uniqueness bits (always zeros)

     #define _MDATA_CLOCK                 0x0000
     #define _MDATA_CLOCK_UTC             0x0004  /*              @A3A*/
     #define _MDATA_CLOCK_NOT_UNIQUE      0x0007  /*              @A6A*/
     #define _MDATA_CLOCK_UTC_NOT_UNIQUE  0x0008  /*              @A6A*/

(there is also a set of options, but they do not relate to time).

Now it becomes clear what it is and how to get some benefit from all this. For example, convert to the usual UNIXTIME.

The magic number here will be 0x4A2FEC4C82000000 – the _MI_Time value corresponding to the beginning of the UNIX epoch – 01/01/1970 00:00:00.000000. And then it’s simple:

#define IBM_EPOCH 0x4A2FEC4C82000000ULL
#define MKSECS    1000000ULL

  unsigned long long tod;
  unsigned           sec, usec;

  matmdata(&tod, _MDATA_CLOCK_UTC_NOT_UNIQUE);
  
  // приводим к UTC
  tod -= IBM_EPOCH;
  // убираем биты уникальности
  tod >>= 12;

  sec  = (unsigned)(tod / MKSECS);
  usec = (unsigned)(tod % MKSECS);

We get a result identical to what is returned gettimeofday() But it's faster – 100,000 consecutive calls to gettimeofday takes 0.301682 seconds, and the same result using matmdata takes 0.011416 seconds under the same conditions.

Truly, on the AS/400 everything is not like with people…

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *