Есть многое в природе, друг Горацио, Что и не снилось нашим мудрецам.(с) Вильям Шекспир
Более 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 все не как у людей...