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_year | int | 1900基準の年差 | 実年は 1900 + tm_year |
tm_mon | int | 0〜11 | 実月は 1〜12に変換が必要 |
tm_mday | int | 1〜31 | 日 |
tm_hour | int | 0〜23 | 時 |
tm_min | int | 0〜59 | 分 |
tm_sec | int | 0〜60 | 秒(うるう秒で60になることあり) |
tm_wday | int | 0〜6 | 曜日 0:日 1:月 … 6:土 |
tm_yday | int | 0〜365 | 年内の通し日 0:1月1日 |
tm_isdst | int | 負/0/正 | 夏時間情報(UTCでは普通-1または0) |
最もよくつまずくのがtm_year
とtm_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_t
とstruct tm
、関数はtime
とgmtime
の組み合わせを覚えておきましょう。
現在時刻をUTCに変換する
以下は、現在のUNIX時刻を取得し、UTCのstruct tm
に分解して表示する例です。
ゼロ埋めで「YYYY-MM-DD HH:MM:SS UTC」の形式に整形しています。
#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秒)や既知のタイムスタンプを使うと、動作確認が容易です。
#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 + 1900
とtm_mon + 1
を忘れないことが最重要ポイントです。
曜日はtm_wday
で0(日)〜6(土)、年内通し日はtm_yday
で0が1月1日です。
#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
は内部の静的領域へのポインタを返します。
次のgmtime
やlocaltime
の呼び出しで内容が上書きされるため、そのままポインタを長く保持してはいけません。
必要ならすぐに値をコピーしましょう。
悪い例と良い例を続けて示します。
#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
を返します。
戻り値のヌルチェックは必ず行い、エラーメッセージや代替処理を入れておきましょう。
#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
でもgmtime
とlocaltime
で結果が異なるのが通常です。
また、どちらも静的領域を返すため、連続して呼び出す場合は値コピーで保護します。
#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ツールに簡単に組み込めます。
#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
本当に必要な最小要素はtime
、gmtime
、printf
だけです。
簡単なエラー処理
より確実にするには、各段階での失敗を検出します。
実運用のコードではエラーハンドリングを省略しない習慣を付けましょう。
#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
静的領域の上書き対策として、受け取ったらすぐローカル変数にコピーするのが定石です。
まとめ
gmtime
はtime_t
をUTC基準のstruct tm
へ分解する標準関数であり、年は+1900
、月は+1
が必要という基準を正しく適用するのが最重要ポイントです。
返り値は静的領域で上書きされるため、即時コピーやNULLチェックを行い、必要に応じてスレッド安全な代替や設計で補完してください。
内部はUTC、表示は用途に応じて使い分けることで、時刻処理の混乱とバグを大きく減らせます。