閉じる

【C言語】localtimeでtime_tをローカルtmに変換する方法

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の宣言は次のとおりです。

C言語
// 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>で宣言されていますので、まずはヘッダをインクルードします。

C言語
#include <time.h>
#include <stdio.h>  // 出力のため

time_tを用意する(例: time(NULL))

現在のUNIX時刻を取得するにはtime(NULL)を使います。

戻り値が(time_t)-1なら取得失敗です。

C言語
time_t now = time(NULL);
if (now == (time_t)-1) {
    // 取得失敗の扱い(ディスクフルやクロック未設定など、実装依存で失敗することがあります)
    fprintf(stderr, "time() に失敗しました。\n");
    return 1;
}

localtimeでtm*を取得

localtimetime_tのアドレスを渡してstruct tmポインタを得ます。

必ずNULLチェックを行います

C言語
struct tm *lt = localtime(&now);
if (lt == NULL) {
    fprintf(stderr, "localtime() に失敗しました。\n");
    return 1;
}

tmから年月日・時分秒を参照

struct tmの主なフィールドは以下のとおりです。

月や年の基準に注意してください。

フィールド意味注意点
tm_year1900年からの年数表示にはtm_year + 1900
tm_mon月(0〜11)表示にはtm_mon + 10始まりに注意
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=非適用、負=不明

表示用には年と月の補正が必須です。

サンプルコード(現在時刻をローカル時刻に変換して表示)

C言語
#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を返すため、必ず判定してから参照する必要があります。

失敗の理由は実装依存ですが、極端な時刻やタイムゾーン情報が扱えない場合などが考えられます。

例外やクラッシュを防ぐためにも防御的に書きましょう

C言語
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変数へ内容をコピーして保持します。

上書きの例と対策

C言語
#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系)を使用してください。

スレッドセーフな代替の例

C言語
#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(&lt, &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, &lt) == 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を用います。

C言語
#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)の扱い

localtimeNULLを返したら、そのポインタを参照しないでください。

ログに時刻値や環境情報を記録し、必要ならば

  • 代替としてgmtimeでUTCに変換して進める
  • 固定フォーマットのエラーメッセージで代替表示する : といったフォールバックを検討します。規格上、errnoが常に設定される保証はありませんので、戻り値による明示的な判定が第一です。

エラー判定を含む堅牢な例

C言語
#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

堅牢にするには、各ステップで戻り値を確認し、使う直前に必ずチェックすることが大切です

まとめ

localtimetime_tをローカルタイムのstruct tmへ分解する基本関数で、年は+1900、月は+1の補正が必要です。

戻り値は静的領域で上書きされるため、即コピーlocaltime_r/localtime_sを使うことで安全性を高められます。

タイムゾーンとサマータイムの影響により結果が環境依存になる点も重要です。

最後に、NULLチェックを怠らない、という基本を守れば、日付時刻の扱いを信頼できるものにできます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!