C言語で日時処理を行う際、UNIX時刻からUTCの年月日や時刻を得たい場面は少なくありません。
gmtime関数は、そのようなときに最も基本となる関数です。
本記事では、gmtimeの役割から、struct tmの中身、実践的なサンプルコード、注意点やスレッドセーフな代替まで、初心者でも理解しやすいように段階的に解説します。
画像イメージも交えながら、UTCとローカル時刻の違いも整理していきます。
gmtimeとは?UNIX時刻からUTCを取得する基本
gmtimeとは何をする関数か
gmtimeは、UNIX時刻(time_t型)を「UTCの構造化された日付・時刻(struct tm)」に変換する標準Cライブラリ関数です。
UNIX時刻(time_t)は「1970-01-01 00:00:00 UTCからの経過秒数」を表す単なる整数ですが、そのままでは人間にとって読みにくい形式です。
gmtimeを使うことで、次のような情報に分解できます。
- 年(cst-code)tm_year
- 月(cst-code)tm_mon
- 日(cst-code)tm_mday
- 時(cst-code)tm_hour
- 分(cst-code)tm_min
- 秒(cst-code)tm_sec など
ここで重要なポイントは、gmtimeが返す日時は常にUTC(協定世界時)であり、ローカルタイムではないという点です。

gmtimeと他の時間変換関数の位置づけ
標準Cには、似たような役割を持つ関数としてlocaltimeがあります。
- cst-code>gmtime: time_t → UTCのstruct tm
- cst-code>localtime: time_t → ローカル時刻(タイムゾーン適用後)のstruct tm
用途に応じて、UTCが欲しいのかローカル時刻が欲しいのかを明確にして使い分けることが大切です。
time_tとUNIX時刻の関係
time_t型は「カレンダー時刻(calendar time)」を表すためのC標準ライブラリの型です。
通常、多くのUNIX系OSやWindowsではUNIX時刻(UNIX time)が格納されます。
time_tの中身は実装依存
C言語の仕様として、cst-code>time_tは「整数型である」とは明示されていません。
しかし、多くの実装では以下のようになっています。
- 実体は符号付き整数型(signed integer)
- 表す値は1970-01-01 00:00:00 UTCからの経過秒数
この値を指して、一般にUNIX時刻やPOSIX時間と呼びます。
time関数との関係
現在のUNIX時刻を取得するには、cst-code>time関数を使います。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
// 現在のUNIX時刻(エポックからの秒数)を取得
now = time(NULL);
printf("UNIX時刻: %ld\n", (long)now);
return 0;
}
UNIX時刻: 1700000000 // 実行したタイミングにより値は変わります
上記のような整数値を、人間が読める年月日・時刻に変換する役割を担うのが、gmtimeやlocaltimeです。
UTCとローカル時刻(ローカルタイム)の違い
日時処理を行う際に避けて通れないのが、UTCとローカル時刻の違いです。

UTC(協定世界時)とは
UTC(協定世界時)は、世界共通の基準時刻で、タイムゾーンによる差がありません。
インターネットプロトコルやログの共通時刻表示など、グローバルなシステムではUTCを基準にすることが多いです。
- 地域による時差なし
- サマータイム(DST)の概念も持たない基準時間
- 表記例: cst-code>2023-11-14 12:00:00 UTC
ローカル時刻とは
ローカル時刻は、特定のタイムゾーンやサマータイムが適用された地域固有の時刻です。
日本であれば通常UTC+9が適用されます。
- タイムゾーン(UTC+X, UTC-X)を考慮
- 一部の国ではサマータイム(DST)により、季節で時刻が1時間ずれる
- 表記例: cst-code>2023-11-14 21:00:00 JST(= UTC+9)
gmtimeとlocaltimeの使い分けイメージ
- 世界中のサーバで一貫したログ時刻を記録したい → gmtime(UTC)
- ユーザーの現地時間で画面表示したい → localtime(ローカル時刻)
このように、内部処理や保存はUTC、表示はローカル時刻という設計を採用するケースが多いです。
gmtimeの基本的な使い方とコード例
gmtimeの関数プロトタイプと戻り値
gmtimeの宣言は、ヘッダtime.hに含まれています。
#include <time.h>
struct tm *gmtime(const time_t *timer);
引数と戻り値の意味
- 引数
timer
変換元のtime_t値へのポインタです。通常はtimeで取得した値を渡します。 - 戻り値
変換結果が格納されたstruct tmへのポインタを返します。内部の静的バッファを指している点が重要で、後述の注意点で詳しく解説します。
失敗時にはNULLが返される可能性があるため、実務コードでは戻り値チェックを行うことが推奨されます。
struct tmの各メンバ(year・mon・mdayなど)の意味
gmtimeが返すのはstruct tm構造体へのポインタです。
この中に、年月日や時刻が各フィールドに分かれて格納されています。

struct tmの主なメンバ一覧
代表的なフィールドを表で整理します。
| メンバ名 | 型 | 意味 | 備考 |
|---|---|---|---|
| tm_sec | int | 秒(0〜60) | うるう秒により最大60となる場合あり |
| tm_min | int | 分(0〜59) | |
| tm_hour | int | 時(0〜23) | |
| tm_mday | int | 日(1〜31) | |
| tm_mon | int | 月(0〜11) | 0が1月、11が12月 |
| tm_year | int | 1900年からの年数 | 2023年なら123 |
| tm_wday | int | 曜日(0〜6) | 0が日曜、1が月曜… |
| tm_yday | int | 年内の通算日(0〜365) | 0が1月1日 |
| tm_isdst | int | サマータイム適用フラグ(>0:適用中, 0:なし) | gmtimeの場合は通常0 |
特にtm_yearとtm_monの扱いは間違えやすいので注意が必要です。
- 年 = cst-code>tm_year + 1900
- 月(1〜12) = cst-code>tm_mon + 1
timeとgmtimeを組み合わせた基本サンプルコード
ここでは、現在時刻をUNIX時刻で取得し、gmtimeでUTCのstruct tmに変換する基本的なサンプルを示します。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now; // UNIX時刻を保持する変数
struct tm *utc; // gmtimeが返すUTC時刻(構造体)へのポインタ
// 現在のUNIX時刻を取得
now = time(NULL);
if (now == (time_t)-1) {
// 取得失敗時の簡易エラーメッセージ
fprintf(stderr, "time() による現在時刻の取得に失敗しました\n");
return 1;
}
// UNIX時刻をUTCのstruct tmに変換
utc = gmtime(&now);
if (utc == NULL) {
fprintf(stderr, "gmtime() による変換に失敗しました\n");
return 1;
}
// ここでは、取得した値を生のまま出力してみる
printf("tm_year = %d\n", utc->tm_year);
printf("tm_mon = %d\n", utc->tm_mon);
printf("tm_mday = %d\n", utc->tm_mday);
printf("tm_hour = %d\n", utc->tm_hour);
printf("tm_min = %d\n", utc->tm_min);
printf("tm_sec = %d\n", utc->tm_sec);
return 0;
}
tm_year = 123
tm_mon = 10
tm_mday = 14
tm_hour = 3
tm_min = 26
tm_sec = 40
この出力は、構造体内部の「生の値」を表示しているため、そのままでは分かりづらいことがあります。
ユーザー向けに表示する場合は、次節のように整形が必要です。
gmtimeで取得したUTCをprintfで整形出力する方法
gmtimeで得たstruct tmを、人間が読みやすい「YYYY-MM-DD hh:mm:ss」形式の文字列として表示する方法を示します。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *utc;
now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "time() に失敗しました\n");
return 1;
}
utc = gmtime(&now);
if (utc == NULL) {
fprintf(stderr, "gmtime() に失敗しました\n");
return 1;
}
// tm構造体の値を人間が読める形式に変換して表示
// 年は1900を足す、月は1を足すことに注意
printf("UTC: %04d-%02d-%02d %02d:%02d:%02d\n",
utc->tm_year + 1900, // 西暦年
utc->tm_mon + 1, // 1〜12の月
utc->tm_mday,
utc->tm_hour,
utc->tm_min,
utc->tm_sec);
return 0;
}
UTC: 2023-11-14 03:26:40
このように、printfを用いたフォーマット指定により、桁数を揃えた見やすい表示が可能です。
gmtimeを使った実践サンプル集
現在のUNIX時刻からUTC日時文字列を生成する
最初に、現在のUNIX時刻からUTC日時の文字列を1行で生成して表示するシンプルな例を示します。

#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *utc;
now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "time() に失敗しました\n");
return 1;
}
utc = gmtime(&now);
if (utc == NULL) {
fprintf(stderr, "gmtime() に失敗しました\n");
return 1;
}
// YYYY-MM-DD hh:mm:ss 形式のUTC日時文字列を生成して表示
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);
return 0;
}
現在のUTC日時: 2023-11-14 03:26:40
このサンプルは、ログのタイムスタンプやデバッグ出力など、さまざまな用途でそのまま活用できます。
任意のUNIXタイムスタンプからUTC日時を取得する
次に、任意のUNIXタイムスタンプ値を指定して、その時刻のUTC日時を確認するサンプルです。
過去や未来の日時をテストするときに便利です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t t;
struct tm *utc;
// 例として、1700000000 というUNIX時刻を扱う
// 実際には、ファイルのタイムスタンプやDBから取得した値を使うこともあります
t = (time_t)1700000000;
utc = gmtime(&t);
if (utc == NULL) {
fprintf(stderr, "gmtime() に失敗しました\n");
return 1;
}
printf("UNIX時刻 %ld は UTCで %04d-%02d-%02d %02d:%02d:%02d です\n",
(long)t,
utc->tm_year + 1900,
utc->tm_mon + 1,
utc->tm_mday,
utc->tm_hour,
utc->tm_min,
utc->tm_sec);
return 0;
}
UNIX時刻 1700000000 は UTCで 2023-11-14 22:13:20 です
このように固定値を指定することで、テストの再現性を確保しながら日付変換の挙動を確認できるようになります。
ローカル時刻(localtime)とUTC(gmtime)の差を比較するサンプル
グローバル対応アプリケーションでは、UTCとローカル時刻の両方を意識する必要があります。
ここでは、同じUNIX時刻をgmtimeとlocaltimeの両方で変換し、その違いを表示するサンプルを示します。

#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *utc;
struct tm *local;
now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "time() に失敗しました\n");
return 1;
}
utc = gmtime(&now);
local = localtime(&now);
if (utc == NULL || local == NULL) {
fprintf(stderr, "gmtime() または localtime() に失敗しました\n");
return 1;
}
printf("UNIX時刻 : %ld\n", (long)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",
local->tm_year + 1900,
local->tm_mon + 1,
local->tm_mday,
local->tm_hour,
local->tm_min,
local->tm_sec);
return 0;
}
UNIX時刻 : 1700000000
UTC : 2023-11-14 22:13:20
Local : 2023-11-15 07:13:20
この出力例では、LocalがUTCより9時間進んでいることが分かります。
これは、環境のタイムゾーンがUTC+9(日本時間)になっているためです。
gmtimeとstrftimeでISO8601形式(YYYY-MM-DDThh:mm:ssZ)を出力する
ISO8601形式は、日時をコンピュータ同士でやり取りする際の標準的なフォーマットです。
UTCを表す場合、末尾にZを付けてYYYY-MM-DDThh:mm:ssZのように書きます。
C言語では、cst-code>strftimeを使うと、struct tmから任意のフォーマットの文字列を生成できます。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *utc;
char buf[64]; // 生成される文字列を格納するバッファ
now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "time() に失敗しました\n");
return 1;
}
utc = gmtime(&now);
if (utc == NULL) {
fprintf(stderr, "gmtime() に失敗しました\n");
return 1;
}
// ISO8601形式: YYYY-MM-DDThh:mm:ssZ を生成
// strftimeのフォーマット指定:
// %Y: 4桁の年
// %m: 2桁の月
// %d: 2桁の日
// %H: 2桁の時(24時間表記)
// %M: 2桁の分
// %S: 2桁の秒
// 'T' と 'Z' は文字列としてそのまま出力
if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", utc) == 0) {
// バッファサイズが足りないなどで失敗した場合
fprintf(stderr, "strftime() によるフォーマットに失敗しました\n");
return 1;
}
printf("ISO8601 UTC: %s\n", buf);
return 0;
}
ISO8601 UTC: 2023-11-14T03:26:40Z
この形式は、REST APIのレスポンスやJSON/XMLのタイムスタンプなどで広く利用されているため、覚えておくと非常に便利です。
gmtime使用時の注意点と安全な代替関数
gmtimeのスレッドセーフ性と内部静的バッファの問題
gmtimeを使う上で、もっとも注意すべき点の1つがスレッドセーフではないことです。
内部静的バッファとは何か
gmtimeは、内部で「静的なstruct tm領域」を1つだけ持っており、その領域に変換結果を書き込んで、そのポインタを返します。
そのため、同じスレッド内であっても、以下のようなコードは危険です。
struct tm *a = gmtime(&t1);
struct tm *b = gmtime(&t2);
// a と b は同じ静的領域を指しているため、
// a が指していた内容は、b の呼び出しで上書きされてしまう
このように、複数回呼び出すと古い結果が上書きされるため、戻り値のポインタを長く保持して使い回すことは危険です。
マルチスレッド環境での問題
マルチスレッド環境では、さらに状況が複雑になります。
別スレッドがgmtimeを呼ぶと、同じ静的バッファが共有されているため、データ競合(race condition)が発生し、予期せぬ値が参照される可能性があります。
マルチスレッドプログラムでgmtimeを直接使うのは原則として避けるべきです。
gmtime_r(gmtime_s)によるスレッドセーフな書き方
この問題に対処するために、多くの環境では再入可能版・スレッドセーフ版の関数が提供されています。
- POSIX環境:
gmtime_r - Windows(MSVCなど):
gmtime_s
gmtime_rの使い方(POSIX)
gmtime_rは、呼び出し側が用意したstruct tmバッファに結果を書き込む関数です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm utc; // 呼び出し側でバッファを確保
struct tm *ret;
now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "time() に失敗しました\n");
return 1;
}
// gmtime_r(time_t *src, struct tm *dst)
ret = gmtime_r(&now, &utc);
if (ret == NULL) {
fprintf(stderr, "gmtime_r() に失敗しました\n");
return 1;
}
printf("UTC(gmtime_r): %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);
return 0;
}
UTC(gmtime_r): 2023-11-14 03:26:40
ここでは構造体そのものをスタック上に確保し、それに直接書き込ませているため、静的バッファを共有しないというメリットがあります。
gmtime_sの使い方(Windows)
Windows/MSVCではgmtime_sが推奨されています。
引数の順序がgmtime_rとは逆であることに注意が必要です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm utc;
now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "time() に失敗しました\n");
return 1;
}
// gmtime_s(struct tm *dst, const time_t *src)
if (gmtime_s(&utc, &now) != 0) {
fprintf(stderr, "gmtime_s() に失敗しました\n");
return 1;
}
printf("UTC(gmtime_s): %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);
return 0;
}
UTC(gmtime_s): 2023-11-14 03:26:40
POSIX系とWindowsで関数名と引数の順序が異なるため、マルチプラットフォーム対応を行う場合は#ifdefなどで切り替える必要があります。
タイムゾーンとサマータイム(DST)に関する注意点
gmtimeはUTCに変換する関数であるため、タイムゾーンやサマータイム(DST)の影響を受けません。
これは一見すると安心感がありますが、いくつかの注意ポイントがあります。
gmtimeは「常にUTC」である
- システムのタイムゾーン設定に依存しない
- サマータイムのある地域でも、出力は常にUTC
そのため、ログや内部計算の基準時刻としては非常に安定しています。
localtimeとの混在に注意
一方で、localtimeはタイムゾーンやDSTを考慮します。
例えば、サマータイムの切り替え時刻近辺では、ローカル時刻が「1時間進む」「1時間戻る」などの現象が起こります。
- あるUNIX時刻をlocaltimeで変換 → 時刻はDST適用後のローカル時刻
- 同じUNIX時刻をgmtimeで変換 → 常にUTCとして一意に決まる
内部ではUTC、表示するときだけlocaltimeという設計が推奨される背景には、このような複雑さを隔離する意図があります。
time_tの32ビット問題(2038年問題)とgmtimeの関係
最後に、2038年問題とgmtimeの関係について触れておきます。

2038年問題とは
多くの古いシステムでは、cst-code>time_tが32ビットの符号付き整数として実装されています。
この場合、表現できる最大値はおおよそ2038-01-19 03:14:07 UTCまでです。
それ以降の日時を扱おうとすると、オーバーフローによって負の値となり、1970年以前の日付として誤解される可能性があります。
これが2038年問題です。
gmtimeへの影響
gmtimeは、引数として渡されたcst-code>time_tを前提に動作します。
したがって、time_tが2038年以降を表現できない場合、当然ながらその先の日時を正しく変換できません。
- 32ビットtime_t環境: 2038年以降のUNIX時刻を扱うとgmtimeが誤った結果を返す可能性
- 64ビットtime_t環境: かなり遠い未来まで安全に扱える
近年の64ビットOSやライブラリでは、time_tが64ビット化されていることが多く、2038年問題は緩和されていますが、古い環境との互換性を考えると依然として注意が必要です。
まとめ
本記事では、C言語のgmtime関数を用いてUNIX時刻からUTCの年月日・時刻を取得する方法を、基礎から実践例、注意点まで解説しました。
struct tmの各メンバの意味や、printf・strftimeによる整形表示、localtimeとの比較、さらにはスレッドセーフなgmtime_r/gmtime_sの使い方も取り上げました。
UTCとローカル時刻の違い、2038年問題などの背景知識も押さえることで、安全で拡張性の高い日時処理が実現できます。
ログ出力やAPI設計など、実務の場面でもぜひ活用してみてください。
