閉じる

C言語のgmtimeでUTCに変換する方法とstruct tmの基本

UTC(協定世界時)は世界共通の基準時刻です。

C言語では、UNIX時刻(time_t)をUTCの要素別の時刻(struct tm)に分解するためにgmtimeを使います。

この記事では、初心者の方でも迷わないようにgmtimeの基本、struct tmの読み方、注意点、そして最小コードまでを一気に解説します

gmtimeとは?UTC(協定世界時)とstruct tmの基本

gmtimeの役割

gmtimeは、UNIX時刻を表すtime_tを入力すると、UTCでの年・月・日・時・分・秒に分解したstruct tmへのポインタを返す関数です。

つまり「秒のカウント」を「人が読める形」に直す関数で、出力は必ずUTC基準になります

この関数は#include <time.h>で宣言されています。

ポイントとして、同様に分解するlocaltimeはローカルタイムゾーンに従いますが、gmtimeは常にUTCである点が異なります。

UTCの意味とメリット

UTCは世界標準時であり、タイムゾーンや夏時間(DST)の影響を受けません。

ログやデータベースでの記録、異なる地域間での時刻の共有では、基準がぶれないUTCが最も扱いやすいのです

アプリ内表示は地域のローカル時刻にしても、内部データやAPIではUTCで統一する設計がよく採用されます。

struct tmの主なフィールド

struct tmは要素別の時刻情報を格納する構造体です。

主なフィールドは次の通りです。

フィールド範囲の目安説明
tm_yearint1900基準の年差実年は 1900 + tm_year
tm_monint0〜11実月は 1〜12に変換が必要
tm_mdayint1〜31
tm_hourint0〜23
tm_minint0〜59
tm_secint0〜60秒(うるう秒で60になることあり)
tm_wdayint0〜6曜日 0:日 1:月 … 6:土
tm_ydayint0〜365年内の通し日 0:1月1日
tm_isdstint負/0/正夏時間情報(UTCでは普通-1または0)

最もよくつまずくのがtm_yeartm_monの基準です

年と月はそのまま表示せず、後述の基準に沿って変換してください。

tm_yearとtm_monの基準

tm_yearは「1900年からの年数差」、tm_monは「0始まりの月番号」です。

人が読む年と月を得るには、年はtm_year + 1900、月はtm_mon + 1に変換します

たとえばtm_year == 124なら実年は2024年、tm_mon == 0なら1月です。

C言語でのgmtimeの使い方

必要なヘッダと型

gmtime#include <time.h>で利用できます。

時刻を表すtime_tと分解結果のstruct tmを扱うため、表示には#include <stdio.h>も併用します。

基本の型はtime_tstruct tm、関数はtimegmtimeの組み合わせを覚えておきましょう

現在時刻をUTCに変換する

以下は、現在のUNIX時刻を取得し、UTCのstruct tmに分解して表示する例です。

ゼロ埋めで「YYYY-MM-DD HH:MM:SS UTC」の形式に整形しています

C言語
#include <stdio.h>   // printf
#include <time.h>    // time, time_t, gmtime, struct tm

int main(void) {
    // 現在のUNIX時刻(UTC起点の経過秒)を取得
    time_t now = time(NULL);
    if (now == (time_t)-1) {
        // 取得失敗(非常にまれ)
        fprintf(stderr, "time() failed\n");
        return 1;
    }

    // gmtimeでUTCの要素別時刻に分解(静的領域へのポインタが返る点に注意)
    struct tm *utc = gmtime(&now);
    if (utc == NULL) {
        // 変換できない(範囲外など)場合
        fprintf(stderr, "gmtime() failed\n");
        return 1;
    }

    // 年と月は基準に合わせて補正する
    int year = utc->tm_year + 1900;
    int mon  = utc->tm_mon + 1;

    // ゼロ埋めで読みやすく表示
    printf("%04d-%02d-%02d %02d:%02d:%02d UTC\n",
           year, mon, utc->tm_mday, utc->tm_hour, utc->tm_min, utc->tm_sec);
    return 0;
}
実行結果
2025-05-20 18:42:03 UTC

この手順だけで「今の時間をUTCで」得られます

アプリ内部やログでの標準化に向いています。

任意のtime_tをUTCのstruct tmに変換

現在時刻だけでなく、任意のtime_t値をUTCで分解できます。

たとえばUNIXエポック(0秒)や既知のタイムスタンプを使うと、動作確認が容易です

C言語
#include <stdio.h>
#include <time.h>

static void print_utc(time_t t) {
    struct tm *utc = gmtime(&t);
    if (!utc) {
        printf("gmtime() failed for t=%lld\n", (long long)t);
        return;
    }
    printf("t=%lld -> %04d-%02d-%02d %02d:%02d:%02d UTC\n",
           (long long)t,
           utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday,
           utc->tm_hour, utc->tm_min, utc->tm_sec);
}

int main(void) {
    // 代表例: UNIXエポック(1970-01-01 00:00:00 UTC)
    time_t t0 = 0;

    // 例: 2021-01-01 00:00:00 UTC は 1609459200
    time_t t1 = 1609459200;

    print_utc(t0);
    print_utc(t1);
    return 0;
}
実行結果
t=0 -> 1970-01-01 00:00:00 UTC
t=1609459200 -> 2021-01-01 00:00:00 UTC

固定のtime_tを使うと、テストが再現可能になります

日付処理のユニットテストでも有用です。

取得したstruct tmの値の読み方

struct tmは前述の通り基準が独特です。

表示時はtm_year + 1900tm_mon + 1を忘れないことが最重要ポイントです

曜日はtm_wdayで0(日)〜6(土)、年内通し日はtm_ydayで0が1月1日です。

C言語
#include <stdio.h>
#include <time.h>

int main(void) {
    time_t now = time(NULL);
    struct tm *utc = gmtime(&now);
    if (!utc) return 1;

    int year = utc->tm_year + 1900;
    int mon  = utc->tm_mon + 1;
    int day_of_week = utc->tm_wday; // 0:日, 1:月, ... 6:土
    int day_of_year = utc->tm_yday; // 0:1月1日

    printf("年=%d, 月=%d, 日=%d, 時=%d, 分=%d, 秒=%d, 曜日=%d, 年内通し日=%d\n",
           year, mon, utc->tm_mday, utc->tm_hour, utc->tm_min, utc->tm_sec,
           day_of_week, day_of_year);
    return 0;
}
実行結果
年=2025, 月=05, 日=20, 時=18, 分=42, 秒=03, 曜日=2, 年内通し日=139

曜日と通し日は数値の基準が0始まりなので、必要なら自前の配列で文字列化すると分かりやすくなります

gmtimeの注意点とよくあるミス

返り値は共有のメモリ

gmtimeは内部の静的領域へのポインタを返します。

次のgmtimelocaltimeの呼び出しで内容が上書きされるため、そのままポインタを長く保持してはいけません

必要ならすぐに値をコピーしましょう。

悪い例良い例を続けて示します。

C言語
#include <stdio.h>
#include <time.h>

int main(void) {
    time_t t1 = 0;             // 1970-01-01
    time_t t2 = 1609459200;    // 2021-01-01

    // 悪い例: どちらも同じ静的領域を指し、2回目の呼び出しで上書きされる
    struct tm *p1 = gmtime(&t1);
    struct tm *p2 = gmtime(&t2);
    // ここでp1の内容はp2と同じになっている可能性が高い
    printf("悪い例: p1=%04d-%02d-%02d, p2=%04d-%02d-%02d\n",
           p1->tm_year + 1900, p1->tm_mon + 1, p1->tm_mday,
           p2->tm_year + 1900, p2->tm_mon + 1, p2->tm_mday);

    // 良い例: すぐに値コピーして独立した構造体として保持する
    struct tm a = *gmtime(&t1);
    struct tm b = *gmtime(&t2);
    printf("良い例: a=%04d-%02d-%02d, b=%04d-%02d-%02d\n",
           a.tm_year + 1900, a.tm_mon + 1, a.tm_mday,
           b.tm_year + 1900, b.tm_mon + 1, b.tm_mday);
    return 0;
}
実行結果
悪い例: p1=2021-01-01, p2=2021-01-01
良い例: a=1970-01-01, b=2021-01-01

マルチスレッドではスレッド安全性にも注意が必要です

POSIX環境ではgmtime_r、一部環境ではgmtime_sが用意されていますが、いずれも標準Cの必須仕様ではありません。

ポータブルに済ませるなら上記のように即座に値コピーするのが安全です。

返り値がNULLのときの扱い

gmtimeは、引数のtime_tが実装の扱える範囲外などの場合にNULLを返します。

戻り値のヌルチェックは必ず行い、エラーメッセージや代替処理を入れておきましょう

C言語
#include <stdio.h>
#include <time.h>
#include <errno.h>

int main(void) {
    // かなり大きな値(環境により扱えない可能性あり)
    time_t t = (time_t)9e18;

    struct tm *utc = gmtime(&t);
    if (utc == NULL) {
        // gmtimeはerrnoを必ず設定するとは限らないので、表示は参考程度
        fprintf(stderr, "gmtime() failed for t=%lld (errno=%d)\n", (long long)t, errno);
        return 1;
    }
    printf("%04d-%02d-%02d %02d:%02d:%02d UTC\n",
           utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday,
           utc->tm_hour, utc->tm_min, utc->tm_sec);
    return 0;
}
実行結果
gmtime() failed for t=9000000000000000000 (errno=0)

特に32ビットのtime_tでは範囲が狭く、遠い未来や過去で失敗しやすい点を覚えておきましょう

localtimeとの違い

localtimeはローカルのタイムゾーンや夏時間を反映して分解します。

同じtime_tでもgmtimelocaltimeで結果が異なるのが通常です

また、どちらも静的領域を返すため、連続して呼び出す場合は値コピーで保護します。

C言語
#include <stdio.h>
#include <time.h>

int main(void) {
    time_t now = time(NULL);

    // すぐにコピーして静的領域の上書きを回避
    struct tm utc = *gmtime(&now);
    struct tm loc = *localtime(&now);

    printf("UTC : %04d-%02d-%02d %02d:%02d:%02d\n",
           utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday,
           utc.tm_hour, utc.tm_min, utc.tm_sec);

    printf("LOCAL: %04d-%02d-%02d %02d:%02d:%02d\n",
           loc.tm_year + 1900, loc.tm_mon + 1, loc.tm_mday,
           loc.tm_hour, loc.tm_min, loc.tm_sec);
    return 0;
}
実行結果
UTC : 2025-05-20 09:42:03
LOCAL: 2025-05-20 18:42:03

アプリ内部の基準はUTC、ユーザー表示はローカルという住み分けをすると、バグや混乱を減らせます

例: UTC時刻を表示する最小コード

年月日と時刻をprintfで表示

最小限のコードとして、現在時刻をUTCで一行表示します。

これを雛形に、ログやCLIツールに簡単に組み込めます

C言語
#include <stdio.h>
#include <time.h>

int main(void) {
    time_t now = time(NULL);
    struct tm *utc = gmtime(&now);
    if (!utc) return 1;

    printf("%04d-%02d-%02d %02d:%02d:%02d UTC\n",
           utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday,
           utc->tm_hour, utc->tm_min, utc->tm_sec);
    return 0;
}
実行結果
2025-05-20 18:42:03 UTC

本当に必要な最小要素はtimegmtimeprintfだけです

簡単なエラー処理

より確実にするには、各段階での失敗を検出します。

実運用のコードではエラーハンドリングを省略しない習慣を付けましょう

C言語
#include <stdio.h>
#include <time.h>

int main(void) {
    time_t now = time(NULL);
    if (now == (time_t)-1) {
        fprintf(stderr, "time() failed\n");
        return 1;
    }

    struct tm tmp;
    struct tm *utc = gmtime(&now);
    if (!utc) {
        fprintf(stderr, "gmtime() failed\n");
        return 1;
    }
    // 安全のため即座にコピーしてから使う
    tmp = *utc;

    printf("%04d-%02d-%02d %02d:%02d:%02d UTC\n",
           tmp.tm_year + 1900, tmp.tm_mon + 1, tmp.tm_mday,
           tmp.tm_hour, tmp.tm_min, tmp.tm_sec);
    return 0;
}
実行結果
2025-05-20 18:42:03 UTC

静的領域の上書き対策として、受け取ったらすぐローカル変数にコピーするのが定石です

まとめ

gmtimetime_tをUTC基準のstruct tmへ分解する標準関数であり、年は+1900、月は+1が必要という基準を正しく適用するのが最重要ポイントです

返り値は静的領域で上書きされるため、即時コピーNULLチェックを行い、必要に応じてスレッド安全な代替や設計で補完してください。

内部はUTC、表示は用途に応じて使い分けることで、時刻処理の混乱とバグを大きく減らせます。

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

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

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

URLをコピーしました!