Pull to refresh

Standard Time как его видит IBM

Level of difficultyEasy
Reading time3 min
Views3.1K

Есть многое в природе, друг Горацио, Что и не снилось нашим мудрецам.(с) Вильям Шекспир


Более 6-ти лет занимаюсь разработкой под IBM i (бывшая AS/400). В основном, конечно, это работа с БД и разная бизнес-логика, но иногда приходится и что-то низкоуровневое писать.

Не так давно занимался разработкой удобного и простого в использовании API для работы с User Queue (есть на АС-ке такой системный объект - очередь *USRQ). И в заголовке извлекаемого из очереди сообщения есть такой параметр - message enqueue time (время размещения сообщения в очереди). Который описан как _MI_Time. Которое, в свою очередь, определено как

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

MI в данном случае - это Machine Instructions. То, что на АС-ке вместо ассемблера (на верхнем уровне). Набор низкоуровневых команд для различных операций. Для многих MI в С-шной библиотеке есть соответствующие врапперы. В частности, этот самый _MI_Time можно получить функцией

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

Или более универсальной (и, как ни странно, более быстрой)

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

с соответствующим флагом в поле Options.

Но вот вопрос - а что это такое и что с ним делать? Как получить из этого какое-то осмысленное время?

Есть, конечно, несколько системных API, конвертирующих это время во что-то более удобоваримое, но они не слишком производительны (когда, к примеру, вызываешь их 1 000 000 и более раз). Ну и любопытно же...

Документация от IBM это вам не MSDN где все полочкам и с примерами... Тут вдумчивость нужна. И творческий подход.

В конечном итоге удалось установить, что в документации это называется Standard Time и представляет из себя количество микросекунд (да-да-да, именно микросекунд) от некоего начала эпохи. И никакой это не char[8], а вполне себе нормальный uint64.

А вот начало эпохи... Попробуйте угадать :-)

Hidden text

23/08/1928 12:03:06.314752

На всякий случай прописью - 23-е августа 1928-го года, 12 часов, 3 минуты, 6 секунд и еще 314752 микросекунды...

Как говорится, "что курили?"

И нет там никакой "проблемы 2038" как в остальном мире.

Hidden text

Зато есть проблема 08/06/2062 16:27:16.974591

Истину вам говорю - 8-го июня 2062-го года, ровно в 16 часов, 27 минут, 16 секунд и 974591 микросекунду Земля налетит на небесную ось...

Правда, если попытаться все-таки найти хоть какую-то логику, то внезапно становится понятно, что "эпоха" тут привязана не к началу или концу, а к середине. Ибо значение 0x8000000000000000 тут соответствует началу тысячелетия - 01/01/2000 00:00:00.000000

Ну и еще одна тонкость - для хранения собственно времени используется только 52 старших бита. А 12 младших - это т.н. "биты уникальности" которые используются (по задумке IBM) для создания уникальных меток с привязкой ко времени (с точностью до микросекунды). Т.е. "дергая" несколько раз это самое время даже в течении микросекунды, вы каждый раз будете получать уникальное значение, содержащее время и "сиквенс" - номер "внутри" микросекунды. В целом - красивое решение (если где-то потребуется).

К слову сказать, для matmdata определено несколько опций, возвращающих это самое время - локальное или UTC, с битами уникальности или без (всегда нули)

     #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*/

(там есть еще набор опций, но ко времени они не относятся).

Теперь становится понятно что это такое и как из всего этого извлечь какую-то пользу. Например, перевести в привычный UNIXTIME.

Магическим числом тут будет 0x4A2FEC4C82000000 - значение _MI_Time, соответствующее началу UNIX эпохи - 01/01/1970 00:00:00.000000. А дальше просто:

#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);

Получаем результат, идентичный тому, что возвращает gettimeofday() Но быстрее - 100 000 последовательных вызовов gettimeofday занимает 0.301682 сек, а тот же результат при помощи matmdata - 0.011416 сек в одних и тех же условиях.

Воистину - на AS/400 все не как у людей...

Tags:
Hubs:
Total votes 18: ↑17 and ↓1+23
Comments29

Articles