Unixエポックからの経過時間をミリ秒単位で取得する(Windows版)

C言語(Win32 API)を使ってWindowsアプリケーションを開発している時に、Unixエポック(1970年1月1日)からの経過時間を秒単位ではなくミリ秒単位で取得する必要に迫られました。

Unixエポックからの経過時間を秒単位で取得するのであればtime関数を使うだけで済むのですが、ミリ秒単位で取得する場合は少しコードを書く必要がありました。今回は、WindowsでUnixエポックからの経過時間をミリ秒単位で取得する方法を紹介します。

UnixエポックとUnix時間

起点のなる時刻1970-01-01 00:00:00(UTC)をUnixエポックと言います。

似た言葉としてUnix時間があります。Unix時間は「ある時刻をUnixエポックからの経過秒数として表わした数値」です。

たとえば、2018-02-13 19:28:30をUnix時間で表現すると1518550110になります。これは1970-01-01 00:00:00(UTC)から1518550110秒経過した時刻が2018-02-13 19:28:30になるということです。

Windowsの経過時間

WindowsにもUnix時間に類似した“ある時刻を起点として経過時間を数値で表わした時間表現”があります。それがFILETIME構造体です。Windowsではこの時刻の数値表現をFile Times(ファイル時間)と呼びます。

メモ
元々はファイルの作成時刻やアクセス時刻を記録するためのデータ型だったことからFILETIMEという型名になっていますが、FILETIME構造体はファイルを扱わない場面でも良く使われるデータ型です。

Unix時間が1970-01-01 00:00:00(UTC)を起点とした経過秒数であるのに対して、Windowsのファイル時間は1601-01-01 00:00:00(UTC)を起点とした100ナノ秒単位の経過時間を表す数値です。

2018-02-13 19:28:30をWindowsファイル時間で表すと15185501100000000になります。経過時間の単位が100ナノ秒単位なので桁数がずいぶんと多くなりますね。

SystemTimeToFileTime関数を使うとSYSTEMTIME構造体FILETIME構造体に変換することができます。

SYSTEMTIME構造体は年、月、日、時、分、秒をそれぞれ別のメンバーとして保持する構造体です。wYearメンバーを参照すればすぐに年が分かる、wMonthメンバーを参照すればすぐに月が分かる、という扱い易さがあります。しかし、時刻を表現するメンバーが細かく分かれているので、2つの時刻の差を求めるのは少し面倒です。

ファイル時間は単なる数値なので引き算をすれば2つの時刻の差を簡単に求めることができます。(実際には上位32ビットと下位32ビットでメンバーが分かれているので64ビット変数に格納し直す必要はありますが…。)

Windowsファイル時間をUnix時間に変換する

Windowsにも起点からの経過時間を数値で表すデータがあることが分かりました。表現精度も100ナノ秒単位あるので、ミリ秒単位の経過時間に丸めることもできそうです。

それでは現在時刻をUnixエポックからの経過時間(ミリ秒単位)で取得してみましょう。仕組みは簡単です。現在時刻とUnixエポック1970-01-01 00:00:00の2つをファイル時間で表します。現在時刻を表すファイル時間からUnixエポックを表すファイル時間を引けば、1970-01-01 00:00:00からの経過時間(100ナノ秒単位)が手に入ります。これを10000で割ればミリ秒単位になります。

現在時刻を1970-01-01 00:00:00からの経過時間(ミリ秒単位)で取得する関数
/** 現在時刻を1970-01-01 00:00:00からの経過時間(ミリ秒単位)で返します。 */ long long current_time_millis() { //Unixエポック 1970-01-01 00:00:00 を SYSTEMTIME構造体に格納します。 SYSTEMTIME unix_epoch; unix_epoch.wYear = 1970; unix_epoch.wMonth = 1; unix_epoch.wDayOfWeek = 4; unix_epoch.wDay = 1; unix_epoch.wHour = 0; unix_epoch.wMinute = 0; unix_epoch.wSecond = 0; unix_epoch.wMilliseconds = 0; //Unixエポックを格納したSYSTEMTIME構造体をFILETIME構造体に変換します。 FILETIME unix_epoch_ft_from_1601; SystemTimeToFileTime(&unix_epoch, &unix_epoch_ft_from_1601); //Unixエポックを格納したFILETIME構造体を64ビット整数型に変換します。 ULARGE_INTEGER unix_epoch_from_1601; unix_epoch_from_1601.HighPart = unix_epoch_ft_from_1601.dwHighDateTime; unix_epoch_from_1601.LowPart = unix_epoch_ft_from_1601.dwLowDateTime; //現在時刻をFILETIME構造体に格納します。 FILETIME current_ft_from_1601; GetSystemTimeAsFileTime(&current_ft_from_1601); //現在時刻を格納したFILETIME構造体を64ビット整数型に変換します。 ULARGE_INTEGER current_from_1601; current_from_1601.HighPart = current_ft_from_1601.dwHighDateTime; current_from_1601.LowPart = current_ft_from_1601.dwLowDateTime; //現在時刻ファイル時間からUnixエポックファイル時間を引きます。 //これでUnixエポックからの経過時間(100ナノ秒単位)になります。 long long diff = current_from_1601.QuadPart - unix_epoch_from_1601.QuadPart; //10000で割って経過時間(100ナノ秒単位)を経過時間(ミリ秒単位)に丸めます。 long long t = diff / 10000; return t; }

Unixエポックのファイル時間を取得するのにいくつかのステップを踏んでいますが、ファイル時間の起点(‘1601-01-01 00:00:00’)からUnixエポック(‘1970-01-01 00:00:00’)までの経過時間というのは必ず決まった値になりますから実は計算する必要はありません。

上記のUnixエポックを格納した64ビット整数unix_epoch_from_1601.QuadPartは常に同じ値116444736000000000になります。このマジックナンバーを使えばコードは以下のように簡略化できます。

現在時刻を1970-01-01 00:00:00からの経過時間(ミリ秒単位)で取得する関数
/** 現在時刻を1970-01-01 00:00:00からの経過時間(ミリ秒単位)で返します。 */ long long current_time_millis() { long long unix_epoch_from_1601_int64 = 116444736000000000LL; //現在時刻をFILETIME構造体に格納します。 FILETIME current_ft_from_1601; GetSystemTimeAsFileTime(&current_ft_from_1601); //現在時刻を格納したFILETIME構造体を64ビット整数型に変換します。 ULARGE_INTEGER current_from_1601; current_from_1601.HighPart = current_ft_from_1601.dwHighDateTime; current_from_1601.LowPart = current_ft_from_1601.dwLowDateTime; //現在時刻ファイル時間からUnixエポックファイル時間を引きます。 //これでUnixエポックからの経過時間(100ナノ秒単位)になります。 long long diff = current_from_1601.QuadPart - unix_epoch_from_1601_int64; //10000で割って経過時間(100ナノ秒単位)を経過時間(ミリ秒単位)に丸めます。 long long t = diff / 10000; return t; }

64ビット整数(long long)をソースコードにリテラルとして直接記述するために116444736000000000の末尾にLLを付けて116444736000000000LLとしています。

これで、現在時刻や任意の時刻を1970-01-01 00:00:00からの経過ミリ秒数に変換することができました。