コンピュータで時刻を扱うとき、目に見える「年・月・日・時刻」と、内部で扱う「秒数」の世界にはギャップがあります。
さらに、世界共通の時刻である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分」といった形に変換できません。
そこで役に立つのがgmtimeやlocaltimeです。
関数同士の役割を整理すると、次のようになります。
| 種類 | 型・関数 | 役割 |
|---|---|---|
| 内部表現 | time_t | エポックからの経過秒数(機械向け) |
| カレンダー表現 | struct tm | 年月日・時分秒など(人間向け) |
| 変換(UTC) | gmtime | time_t → UTCのstruct tm |
| 変換(ローカル) | localtime | time_t → ローカルのstruct tm |
gmtimeは、内部表現とカレンダー表現の橋渡しをするための変換関数であり、その結果がUTCとして解釈される点が特徴です。
gmtimeの基本的な使い方
time関数で現在時刻(time_t)を取得する
まずは現在の時刻をtime_tとして取得します。
これにはtime関数を使用します。
基本的な使い方は次のとおりです。
#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と組み合わせたサンプルを示します。
#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_tをgmtimeに渡し、返ってきたstruct tmから年・月・日・時・分・秒を取り出して表示しています。
gmtimeの戻り値とNULLチェック
gmtimeの宣言は次のようになっています。
struct tm *gmtime(const time_t *timer);
引数にはtime_t型の値へのポインタを渡し、戻り値として内部的に保持しているstruct tmへのポインタが返されます。
ここで重要なのは、エラー時にはNULLが返されるという点です。
現代の一般的な環境では、通常の範囲の時刻であればエラーになることは少ないですが、C言語初心者のうちから戻り値のNULLチェックを行う習慣を付けておくことが重要です。
先ほどのサンプルコードでは、次のようにチェックしています。
utc_tm = gmtime(&now);
if (utc_tm == NULL) {
printf("gmtimeによる変換に失敗しました。\n");
return 1;
}
ポインタを返す関数を使うときは、常にNULLの可能性を意識することが、バグを防ぐための基本となります。
struct tmの各メンバを押さえる
年月日
gmtimeが返すstruct tmには、年・月・日を表す複数のメンバがあります。
ただし、直感的ではない点がいくつかあるため、最初にしっかり押さえておくことが大切です。
代表的なメンバは次のとおりです。
| メンバ名 | 型 | 意味 | 注意点 |
|---|---|---|---|
tm_year | int | 西暦年から1900を引いた値 | 2025年なら125 |
tm_mon | int | 月(0〜11) | 0が1月、11が12月 |
tm_mday | int | 日(1〜31) | 1から始まる |
たとえば、2025年11月22日のUTCであれば、各メンバは次のようになります。
tm_year= 2025 – 1900 = 125tm_mon= 10(0が1月なので、11月は10)tm_mday= 22
実際にこれらを表示する小さなサンプルを見てみましょう。
#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_yearとtm_monを必ず補正することを忘れないようにしましょう。
時分秒
時・分・秒もstruct tmのメンバとして保持されています。
これらは比較的直感的な値です。
| メンバ名 | 型 | 意味 | 範囲 |
|---|---|---|---|
tm_hour | int | 時 | 0〜23 |
tm_min | int | 分 | 0〜59 |
tm_sec | int | 秒 | 0〜60(うるう秒を考慮する実装もある) |
これらを表示する簡単な例です。
#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〜6 | 0:日曜, 1:月曜, …, 6:土曜 |
tm_yday | 年内の通算日 | 0〜365 | 0が1月1日 |
例えば、2025年11月22日が何曜日で、年内の何日目かを知りたいときに役立ちます。
曜日を文字列で表示する簡単な例を示します。
#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になることが多いです。
実際に値を確認するサンプルを示します。
#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オブジェクトが書き換えられる - 以前に取得したポインタは、次の呼び出しによって内容が上書きされる可能性がある
具体的な注意点を示すための例を見てみましょう。
#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;
}
このコードは一見問題なさそうに見えますが、実際にはtm1とtm2が同じstruct tmを指しているため、表示される両方の行が「t2の時刻」になってしまう可能性があります。
この問題を避けるためには、次のように自前でstruct tmのコピーを作る方法が効果的です。
#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には含まれません)。
宣言は次のようになっています。
struct tm *gmtime_r(const time_t *timer, struct tm *result);
第2引数に自分で用意したstruct tmの領域を渡すと、その中に変換結果を書き込んでくれます。
これにより、静的領域を共有しないため、スレッド間でも安全に利用できます。
使用例を示します。
#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_rやgmtime_sといったスレッドセーフな関数の使用も検討すると良いです。
gmtimeとlocaltimeの使い分け
最後に、gmtimeとlocaltimeの違いと使い分けについて整理しておきます。
両者は非常によく似たインタフェースを持っていますが、基準となる時刻が異なります。
| 関数名 | 変換先 | どの時間帯の時刻か | 典型的な用途 |
|---|---|---|---|
gmtime | struct tm | UTC(協定世界時) | ログ、サーバ間通信、時刻の内部管理 |
localtime | struct tm | ローカルタイム(PCのタイムゾーン) | 画面表示、ユーザ向けの時刻表示 |
localtimeの基本的な使い方はgmtimeとよく似ています。
#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で統一して扱う感覚を身につけることが、時刻処理を正しく行ううえでの大きな一歩になります。
