C言語で時間を扱うとき、必ず登場する型がtime_tです。
現在時刻の取得や日付表示、ログ出力、そして2038年問題まで、時間まわりの処理はこの型を正しく理解しているかどうかで大きく変わります。
本記事では、time_tの正体・使い方・32bit問題への備えを、サンプルコードと図解を交えながら整理して解説します。
time_tとは何か
time_tの基本型と役割
time_tは、C言語の標準ライブラリで定義されている「時刻を表すための型」です。
ヘッダファイルtime.hの中で定義されています。
ポイントは、time_tは「中身の型が処理系依存の抽象型」であるということです。
多くの環境ではlongやlong longなどの整数型として実装されていますが、Cの仕様上は「どんな型であってもよい」とされています。
典型的な定義イメージを表にまとめると、次のようになります。
| 環境例 | time_tの実態(例) | ビット数(例) | 備考 |
|---|---|---|---|
| 32bit Linux古い環境 | long | 32bit | 2038年問題の対象 |
| 64bit Linux | long | 64bit | 非常に長期間を表現可能 |
| Windows(Visual C) | __time64_tなど | 64bit | 実装依存の独自名称あり |
重要なのは「time_tの中身の型を決め打ちしない」ことです。
自分でlongやintに勝手にキャストせず、APIが期待するtime_tのまま扱うのが安全です。
time_tが秒数を表す理由
time_tは、多くの実装で「ある時点からの経過秒数」を表します。
この「ある時点」は、エポック(epoch)と呼ばれ、Unix系の環境では通常1970-01-01 00:00:00(UTC)が採用されています。
なぜ秒数なのかというと、以下のような理由があります。
- 加算・減算による時間計算がしやすい
- 整数で表現することで、処理が高速かつ実装が簡単
- 内部表現と、人間が読む日付文字列を分離できる
内部では「ただの秒数」ですが、人が使うときは構造体や文字列に変換して意味のある日時として扱います。

このように、time_tは「タイムスタンプ(時間のスタンプ)」としての役割を担っています。
データベースやログなど、多くのシステムで同様の考え方が採用されています。
time_tとtime関数の関係
time_tを実際に使う入口となるのが、time関数です。
定義はtime.hにあります。
time_t time(time_t *timer);
役割を整理すると、次のようになります。
- 現在のカレンダー時刻を
time_tとして返す - 引数
timerがNULLでなければ、*timerにも同じ値を書き込む - 失敗時は
(time_t)-1を返す
time_tという型は、このtime関数によって値を与えられ、他の時間系APIの共通フォーマットとして機能します。
C言語での現在時刻の取得方法
time関数でtime_tの値を取得する
まずは「現在時刻のtime_t値」を手に入れるところから始めます。

次のサンプルは、現在時刻をtime_tとして取得し、その整数値を表示するだけのシンプルなプログラムです。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
// 現在時刻を取得し、変数nowに格納する
// 引数に &now を渡すことで、戻り値と同じ値が *now にも書き込まれます
now = time(&now);
if (now == (time_t)-1) {
// 取得に失敗した場合の簡易エラーハンドリング
fprintf(stderr, "現在時刻の取得に失敗しました\n");
return 1;
}
// time_tの数値をそのまま表示(環境によって桁数が異なる場合があります)
// printfでtime_tを安全に表示するには、キャストせずに%ldなどを使わないのが理想ですが、
// 多くの環境でtime_tは整数型のため、ここでは(long long)にキャストして表示しています。
printf("time_tとしての現在時刻: %lld\n", (long long)now);
return 0;
}
time_tとしての現在時刻: 1733993730
表示される数値は、環境や実行タイミングによって異なりますが、「エポックからの経過秒数」を表しています。
time_tからtm構造体へ変換(localtimeとgmtime)
そのままの秒数では人間には読みづらいので、年月日や時分秒などの要素に分解する必要があります。
C標準ライブラリでは、struct tmという構造体に分解します。
struct tmの主なメンバは次の通りです。
| フィールド | 意味 | 備考 |
|---|---|---|
| tm_year | 1900年からの経過年数 | 例: 2025年なら125 |
| tm_mon | 0から始まる月(0=1月) | 人に見せるときは+1する |
| tm_mday | 日(1〜31) | |
| tm_hour | 時(0〜23) | |
| tm_min | 分(0〜59) | |
| tm_sec | 秒(0〜60) | うるう秒のため60になる可能性あり |
| tm_wday | 曜日(0=日曜,1=月曜,…) | |
| tm_yday | 年始からの通算日(0〜365) | |
| tm_isdst | 夏時間フラグ(>0,0,<0) | 実装依存 |
time_t → struct tmの変換には主に2つの関数を使います。
localtime: ローカルタイム(タイムゾーンと夏時間を考慮)に変換gmtime: UTC(協定世界時)に変換

サンプルコードを見てみます。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *local;
struct tm *utc;
// 現在時刻をtime_tとして取得
now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "現在時刻の取得に失敗しました\n");
return 1;
}
// ローカル時刻への変換
local = localtime(&now);
if (local == NULL) {
fprintf(stderr, "localtimeに失敗しました\n");
return 1;
}
// UTCへの変換
utc = gmtime(&now);
if (utc == NULL) {
fprintf(stderr, "gmtimeに失敗しました\n");
return 1;
}
// struct tmの値を簡単に表示(年や月は補正が必要)
printf("ローカル時刻: %d-%02d-%02d %02d:%02d:%02d\n",
local->tm_year + 1900, // 1900年からの経過年数なので1900を足す
local->tm_mon + 1, // 0始まりの月なので+1する
local->tm_mday,
local->tm_hour,
local->tm_min,
local->tm_sec);
printf("UTC時刻 : %d-%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;
}
ローカル時刻: 2024-12-12 21:35:30
UTC時刻 : 2024-12-12 12:35:30
同じtime_tから、ローカル時刻とUTC時刻という2通りの解釈が得られることがわかります。
現在時刻を日付文字列に変換(strftimeによるフォーマット)
人に見せたりログに出力したりするには、「自由な書式の文字列」に整形したい場面が多くあります。
そこで使うのがstrftimeです。
基本的な宣言は次の通りです。
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
s: 出力先バッファmax: バッファサイズformat: 書式指定文字列(%Yなど)tm: 日時情報を持つstruct tm

代表的な書式指定子だけ抜粋しておきます。
| 書式 | 意味 | 出力例 |
|---|---|---|
| %Y | 西暦4桁 | 2024 |
| %m | 月(01〜12) | 12 |
| %d | 日(01〜31) | 07 |
| %H | 時(00〜23) | 09 |
| %M | 分(00〜59) | 05 |
| %S | 秒(00〜60) | 59 |
| %z | UTCとの差(±hhmm) | +0900 |
| %Z | タイムゾーン名 | JST, UTCなど |
実際にstrftimeで現在時刻を整形する例です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *local;
char buf[64]; // 日付文字列を格納するバッファ
now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "現在時刻の取得に失敗しました\n");
return 1;
}
local = localtime(&now);
if (local == NULL) {
fprintf(stderr, "localtimeに失敗しました\n");
return 1;
}
// YYYY-MM-DD HH:MM:SS 形式で整形する
if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", local) == 0) {
// バッファ不足などで出力に失敗した場合、戻り値は0になります
fprintf(stderr, "strftimeに失敗しました\n");
return 1;
}
printf("現在時刻(ローカル): %s\n", buf);
return 0;
}
現在時刻(ローカル): 2024-12-12 21:35:30
strftimeを使うと、任意のフォーマットで安全に日付時刻文字列を生成できます。
asctimeとctimeによる簡易な日付文字列出力
strftimeは柔軟ですが、「とりあえず人が読めればよい」という場合に手っ取り早く使える関数がasctimeとctimeです。
asctime:struct tm→ 文字列ctime:time_t→ 文字列(内部でlocaltimeする)
どちらも固定フォーマットの文字列を返します。
形式は以下のようになります。
"Thu Dec 12 21:35:30 2024\n"
サンプルで動きを確認します。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *local;
char *s1;
char *s2;
now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "現在時刻の取得に失敗しました\n");
return 1;
}
// ctimeはtime_tから直接文字列を取得
s1 = ctime(&now);
if (s1 == NULL) {
fprintf(stderr, "ctimeに失敗しました\n");
return 1;
}
// asctimeはstruct tmから文字列を取得
local = localtime(&now);
if (local == NULL) {
fprintf(stderr, "localtimeに失敗しました\n");
return 1;
}
s2 = asctime(local);
if (s2 == NULL) {
fprintf(stderr, "asctimeに失敗しました\n");
return 1;
}
printf("ctime の結果: %s", s1); // 末尾に改行(\n)を含む
printf("asctimeの結果: %s", s2); // 同じく末尾に改行を含む
return 0;
}
ctime の結果: Thu Dec 12 21:35:30 2024
asctimeの結果: Thu Dec 12 21:35:30 2024
注意点として、これらの関数は内部の静的バッファを返すため、スレッド安全でない実装も多く、またフォーマットを変更することもできません。
本番コードではstrftimeの利用を優先し、asctime/ctimeはデバッグ用や簡易表示程度にとどめるのがおすすめです。
time_tと日付表示の実用テクニック
YYYY-MM-DD形式での日時表示
システムログやAPIのレスポンスなどでは、機械にも人にも読みやすいISO 8601風の形式が好まれます。
典型的にはYYYY-MM-DD HH:MM:SSやYYYY-MM-DDTHH:MM:SSZといった書式です。

YYYY-MM-DD HH:MM:SS形式で表示する簡単な関数を例示します。
#include <stdio.h>
#include <time.h>
// time_t値を受け取り、"YYYY-MM-DD HH:MM:SS" 形式で表示する関数
void print_datetime(time_t t) {
struct tm *local;
char buf[32];
local = localtime(&t);
if (local == NULL) {
printf("(時刻変換エラー)\n");
return;
}
if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", local) == 0) {
printf("(フォーマットエラー)\n");
return;
}
printf("%s\n", buf);
}
int main(void) {
time_t now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "現在時刻の取得に失敗しました\n");
return 1;
}
printf("現在日時: ");
print_datetime(now);
return 0;
}
現在日時: 2024-12-12 21:35:30
このように表示部分を関数に切り出すことで、ログ出力など他の箇所でも再利用しやすくなります。
ログ出力でのtime_t活用例
ログでは、人間が読むための日時文字列と機械処理のためのtime_t値の両方を残しておくと便利です。
理由としては次のようなものがあります。
- 文字列は目視確認に便利
- time_t値はソートやフィルタ、差分計算に便利
- 後からタイムゾーン変換を行いたい場合にも元情報として有用
サンプルとして、ログ1行に両方を埋め込む方法を示します。
#include <stdio.h>
#include <time.h>
// 簡易ログ出力関数
void log_message(const char *level, const char *message) {
time_t now = time(NULL);
struct tm *local;
char timestr[32];
if (now == (time_t)-1) {
// 時刻取得失敗時は時刻なしで出力
fprintf(stderr, "[UNKNOWN_TIME] [%s] %s\n", level, message);
return;
}
local = localtime(&now);
if (local == NULL ||
strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", local) == 0) {
fprintf(stderr, "[%lld] [%s] %s\n", (long long)now, level, message);
return;
}
// 人間向け日時 + time_tの生値 + ログレベル + メッセージ
fprintf(stderr, "[%s] [%lld] [%s] %s\n",
timestr, (long long)now, level, message);
}
int main(void) {
log_message("INFO", "アプリケーションを開始しました");
// ここに処理があると仮定
log_message("ERROR", "何らかのエラーが発生しました");
return 0;
}
[2024-12-12 21:35:30] [1733993730] [INFO] アプリケーションを開始しました
[2024-12-12 21:35:30] [1733993730] [ERROR] 何らかのエラーが発生しました
time_tの生値と人間向けの日付文字列を併記する設計は、後からの解析や自動処理にとても役立ちます。
タイムゾーンとtime_tの扱い方
time_t自体にはタイムゾーン情報は含まれていません。
time関数が返す値は、多くの環境でUTC基準の秒数です。
- UTCベースのtime_t値
- localtime: システム設定されたタイムゾーンを用いて「ローカル時刻」に変換
- gmtime: タイムゾーンを考慮せず、UTCとして解釈

タイムゾーンに関するポイントを整理すると、次のようになります。
- time_tは基本的に「絶対的な時刻(UTC)」を表す
- 表示する側の関数(localtimeなど)がタイムゾーンを解釈する
- ログを解析したり、異なる地域のサーバ間で時刻を比較する場合は、UTC表示を用いると誤解が少ない
strftimeで%zや%Zを使うと、タイムゾーン情報も含めて表示できます。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *local;
char buf[64];
if (now == (time_t)-1) {
fprintf(stderr, "現在時刻の取得に失敗しました\n");
return 1;
}
local = localtime(&now);
if (local == NULL) {
fprintf(stderr, "localtimeに失敗しました\n");
return 1;
}
// タイムゾーン情報まで含めて表示
if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z %Z", local) == 0) {
fprintf(stderr, "strftimeに失敗しました\n");
return 1;
}
printf("ローカル時刻+タイムゾーン: %s\n", buf);
return 0;
}
ローカル時刻+タイムゾーン: 2024-12-12 21:35:30 +0900 JST
内部では常にtime_t(UTC秒数)で保持し、表示時にタイムゾーンを適用する設計にすると、グローバルなシステムでもズレや混乱を避けやすくなります。
時刻計算(経過時間・差分)とtime_t
time_tが秒数であることの最大の利点は、加算・減算による時間計算が容易な点です。
代表的な利用は次の2つです。
- 2つの時刻の差分(経過秒数)を求める
- ある時刻からn秒後(または前)の時刻を求める
経過時間を測る例
処理にかかった時間を測定する非常に簡単な方法として、timeを2回呼ぶ方法があります。
#include <stdio.h>
#include <time.h>
// ダミーの重い処理(単純なループ)
void heavy_task(void) {
volatile long long sum = 0; // 最適化で消されないようvolatileを付ける
for (long long i = 0; i < 100000000; ++i) {
sum += i;
}
}
int main(void) {
time_t start, end;
double elapsed;
// 開始時刻を取得
start = time(NULL);
if (start == (time_t)-1) {
fprintf(stderr, "開始時刻の取得に失敗しました\n");
return 1;
}
heavy_task(); // 処理を実行
// 終了時刻を取得
end = time(NULL);
if (end == (time_t)-1) {
fprintf(stderr, "終了時刻の取得に失敗しました\n");
return 1;
}
// difftimeは time_t同士の差を秒数(double)として返します
elapsed = difftime(end, start);
printf("処理に要した時間: %.0f 秒\n", elapsed);
return 0;
}
処理に要した時間: 3 秒
ここではdifftimeを使っていますが、time_tが整数型である保証はないので、end - startのような生の減算よりdifftimeの利用が安全です。
未来や過去の時刻を求める例
今から1時間後の時刻を求める場合、3600秒を足すだけで済みます。
#include <stdio.h>
#include <time.h>
// "YYYY-MM-DD HH:MM:SS" 形式で表示するヘルパー
void print_datetime(const char *label, time_t t) {
struct tm *local = localtime(&t);
char buf[32];
if (local == NULL ||
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", local) == 0) {
printf("%s: (不明)\n", label);
return;
}
printf("%s: %s\n", label, buf);
}
int main(void) {
time_t now = time(NULL);
if (now == (time_t)-1) {
fprintf(stderr, "現在時刻の取得に失敗しました\n");
return 1;
}
time_t one_hour_later = now + 60 * 60; // 1時間後
time_t one_day_before = now - 24 * 60 * 60; // 1日前
print_datetime("現在", now);
print_datetime("1時間後", one_hour_later);
print_datetime("1日前", one_day_before);
return 0;
}
現在: 2024-12-12 21:35:30
1時間後: 2024-12-12 22:35:30
1日前: 2024-12-11 21:35:30
time_tを直接加減算することで、簡潔に時間の前後関係を扱えます。
time_tの32bit問題と対策
2038年問題とは(time_tの32bit制限)
2038年問題は、32bitのtime_tを使っているシステムで発生する、時刻表現のオーバーフロー問題です。

32bit符号付き整数の最大値は2,147,483,647です。
この値を「1970年からの秒数」と解釈すると、次の日時になります。
- 2038-01-19 03:14:07(UTC)
この1秒後、つまり2,147,483,648に達すると、32bit符号付き整数としてはオーバーフローして負の値となり、1901年付近の時刻として扱われてしまいます。
2038年問題は、「32bit幅でtime_tを持っているシステムで、2038年以降の時刻が正しく表現できない問題」と要約できます。
32bit環境でのtime_tの上限と影響
32bit環境でも、実装によっては既にtime_tが64bit拡張されている場合があります。
しかし、古いシステムや組み込み系では依然として32bitのままのことがあります。
具体的な上限を表にしてみます。
| 表現 | 値 |
|---|---|
| 最大time_t(32bit) | 2,147,483,647 |
| 対応するUTC日時 | 2038-01-19 03:14:07 |
| その1秒後 | オーバーフロー発生 |
影響として考えられるのは、次のようなケースです。
- 2038年以降を予約日として扱うシステム(長期予約システムなど)
- 有効期限を長期間先に設定するライセンス管理
- 将来日時を扱うログやバックアップシステム
time_tをそのまま保存しているファイルフォーマットや通信プロトコルも問題となります。
32bitのtime_tで保存してしまうと、後で64bitに移行しても過去のデータの解釈に齟齬が生じる可能性があります。
64bit環境におけるtime_tの扱い
現在主流の64bit OSやコンパイラでは、time_tが64bit幅に拡張されていることが多く、2038年問題を現実的なスケールでほぼ解消しています。
| ビット幅 | 最大値(秒) | おおよその上限年 |
|---|---|---|
| 32bit | 約2.1×10^9 | 2038年 |
| 64bit | 約9.2×10^18 | 約2.9億年先まで |
64bitのtime_tでは、人類の文明スケールをはるかに超える期間を表現できるため、実質的に上限を気にせず利用できます。
しかし、注意すべきは「ビルド設定や互換モード」によっては32bitのtime_tが使われてしまう点です。
例えば一部のUnix系環境では、コンパイル時に_TIME_BITS=64を定義することで、32bitアプリケーションでもtime_tを64bitに切り替える、といった仕組みがあります。
自分の開発環境でtime_tが何ビットなのかを確認することが重要です。
#include <stdio.h>
#include <time.h>
int main(void) {
printf("sizeof(time_t) = %zu バイト\n", sizeof(time_t));
return 0;
}
sizeof(time_t) = 8 バイト
このように8バイト(64bit)であれば、少なくともtime_tのビット幅に関する2038年問題は回避できています。
time_tの代替と将来を見据えた実装指針
将来を見据えた設計のために、time_tの扱い方で意識しておきたいポイントを整理します。
1. time_tの中身の型を決め打ちしない
NGな例:
int my_time = (int)time(NULL);printf("%ld\n", (long)time(NULL));
これらは環境によっては情報が失われたり、警告・未定義動作の原因になります。
代わりに、time_tをそのまま保持するか、整数として保存したいならint64_tなど明示的な型に変換しましょう。
#include <stdio.h>
#include <time.h>
#include <stdint.h>
int main(void) {
time_t now = time(NULL);
if (now == (time_t)-1) {
return 1;
}
// 明示的にint64_tへ変換して保存する例
int64_t stamp = (int64_t)now;
printf("UNIXタイムスタンプ(64bit整数): %lld\n", (long long)stamp);
return 0;
}
2. 永続化やプロトコルではビット幅を明示する
ファイルやネットワークで時刻情報をやりとりする場合、「time_tそのもの」をそのまま書き出すのは避けた方が安全です。
理由は、将来別の環境(ビット幅の違うtime_t)で読み込むときに、意味が変わってしまう可能性があるためです。
代わりに:
- UTCエポック秒を
int64_tとして保存する - 文字列形式(ISO 8601など)で保存する
といった方法を選ぶと、長期運用に耐えやすくなります。
3. 高精度が必要な場合は別の型を併用する
time_tは秒単位であることが多いため、ミリ秒やナノ秒精度が必要な場合には不十分です。
POSIX環境ではstruct timespecなど、より高精度な型が提供されています。
time_t: 秒単位(多くの実装)struct timeval: 秒 + マイクロ秒struct timespec: 秒 + ナノ秒
将来性を考えるなら、「内部では高精度な型で保持し、外部インターフェースとして秒単位のtime_tやint64_tを提供する」といった多層構造も選択肢になります。
4. 2038年問題を意識したマイグレーション
既存システムで32bitのtime_tを使っている場合、以下のようなステップでの移行が考えられます。
- 現状のtime_tのビット幅と利用箇所を把握する
- ファイルフォーマットや通信プロトコルでtime_tが使われていないか確認する
- 必要であれば、64bit整数や文字列形式に移行する
新規開発では、最初から64bitの時刻表現を前提に設計しておくと、将来の悩みを減らすことができます。
まとめ
time_tはC言語における時間処理の基盤となる型であり、その多くはエポックからの経過秒数として実装されています。
timeで現在時刻を取得し、localtime/gmtimeでstruct tmに変換、strftimeで任意フォーマットの文字列に整形するという流れを押さえれば、実用的な日時表示やログ出力が行えます。
一方で、32bitのtime_tには2038年問題という制約があり、ビット幅を決め打ちしない・永続化時にはint64_tや文字列を使うといった実装指針が重要になります。
内部は単純な秒数として扱い、表示時にタイムゾーンやフォーマットを適用する設計を意識することで、堅牢で将来に強い時間処理が実現できます。
