閉じる

【C言語】UTC時刻を扱う第一歩:time_tからstruct tmへ変換するgmtimeの基本

コンピュータで時刻を扱うとき、目に見える「年・月・日・時刻」と、内部で扱う「秒数」の世界にはギャップがあります。

さらに、世界共通の時刻であるUTCと、日本のようなローカル時刻も区別しなければなりません。

本記事では、C言語で内部表現のtime_tからUTCのカレンダー時刻へ変換するgmtime関数について、初心者の方にも分かりやすいように解説します。

gmtimeとは何かを理解する

UTC(協定世界時)とローカル時刻の違い

まず、UTC(協定世界時)とは何かを押さえておきます。

UTCは世界で共通に使われる基準時刻で、イギリスのグリニッジ天文台付近の時刻を基準としたものです。

日本時間(JST)はUTCより9時間進んでいるため、例えばUTCが10時であれば、日本では19時になります。

コンピュータの時刻には大きく2つの考え方があります。

1つは世界共通で変わらない「UTC時刻」であり、もう1つは国や地域、タイムゾーンに依存する「ローカル時刻」です。

日本のPCでは通常、画面に表示される時刻はローカル時刻(JST)ですが、内部的な処理ではUTCを基準に計算することが多いです。

gmtimeは、この「UTC」を基準とした時刻情報を得るための関数です。

対になるものとして、ローカル時刻を得るlocaltimeという関数もありますが、それは後半で触れます。

gmtimeが返すstruct tmの役割

C言語の標準ライブラリでは、時刻を表すためにstruct tmという構造体が定義されています。

これは人間にとって分かりやすい形式、つまり年・月・日・時・分・秒といった「カレンダー時刻」を収納する箱のような役割です。

gmtimeは、内部表現であるtime_t型の値を受け取り、その値をUTC基準のstruct tmに変換します。

ここで重要なのは、gmtimeが返すstruct tm *はUTCとして解釈されるという点です。

日本など、別のタイムゾーンのローカル時刻ではありません。

time_tとカレンダー時刻の関係

C言語で現在時刻を扱うとき、多くの場合time_tという型が登場します。

これはある基準時刻(多くのシステムでは1970年1月1日00:00:00 UTC)からの経過秒数を表しています。

この基準時刻は「Unixエポック」と呼ばれることがあります。

つまりtime_tは、人間にとって分かりやすい形ではなく、「秒数」という機械寄りの表現です。

そのままでは「2025年11月22日 15時30分」といった形に変換できません。

そこで役に立つのがgmtimelocaltimeです。

関数同士の役割を整理すると、次のようになります。

種類型・関数役割
内部表現time_tエポックからの経過秒数(機械向け)
カレンダー表現struct tm年月日・時分秒など(人間向け)
変換(UTC)gmtimetime_t → UTCのstruct tm
変換(ローカル)localtimetime_t → ローカルのstruct tm

gmtimeは、内部表現とカレンダー表現の橋渡しをするための変換関数であり、その結果がUTCとして解釈される点が特徴です。

gmtimeの基本的な使い方

time関数で現在時刻(time_t)を取得する

まずは現在の時刻をtime_tとして取得します。

これにはtime関数を使用します。

基本的な使い方は次のとおりです。

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

int main(void) {
    // 現在時刻を保持するための変数
    time_t now;

    // time関数で現在時刻を取得
    // 引数にNULLを渡すと、戻り値としてtime_tが返されます
    now = time(NULL);

    // エラー時は(time_t)-1が返される可能性があります
    if (now == (time_t)-1) {
        printf("現在時刻の取得に失敗しました。\n");
        return 1;
    }

    printf("time_tとしての現在時刻: %ld\n", (long)now);

    return 0;
}

上のコードでは、time(NULL)で現在の時刻を取得し、time_t型の変数nowに代入しています

この値は、まだ人間には分かりにくい「秒数」でしかありませんが、次に説明するgmtimeで変換することで、カレンダー時刻にできます。

※このプログラムは実行環境や実行時刻によって出力が変わるため、出力例は省略します。

gmtimeでtime_tからUTCのstruct tmへ変換する

gmtimeを使うと、time_tからUTCのstruct tmへ変換できます。

先ほどのtimeと組み合わせたサンプルを示します。

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

int main(void) {
    time_t now;
    struct tm *utc_tm;

    // 現在の時刻をtime_tとして取得
    now = time(NULL);
    if (now == (time_t)-1) {
        printf("現在時刻の取得に失敗しました。\n");
        return 1;
    }

    // gmtimeでUTCのカレンダー時刻に変換
    utc_tm = gmtime(&now);
    if (utc_tm == NULL) {
        // 変換に失敗した場合
        printf("gmtimeによる変換に失敗しました。\n");
        return 1;
    }

    // struct tmの内容を表示
    // tm_yearは1900からの経過年数、tm_monは0〜11なので補正が必要です
    printf("UTC時刻: %04d-%02d-%02d %02d:%02d:%02d\n",
           utc_tm->tm_year + 1900,  // 西暦年に変換
           utc_tm->tm_mon + 1,      // 1〜12月に変換
           utc_tm->tm_mday,         // 日
           utc_tm->tm_hour,         // 時
           utc_tm->tm_min,          // 分
           utc_tm->tm_sec);         // 秒

    return 0;
}

実行例(例として、ある時刻に実行した場合の一例)を示します。

実行結果
UTC時刻: 2025-11-22 06:30:15

この例では、取得したtime_tgmtimeに渡し、返ってきたstruct tmから年・月・日・時・分・秒を取り出して表示しています

gmtimeの戻り値とNULLチェック

gmtimeの宣言は次のようになっています。

C言語
struct tm *gmtime(const time_t *timer);

引数にはtime_t型の値へのポインタを渡し、戻り値として内部的に保持しているstruct tmへのポインタが返されます。

ここで重要なのは、エラー時にはNULLが返されるという点です。

現代の一般的な環境では、通常の範囲の時刻であればエラーになることは少ないですが、C言語初心者のうちから戻り値のNULLチェックを行う習慣を付けておくことが重要です。

先ほどのサンプルコードでは、次のようにチェックしています。

C言語
utc_tm = gmtime(&now);
if (utc_tm == NULL) {
    printf("gmtimeによる変換に失敗しました。\n");
    return 1;
}

ポインタを返す関数を使うときは、常にNULLの可能性を意識することが、バグを防ぐための基本となります。

struct tmの各メンバを押さえる

年月日

gmtimeが返すstruct tmには、年・月・日を表す複数のメンバがあります。

ただし、直感的ではない点がいくつかあるため、最初にしっかり押さえておくことが大切です。

代表的なメンバは次のとおりです。

メンバ名意味注意点
tm_yearint西暦年から1900を引いた値2025年なら125
tm_monint月(0〜11)0が1月、11が12月
tm_mdayint日(1〜31)1から始まる

たとえば、2025年11月22日のUTCであれば、各メンバは次のようになります。

  • tm_year = 2025 – 1900 = 125
  • tm_mon = 10(0が1月なので、11月は10)
  • tm_mday = 22

実際にこれらを表示する小さなサンプルを見てみましょう。

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

int main(void) {
    time_t now = time(NULL);
    struct tm *utc_tm = gmtime(&now);

    if (utc_tm == NULL) {
        printf("gmtimeによる変換に失敗しました。\n");
        return 1;
    }

    printf("tm_year(1900からの年数): %d\n", utc_tm->tm_year);
    printf("tm_mon(0が1月): %d\n", utc_tm->tm_mon);
    printf("tm_mday(日): %d\n", utc_tm->tm_mday);

    printf("表示用に補正した年月日: %04d-%02d-%02d\n",
           utc_tm->tm_year + 1900,  // 西暦年
           utc_tm->tm_mon + 1,      // 1〜12月
           utc_tm->tm_mday);

    return 0;
}

このように、画面に表示するときやログに出すときは、tm_yeartm_monを必ず補正することを忘れないようにしましょう。

時分秒

時・分・秒もstruct tmのメンバとして保持されています。

これらは比較的直感的な値です。

メンバ名意味範囲
tm_hourint0〜23
tm_minint0〜59
tm_secint0〜60(うるう秒を考慮する実装もある)

これらを表示する簡単な例です。

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

int main(void) {
    time_t now = time(NULL);
    struct tm *utc_tm = gmtime(&now);

    if (utc_tm == NULL) {
        printf("gmtimeによる変換に失敗しました。\n");
        return 1;
    }

    printf("UTCの時分秒: %02d:%02d:%02d\n",
           utc_tm->tm_hour,  // 時(0〜23)
           utc_tm->tm_min,   // 分(0〜59)
           utc_tm->tm_sec);  // 秒(0〜60のこともある)

    return 0;
}

ここで扱っているのはすべてUTC基準の時刻であり、日本時間などのローカル時刻ではない点を意識しておくと混乱が少なくなります。

曜日・通算日

struct tmには、曜日や、その年の何日目かといった情報も含まれています。

これらは計算で求めることもできますが、gmtimeが自動で設定してくれるため、そのまま利用できます。

メンバ名意味範囲
tm_wday曜日0〜60:日曜, 1:月曜, …, 6:土曜
tm_yday年内の通算日0〜3650が1月1日

例えば、2025年11月22日が何曜日で、年内の何日目かを知りたいときに役立ちます。

曜日を文字列で表示する簡単な例を示します。

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

int main(void) {
    time_t now = time(NULL);
    struct tm *utc_tm = gmtime(&now);

    if (utc_tm == NULL) {
        printf("gmtimeによる変換に失敗しました。\n");
        return 1;
    }

    // 曜日を文字列に対応させる配列
    const char *wday_names[] = {
        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
    };

    if (utc_tm->tm_wday < 0 || utc_tm->tm_wday > 6) {
        printf("曜日情報が不正です。\n");
        return 1;
    }

    printf("UTCの曜日: %s\n", wday_names[utc_tm->tm_wday]);
    printf("年内の通算日(0が1月1日): %d\n", utc_tm->tm_yday);

    return 0;
}

このように、曜日や通算日は日付の計算や表示で便利に使える付加情報です。

特に通算日は、2つの日付の差を求めるときのヒントになります。

サマータイム情報

struct tmには、サマータイム(夏時間)に関する情報を示すtm_isdstというメンバもあります。

メンバ名意味
tm_isdst夏時間を使用しているかどうか

この値は通常、次のような値を取ります。

  • 0: サマータイムではない
  • 正の値: サマータイムである
  • 負の値: 不明(決定できない)

ただし、gmtimeが返すstruct tmにおいては、UTCという世界共通時刻に対してサマータイムの概念は通常ありません

そのため、多くの実装ではtm_isdstが0になることが多いです。

実際に値を確認するサンプルを示します。

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

int main(void) {
    time_t now = time(NULL);
    struct tm *utc_tm = gmtime(&now);

    if (utc_tm == NULL) {
        printf("gmtimeによる変換に失敗しました。\n");
        return 1;
    }

    printf("tm_isdstの値: %d\n", utc_tm->tm_isdst);

    if (utc_tm->tm_isdst > 0) {
        printf("夏時間が適用されています(ただしUTCでは通常ありえません)。\n");
    } else if (utc_tm->tm_isdst == 0) {
        printf("夏時間は適用されていません。\n");
    } else {
        printf("夏時間かどうかは不明です。\n");
    }

    return 0;
}

サマータイムは主にローカル時刻(localtime)で問題になる概念であり、UTCと組み合わせて考えることで、世界各地の時刻を正しく扱うことができます。

gmtimeを安全に使うための注意点

静的領域を使うgmtimeの仕様とスレッド安全性

gmtimeは、初心者が最初につまずきやすいポイントを含んでいます。

それは戻り値のstruct tm *が「毎回同じ静的な領域」を指しているという仕様です。

つまり、次のような特徴があります。

  • gmtimeを呼び出すたびに、内部の同じstruct tmオブジェクトが書き換えられる
  • 以前に取得したポインタは、次の呼び出しによって内容が上書きされる可能性がある

具体的な注意点を示すための例を見てみましょう。

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

int main(void) {
    time_t t1 = time(NULL);
    time_t t2 = t1 + 3600; // 1時間後を適当に作る

    struct tm *tm1 = gmtime(&t1);
    struct tm *tm2 = gmtime(&t2);

    // tm1とtm2は同じ静的な領域を指している可能性が高く、
    // 2回目のgmtime呼び出しで内容が上書きされます。

    printf("t1のUTC時刻: %04d-%02d-%02d %02d:%02d:%02d\n",
           tm1->tm_year + 1900,
           tm1->tm_mon + 1,
           tm1->tm_mday,
           tm1->tm_hour,
           tm1->tm_min,
           tm1->tm_sec);

    printf("t2のUTC時刻: %04d-%02d-%02d %02d:%02d:%02d\n",
           tm2->tm_year + 1900,
           tm2->tm_mon + 1,
           tm2->tm_mday,
           tm2->tm_hour,
           tm2->tm_min,
           tm2->tm_sec);

    return 0;
}

このコードは一見問題なさそうに見えますが、実際にはtm1tm2が同じstruct tmを指しているため、表示される両方の行が「t2の時刻」になってしまう可能性があります。

この問題を避けるためには、次のように自前でstruct tmのコピーを作る方法が効果的です。

C言語
#include <stdio.h>
#include <time.h>
#include <string.h> // memcpyを使うため

int main(void) {
    time_t t1 = time(NULL);
    time_t t2 = t1 + 3600; // 1時間後

    struct tm tmp1;
    struct tm tmp2;

    struct tm *p;

    // t1のUTC時刻を取得し、構造体ごとコピー
    p = gmtime(&t1);
    if (p == NULL) {
        printf("gmtimeに失敗しました(t1)。\n");
        return 1;
    }
    memcpy(&tmp1, p, sizeof(struct tm));

    // t2のUTC時刻を取得し、構造体ごとコピー
    p = gmtime(&t2);
    if (p == NULL) {
        printf("gmtimeに失敗しました(t2)。\n");
        return 1;
    }
    memcpy(&tmp2, p, sizeof(struct tm));

    // それぞれ独立した構造体から表示
    printf("t1のUTC時刻: %04d-%02d-%02d %02d:%02d:%02d\n",
           tmp1.tm_year + 1900,
           tmp1.tm_mon + 1,
           tmp1.tm_mday,
           tmp1.tm_hour,
           tmp1.tm_min,
           tmp1.tm_sec);

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

    return 0;
}

このように、gmtimeの戻り値は必要に応じて自分の変数にコピーすることで、安全に扱うことができます。

さらに、スレッドを使う環境では、別スレッドからのgmtime呼び出しで静的領域が上書きされる危険性もあります。

これは「スレッドセーフではない」動作となるため、次で説明するgmtime_rのような関数が用意されています。

gmtime_rとの違い

gmtime_rは、スレッドセーフなgmtimeのバージョンとして、多くのUNIX系システムで提供されている関数です(厳密にはPOSIX系の拡張であり、標準Cには含まれません)。

宣言は次のようになっています。

C言語
struct tm *gmtime_r(const time_t *timer, struct tm *result);

第2引数に自分で用意したstruct tmの領域を渡すと、その中に変換結果を書き込んでくれます。

これにより、静的領域を共有しないため、スレッド間でも安全に利用できます。

使用例を示します。

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

int main(void) {
    time_t now = time(NULL);
    struct tm utc_tm;        // 自前で領域を用意
    struct tm *ret;

    // gmtime_rを使ってUTCのカレンダー時刻に変換
    ret = gmtime_r(&now, &utc_tm);
    if (ret == NULL) {
        printf("gmtime_rによる変換に失敗しました。\n");
        return 1;
    }

    printf("UTC時刻(gmtime_r使用): %04d-%02d-%02d %02d:%02d:%02d\n",
           utc_tm.tm_year + 1900,
           utc_tm.tm_mon + 1,
           utc_tm.tm_mday,
           utc_tm.tm_hour,
           utc_tm.tm_min,
           utc_tm.tm_sec);

    return 0;
}

ポイントは、第2引数に渡したutc_tmに結果が書き込まれるため、戻り値のポインタと&utc_tmは同じアドレスを指すということです。

ただし、Windowsの標準ライブラリにはgmtime_rが存在せず、代わりにgmtime_sが提供されています

環境ごとの違いがあるため、実際の開発では使用しているプラットフォームのマニュアルを確認してください。

まとめると、シングルスレッドであればgmtimeでも実用上問題ないことが多いですが、将来的な拡張や安全性を考えるなら、gmtime_rgmtime_sといったスレッドセーフな関数の使用も検討すると良いです。

gmtimeとlocaltimeの使い分け

最後に、gmtimeとlocaltimeの違いと使い分けについて整理しておきます。

両者は非常によく似たインタフェースを持っていますが、基準となる時刻が異なります。

関数名変換先どの時間帯の時刻か典型的な用途
gmtimestruct tmUTC(協定世界時)ログ、サーバ間通信、時刻の内部管理
localtimestruct tmローカルタイム(PCのタイムゾーン)画面表示、ユーザ向けの時刻表示

localtimeの基本的な使い方はgmtimeとよく似ています。

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

int main(void) {
    time_t now = time(NULL);
    struct tm *local_tm;

    // ローカル時刻へ変換
    local_tm = localtime(&now);
    if (local_tm == NULL) {
        printf("localtimeによる変換に失敗しました。\n");
        return 1;
    }

    printf("ローカル時刻: %04d-%02d-%02d %02d:%02d:%02d\n",
           local_tm->tm_year + 1900,
           local_tm->tm_mon + 1,
           local_tm->tm_mday,
           local_tm->tm_hour,
           local_tm->tm_min,
           local_tm->tm_sec);

    return 0;
}

サーバ開発やログ管理などで、時刻を一意に扱いたい場合はUTC(=gmtime)を使うことが多いです。

例えば、異なるタイムゾーンにある複数のサーバのログを比較したい場合、すべてUTCで統一されていると非常に扱いやすくなります。

一方で、ユーザに見せる画面や、PC上のアプリケーションでの表示にはlocaltimeローカル時刻を使うのが自然です。

日本のユーザにはJST、アメリカのユーザにはそれぞれのタイムゾーンの時刻を表示する、といった使い方です。

このように、内部ではUTCで統一し、表示の直前にローカル時刻へ変換するというパターンは、多くのシステムで採用されている鉄板の設計です。

C言語で時間を扱う際も、この考え方を意識しておくと、後々の拡張や国際化対応がしやすくなります。

まとめ

本記事では、C言語におけるUTC時刻の扱いの第一歩として、time_tからstruct tmへ変換するgmtime関数を中心に解説しました。

内部表現であるtime_tと、人間向けのカレンダー時刻struct tmの関係、UTCとローカル時刻の違い、各メンバの意味や注意点、さらに静的領域やスレッド安全性、gmtime_rとの違いについても説明しました。

まずはUTCで統一して扱う感覚を身につけることが、時刻処理を正しく行ううえでの大きな一歩になります。

数学・数値処理

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

URLをコピーしました!