UNIX時刻を人間に読みやすい年・月・日・時刻へ変換するとき、もっとも手軽なのがC標準ライブラリのlocaltime
です。
本記事では、現在時刻などのtime_t
からローカルタイムのstruct tm
へ変換する方法を、初心者の方にも分かりやすいように手順と注意点を丁寧に解説します。
C言語のlocaltimeとは?time_t→ローカルtm構造体
localtimeの役割と基本
localtime
は、UNIXエポック(1970-01-01 00:00:00 UTC)からの通算秒数であるtime_t
を、ローカルタイムゾーンにおける「壊れた時刻」(struct tm
)に分解する関数です。
分解とは、年、月、日、時、分、秒、曜日、通算日などの各フィールドに分けることを意味します。
ローカルタイムとは、OSや環境変数の設定で決まる地域の時刻であり、UTCからの時差やサマータイム(DST)の影響を受けます。
なお、UTCに変換したいときはgmtime
を使いますが、これは別記事で扱います。
引数(time_t*)と戻り値
localtime
の宣言は次のとおりです。
// time.h より
struct tm *localtime(const time_t *timer);
引数: time_t
へのポインタです。例えばtime(NULL)
で得た現在時刻を変数に格納し、そのアドレスを渡します。
戻り値: 分解後のstruct tm
へのポインタ。ただし内部の静的領域を指すため、次の呼び出しで上書きされます。失敗時はNULL
が返ります。
ローカルタイムゾーンで解釈
変換結果はシステムのローカルタイムゾーン設定に従って決まるため、同じtime_t
でも、実行環境のTZ(例: Asia/Tokyo、America/New_York)によって日付や時刻が異なることがあります。
サマータイムが採用される地域ではtm_isdst
が正の値になる場合があり、1時間のシフトが生じます。
localtimeの使い方
<time.h>をインクルード
localtime
は<time.h>
で宣言されていますので、まずはヘッダをインクルードします。
#include <time.h>
#include <stdio.h> // 出力のため
time_tを用意する(例: time(NULL))
現在のUNIX時刻を取得するにはtime(NULL)
を使います。
戻り値が(time_t)-1
なら取得失敗です。
time_t now = time(NULL);
if (now == (time_t)-1) {
// 取得失敗の扱い(ディスクフルやクロック未設定など、実装依存で失敗することがあります)
fprintf(stderr, "time() に失敗しました。\n");
return 1;
}
localtimeでtm*を取得
localtime
にtime_t
のアドレスを渡してstruct tm
ポインタを得ます。
必ずNULL
チェックを行います。
struct tm *lt = localtime(&now);
if (lt == NULL) {
fprintf(stderr, "localtime() に失敗しました。\n");
return 1;
}
tmから年月日・時分秒を参照
struct tm
の主なフィールドは以下のとおりです。
月や年の基準に注意してください。
フィールド | 意味 | 注意点 |
---|---|---|
tm_year | 1900年からの年数 | 表示にはtm_year + 1900 |
tm_mon | 月(0〜11) | 表示にはtm_mon + 1 。0始まりに注意 |
tm_mday | 日(1〜31) | その月の日 |
tm_hour | 時(0〜23) | 24時間制 |
tm_min | 分(0〜59) | |
tm_sec | 秒(0〜60) | うるう秒で60になる可能性あり |
tm_wday | 曜日(0〜6) | 0=日曜 |
tm_yday | 年内通算日(0〜365) | 0が1月1日 |
tm_isdst | サマータイム指示 | 正の値=適用、0=非適用、負=不明 |
表示用には年と月の補正が必須です。
サンプルコード(現在時刻をローカル時刻に変換して表示)
#include <stdio.h>
#include <time.h>
int main(void) {
// 1) 現在のUNIX時刻を取得
time_t now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "time() に失敗しました。\n");
return 1;
}
// 2) ローカルタイムへ分解
struct tm *lt = localtime(&now);
if (lt == NULL) {
fprintf(stderr, "localtime() に失敗しました。\n");
return 1;
}
// 3) 表示のための補正
int year = lt->tm_year + 1900; // 1900年基準
int month = lt->tm_mon + 1; // 0始まりなので +1
int day = lt->tm_mday;
int hour = lt->tm_hour;
int min = lt->tm_min;
int sec = lt->tm_sec;
const char *wday_name[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
// 4) 整形して出力
printf("Local time: %04d-%02d-%02d %02d:%02d:%02d (%s), DST=%d\n",
year, month, day, hour, min, sec,
wday_name[lt->tm_wday],
lt->tm_isdst);
return 0;
}
Local time: 2025-10-16 13:45:09 (Thu), DST=0
このようにlocaltime
で得たtm
から必要なフィールドを取り出し、年と月を補正して表示します。
戻り値NULLのチェック
localtime
は失敗時にNULL
を返すため、必ず判定してから参照する必要があります。
失敗の理由は実装依存ですが、極端な時刻やタイムゾーン情報が扱えない場合などが考えられます。
例外やクラッシュを防ぐためにも防御的に書きましょう。
struct tm *lt = localtime(&now);
if (lt == NULL) {
// フォールバックやエラーログを行う
// ここで errno が設定される保証は規格上ありません
fprintf(stderr, "localtime() が NULL を返しました。\n");
// 代替: UTCにしたい場合は gmtime() を試すなど(別記事)
return 1;
}
localtimeの注意点
返されるtmは上書きされる
localtime
が返すstruct tm*
は静的領域を指し、次の呼び出しで上書きされます。
必要ならユーザー側のstruct tm
変数へ内容をコピーして保持します。
上書きの例と対策
#include <stdio.h>
#include <time.h>
#include <string.h>
int main(void) {
time_t t1 = 0; // 1970-01-01 00:00:00 UTC
time_t t2 = 24 * 60 * 60; // 1970-01-02 00:00:00 UTC (翌日)
struct tm *p1 = localtime(&t1); // 静的領域Aを指す
struct tm *p2 = localtime(&t2); // 同じ静的領域Aが上書きされる可能性大
// p1 と p2 は同じアドレスを指すことが多い
printf("p1=%p, p2=%p\n", (void*)p1, (void*)p2);
// 対策: 内容をコピーしておく
struct tm copy1;
if (p1 != NULL) {
// p1 は上書き済みの可能性があるので、実際には呼び出し直後にコピーすべきです
copy1 = *localtime(&t1); // 呼び出し直後に即コピーが安全
}
// 安全に参照
printf("copy1.tm_mday=%d\n", copy1.tm_mday);
return 0;
}
例としてアドレスが同一になることが多く、二度目の呼び出しで一度目の内容が失われます。
必要な内容は呼び出し直後に構造体へコピーするのが基本です。
マルチスレッド環境ではさらに危険です。
同一プロセス内の他スレッドがlocaltime
を呼ぶと、静的領域が競合します。
スレッドセーフな代替としてlocaltime_r
(POSIX)やlocaltime_s
(MSVC系)を使用してください。
スレッドセーフな代替の例
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
#if defined(_WIN32) && !defined(__MINGW32__)
// MSVC系: localtime_s(dest, src)
struct tm lt;
if (localtime_s(<, &now) != 0) {
fprintf(stderr, "localtime_s() に失敗しました。\n");
return 1;
}
#elif defined(_POSIX_VERSION)
// POSIX: localtime_r(src, dest)
struct tm lt;
if (localtime_r(&now, <) == NULL) {
fprintf(stderr, "localtime_r() に失敗しました。\n");
return 1;
}
#else
// ポータブルでないが、最後の手段: 戻り値を即コピー
struct tm *p = localtime(&now);
if (p == NULL) {
fprintf(stderr, "localtime() に失敗しました。\n");
return 1;
}
struct tm lt = *p; // 即コピー
#endif
printf("Local: %04d-%02d-%02d %02d:%02d:%02d\n",
lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
lt.tm_hour, lt.tm_min, lt.tm_sec);
return 0;
}
タイムゾーンの影響
出力結果はTZやサマータイム設定に大きく依存します。
OSの地域設定や環境変数TZ
の値を変えると、localtime
の結果も変化します。
POSIX環境ではTZ
を設定しtzset
を呼ぶと反映できます(非標準)。
Windowsでは_putenv_s
と_tzset
を用います。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main(void) {
time_t now = time(NULL);
#if defined(_WIN32) && !defined(__MINGW32__)
// Windows
_putenv_s("TZ", "UTC"); // 例: UTCに設定
_tzset();
#else
// POSIX系
setenv("TZ", "UTC", 1); // 例: UTCに設定。例: "Asia/Tokyo", "America/New_York"
tzset();
#endif
struct tm *ut = localtime(&now);
if (ut) {
printf("As UTC: %04d-%02d-%02d %02d:%02d:%02d\n",
ut->tm_year + 1900, ut->tm_mon + 1, ut->tm_mday,
ut->tm_hour, ut->tm_min, ut->tm_sec);
}
return 0;
}
上記APIは規格外や実装依存を含むため、移植性が重要なコードでは利用可否を検討してください。
タイムゾーンDBが異なるプラットフォーム間では、歴史的なタイムゾーン変更やDSTルールの差で結果が微妙に変わることがあります。
連続呼び出し時の注意点
連続でlocaltime
を呼ぶと、前回の結果が上書きされる点に注意します。
複数の時刻を同時に保持したい場合は、各回の戻り値をただのポインタとして持たず、struct tm
に即コピーするか、localtime_r
/localtime_s
で呼び出しごとに別のバッファを使います。
マルチスレッドでは必ずスレッドセーフ版を選ぶのが安全です。
エラー時(NULL)の扱い
localtime
がNULL
を返したら、そのポインタを参照しないでください。
ログに時刻値や環境情報を記録し、必要ならば
- 代替として
gmtime
でUTCに変換して進める - 固定フォーマットのエラーメッセージで代替表示する : といったフォールバックを検討します。規格上、
errno
が常に設定される保証はありませんので、戻り値による明示的な判定が第一です。
エラー判定を含む堅牢な例
#include <stdio.h>
#include <time.h>
int main(void) {
time_t t = time(NULL);
if (t == (time_t)-1) {
fprintf(stderr, "time() 失敗\n");
return 1;
}
struct tm *lt = localtime(&t);
if (!lt) {
// フォールバック: UTCで良ければ gmtime()
struct tm *gt = gmtime(&t);
if (gt) {
printf("UTC fallback: %04d-%02d-%02d %02d:%02d:%02d\n",
gt->tm_year + 1900, gt->tm_mon + 1, gt->tm_mday,
gt->tm_hour, gt->tm_min, gt->tm_sec);
} else {
fprintf(stderr, "localtime() と gmtime() の両方が失敗\n");
}
return 0;
}
printf("Local ok: %04d-%02d-%02d %02d:%02d:%02d\n",
lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
lt->tm_hour, lt->tm_min, lt->tm_sec);
return 0;
}
Local ok: 2025-10-16 13:45:09
堅牢にするには、各ステップで戻り値を確認し、使う直前に必ずチェックすることが大切です。
まとめ
localtime
はtime_t
をローカルタイムのstruct tm
へ分解する基本関数で、年は+1900、月は+1の補正が必要です。
戻り値は静的領域で上書きされるため、即コピーかlocaltime_r
/localtime_s
を使うことで安全性を高められます。
タイムゾーンとサマータイムの影響により結果が環境依存になる点も重要です。
最後に、NULLチェックを怠らない、という基本を守れば、日付時刻の扱いを信頼できるものにできます。