C言語で日付や時刻を扱うときには、必ずと言ってよいほどtime_t型が登場します。
この記事では、C言語初心者の方を対象に、time_t型の基本から、標準ライブラリとの関係、実際の使い方、さらに2038年問題などの注意点までを丁寧に解説します。
コード例も交えながら、秒単位の時刻処理の流れを理解できるように説明していきます。
time_t型とは何かを理解しよう
time_t型の基本
C言語で時刻を扱うときには、標準ライブラリが用意している専用の型を使います。
その代表がtime_t型です。
C言語のヘッダファイルtime.hには、時刻に関する関数や型が宣言されています。
その中で、日付や時刻の「絶対的な位置」を表すために使われるのがtime_t型です。
一般的には、次のようなイメージで考えると分かりやすいです。
- time_t型はある基準時刻からの経過時間を表します。
- 多くの環境では、UTC(協定世界時)の1970年1月1日0時0分0秒からの経過秒数として実装されています。
- time_t型は、プラットフォーム依存の型です。整数か浮動小数か、ビット数は何ビットかなどは、環境によって異なります。
このため、プログラマはtime_tの中身を直接意識しすぎず、標準ライブラリ関数を通じて扱うことが推奨されます。
time_t型が使われる標準ライブラリ
time_t型は、C言語標準ライブラリのtime.hに定義されている多くの関数とセットで使われます。
代表的な関数を整理しておきます。
| 関数名 | 役割 | 主な引数・戻り値 |
|---|---|---|
time | 現在時刻を取得する | 戻り値がtime_t型 |
difftime | 2つのtime_tの差を秒数で返す | 引数にtime_tを2つ、戻り値はdouble |
localtime | time_tからローカル時刻の構造体へ変換 | 引数がtime_tへのポインタ、戻り値はstruct tm* |
gmtime | time_tからUTC時刻の構造体へ変換 | 引数がtime_tへのポインタ、戻り値はstruct tm* |
mktime | 構造体tmからtime_tへ変換 | 引数がstruct tm*、戻り値がtime_t |
ctime | time_tを可読な文字列へ変換 | 引数がtime_tへのポインタ、戻り値はchar* |
strftime | struct tmをフォーマットした文字列に変換 | 引数にstruct tm*など |
実際のプログラムでは、time_tで現在時刻を取得し、それをstruct tmや文字列に変換して表示するという流れで利用することが多いです。
C言語でのtime_t型の定義と役割
time_t型は整数か浮動小数か
C言語の規格では、time_t型の中身について厳密な形式は決めていません。
そのため、time_t型が整数なのか浮動小数なのかは、処理系(コンパイラやOS)に依存します。
ただし、ほとんどの一般的な環境では次のようになっています。
- 多くのUnix系OS(Linuxなど)では、符号付き整数型として実装されています。
- 32ビット環境では32ビット整数、64ビット環境では64ビット整数であることが多いです。
- Windowsなどの一部環境では、実装が異なる場合がありますが、やはり整数型であることが主流です。
C言語プログラマとしては、time_tの「形式」を決め打ちしないことが重要です。
例えば、勝手にlong型にキャストして扱うと、環境によっては不具合の原因になります。
ポイントは「time_tは、time_tとして扱う」ことです。
比較や計算はtime_t同士で行い、表示には後述する正しい方法を使うようにします。
time_t型が表す「秒」と「UTC時刻」の関係
time_tは「ある基準時刻からの経過時間」を表すと述べました。
この基準時刻は、一般にEpoch(エポック)と呼ばれます。
多くのUnix系システムでは、次のようになっています。
- 基準時刻はUTCの1970年1月1日0時0分0秒
- time_tの値が
0のとき、それがこの基準時刻に対応します。 - time_tが
1増えると、1秒後を意味します。 - time_tが
-1なら、基準時刻の1秒前(1969年12月31日23時59分59秒)を表します。
ここで重要なのは、time_tはUTC基準での秒数だという点です。
日本時間(JST)などのローカル時刻とのズレは、この後に登場するlocaltime関数などで補正されます。
また、実装によっては、うるう秒をどう扱うかが異なる場合がありますが、C言語の規格上は秒単位の大まかな時刻が扱えればよいというスタンスです。
細かいうるう秒の扱いが必要な高度なアプリケーションでは、別のライブラリを利用することもあります。
time_t型で表現できる範囲と2038年問題
time_t型は、ビット数に応じて表現できる範囲が決まります。
特に32ビット符号付き整数で実装されたtime_tには有名な問題があります。
それが2038年問題です。
32ビット符号付き整数のtime_tは、おおまかに次の範囲を表せます。
- 最小値: 約
-2,147,483,648秒 - 最大値: 約
2,147,483,647秒
Unixエポック(1970年1月1日)から数えると、最大値のタイムスタンプは2038年1月19日3時14分7秒(UTC)付近になります。
この時刻を超えると、値がオーバーフローして負の値に巻き戻ってしまう可能性があります。
これが2038年問題です。
一方、64ビットのtime_tであれば、表現できる範囲は非常に広くなり、現実的には十分すぎるほどの未来や過去まで扱えます。
現在では、多くの64ビットOSや環境で、time_tは64ビット整数として実装されており、2038年問題は事実上解消されつつあります。
ただし、組み込み機器や古い32ビット環境では、いまだに32ビットtime_tが使われていることもあるため、長期間稼働するシステムでは注意が必要です。
time_t型の基本的な使い方
time関数で現在時刻を取得する
time_t型を実際に使う出発点となるのがtime関数です。
time関数は、現在の暦時刻(カレンダー時刻)を取得し、その値をtime_t型で返します。
time関数の基本的な使い方
標準的な使い方を示すサンプルコードを見てみましょう。
#include <stdio.h> // printf
#include <time.h> // time, time_t
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値を取得しました。\n");
return 0;
}
このコードでは、time関数の戻り値をそのままtime_t型の変数に代入しています。
実際には、ここからさらに表示や変換を行っていくことになります。
time_t型の値をprintfで表示する方法
C言語初心者がよく戸惑うポイントの一つが、time_t型の値をprintfでどう表示するかという点です。
理由は、time_tの実体の型が環境依存だからです。
安全な表示方法1: 一度キャストして表示する
最も簡単な方法は、十分に大きな整数型にキャストしてから表示するやり方です。
例えば、多くの環境でlong longは64ビット整数なので、次のようにします。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
if (now == (time_t)(-1)) {
printf("現在時刻の取得に失敗しました。\n");
return 1;
}
// long long にキャストしてから表示する方法
printf("time_tの値(秒): %lld\n", (long long)now);
return 0;
}
この方法は、実務でもよく使われているシンプルなテクニックです。
ただし、すべての処理系でlong longがtime_tを完全にカバーできるとは限らない点には注意が必要です(とはいえ、現代的な一般環境ではほぼ問題になりません)。
安全な表示方法2: 標準で提供されるマクロ(環境依存)
環境によっては、time_tの表示用に専用のマクロを提供していることがあります。
例えば、POSIX環境では%jdと(intmax_t)キャストを組み合わせる方法などがありますが、ここではC標準の範囲を超えるため詳しい説明は控えます。
初心者のうちは、「time_tはlong longにキャストして%lldで出力する」というスタイルを一つの目安として覚えておくとよいです。
time_t型とdifftime関数で経過時間を計算する
2つの時刻の差(経過時間)を秒単位で知りたい場合は、difftime関数を使います。
difftimeは、2つのtime_tを受け取り、その差をdouble型で返してくれます。
difftimeを使った経過秒数の計測
次のコードでは、何らかの処理にかかった時間を測定する例を示します。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t start, end;
double elapsed;
// 計測開始時刻を取得
start = time(NULL);
if (start == (time_t)(-1)) {
printf("開始時刻の取得に失敗しました。\n");
return 1;
}
// ここで何らかの処理を行う(例として簡単なループ)
for (volatile long i = 0; i < 100000000L; i++) {
// 何もしないループで時間を少し消費する
}
// 計測終了時刻を取得
end = time(NULL);
if (end == (time_t)(-1)) {
printf("終了時刻の取得に失敗しました。\n");
return 1;
}
// difftimeで2つの時刻の差を計算
elapsed = difftime(end, start);
printf("処理にかかった時間は %.0f 秒です。\n", elapsed);
return 0;
}
このように、time_tの差を自分で引き算するのではなく、difftime関数を使うのが推奨されます。
これは、将来time_tの内部表現が変わった場合にも安全であることが理由です。
time_t型からstruct tm型への変換
time_tは機械向けの秒数であり、そのままでは年・月・日・時・分・秒などの情報が分かりません。
人間にとって分かりやすい形式にするには、構造体tm(struct tm)に変換します。
<struct tm>は次のようなメンバを持つ構造体です(詳細は簡略化しています)。
tm_year: 1900年からの経過年数tm_mon: 0から11(0が1月、11が12月)tm_mday: 日(1〜31)tm_hour: 時(0〜23)tm_min: 分(0〜59)tm_sec: 秒(0〜60)- など
この変換には、localtimeまたはgmtimeを使います。
localtimeでローカル時刻に変換する
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *local;
if (now == (time_t)(-1)) {
printf("現在時刻の取得に失敗しました。\n");
return 1;
}
// time_tからローカル時刻のstruct tmへ変換
local = localtime(&now);
if (local == NULL) {
printf("localtimeによる変換に失敗しました。\n");
return 1;
}
// 年月日時分秒をそれぞれ取り出して表示
printf("ローカル時刻: %d年 %d月 %d日 %d時 %d分 %d秒\n",
local->tm_year + 1900, // 1900年からの年数なので1900を足す
local->tm_mon + 1, // 0が1月なので1を足す
local->tm_mday,
local->tm_hour,
local->tm_min,
local->tm_sec);
return 0;
}
ここで、tm_yearとtm_monに補正が必要な点に注意してください。
C言語では、このように0や1900からのオフセットで表現されていることが多いです。
gmtimeでUTC時刻に変換する
gmtimeを使うと、UTC(協定世界時)での時刻をstruct tmに変換できます。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *utc;
if (now == (time_t)(-1)) {
printf("現在時刻の取得に失敗しました。\n");
return 1;
}
// time_tからUTC時刻のstruct tmへ変換
utc = gmtime(&now);
if (utc == NULL) {
printf("gmtimeによる変換に失敗しました。\n");
return 1;
}
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;
}
ローカル時刻とUTC時刻の差は、タイムゾーンやサマータイムの設定に依存します。
世界共通の基準時刻で扱いたいときはUTC、ユーザに見せるときはローカル時刻という使い分けが一般的です。
struct tm型からtime_t型へ変換する
逆に、「指定した年月日時分秒をtime_tに変換したい」場合もあります。
このときに使うのがmktime関数です。
mktimeは、struct tm*を受け取り、それが表すローカル時刻をtime_tに変換してくれます。
また、構造体の中身も正規化してくれます。
mktimeを使った変換の例
#include <stdio.h>
#include <time.h>
int main(void) {
struct tm t;
time_t tt;
// 構造体tmを0で初期化
// これにより、未使用フィールドが0になる
t.tm_sec = 0; // 秒
t.tm_min = 0; // 分
t.tm_hour = 12; // 時(12時)
t.tm_mday = 1; // 日(1日)
t.tm_mon = 0; // 月(0が1月)
t.tm_year = 2025 - 1900; // 年(1900年からの年数)
// サマータイム情報などは未設定(自動判定させるには-1を指定)
t.tm_isdst = -1;
// struct tm から time_t へ変換
tt = mktime(&t);
if (tt == (time_t)(-1)) {
printf("mktimeによる変換に失敗しました。\n");
return 1;
}
// 変換されたtime_tの値を表示
printf("指定したローカル時刻のtime_t値: %lld\n", (long long)tt);
return 0;
}
このように、struct tmとmktimeを組み合わせることで、任意の年月日時分秒をtime_tに変換できます。
これにより、日付同士の比較や経過日数の計算などがやりやすくなります。
time_t型を使うときの注意点と実践的なコツ
time_t型の比較と演算で気をつけること
time_t同士の比較(早い遅い)や差の計算は、基本的には通常の整数と同じように扱えます。
ただし、次の点に注意すると安全です。
まず、比較の例です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t t1 = time(NULL);
time_t t2;
if (t1 == (time_t)(-1)) {
printf("時刻取得に失敗しました。\n");
return 1;
}
// 少し待つための簡単なループ(本当の待機にはsleepなどを使う)
for (volatile long i = 0; i < 100000000L; i++) {}
t2 = time(NULL);
if (t2 == (time_t)(-1)) {
printf("時刻取得に失敗しました。\n");
return 1;
}
if (t1 < t2) {
printf("t1の方が過去の時刻です。\n");
} else if (t1 > t2) {
printf("t1の方が未来の時刻です。\n");
} else {
printf("t1とt2は同じ時刻です。\n");
}
// 差を計算したいときはdifftimeを使うのが推奨
printf("差(秒): %.0f\n", difftime(t2, t1));
return 0;
}
差の計算はdifftimeを使うと述べましたが、実際には多くの環境でt2 - t1と書いても動きます。
しかし、C規格はtime_tの内部表現を保証していないため、将来的な互換性や可搬性を考えるとdifftimeを使うほうが安全です。
また、time_tに大きなオフセットを足し引きすると、オーバーフローの危険があります。
例えば、何十年も先の時刻を計算するときには、本当にその範囲がtime_tで表現できるかを意識する必要があります。
32ビットと64ビットでのtime_t型の違い
前述の通り、time_tのビット数は環境によって異なります。
その代表例が、32ビットと64ビットの違いです。
- 32ビットtime_t: おおよそ1901年〜2038年の間しか表現できません。
- 64ビットtime_t: 遥か昔から遥か未来まで表現できます。
この違いは、次のような実践的な影響を持ちます。
1つ目は「2038年問題」です。
32ビットtime_tを使っているシステムでは、2038年以降の時刻を扱う必要があるプログラムを設計するときに問題が発生します。
2つ目は「型のサイズ依存コードを書かない」ことの重要性です。
例えば、次のようなコードは危険です。
// 悪い例: time_tを勝手にlong型と決めつけている
long t = time(NULL); // 環境によってはtime_tの方がlongより大きいかもしれない
このように、特定の整数型にtime_tを代入したりキャストしたりするコードは、環境を変えたときに不具合の元になります。
time_tはtime_tのまま保持し、必要なときだけ慎重にキャストするという方針を心掛けてください。
C言語初心者がハマりやすいtime_t型の落とし穴
最後に、time_t型を扱う際にC言語初心者がハマりやすいポイントをいくつかまとめておきます。
1つ目は「struct tmのフィールドの意味を間違える」ことです。
特に次の2つがよく間違えられます。
tm_yearは西暦そのものではなく、1900年からの年数である。tm_monは1月が0、12月が11である。
このため、表示するときやmktimeに渡すときには、必ず+1900、+1などの補正を行う必要があります。
2つ目は「time_tを文字列にしようとして直接printfする」問題です。
printf("%d", now);のように書くと、環境によってはフォーマット指定子と実際の型が一致せず、未定義動作になります。
必ずlong longなどにキャストし、対応するフォーマット指定子を使うようにしてください。
3つ目は「localtimeやgmtimeが返すポインタの扱い」です。
これらの関数は、静的領域にある構造体へのポインタを返します。
つまり、
- 毎回同じ領域が使われる。
- スレッドセーフではない環境もある。
- freeしてはいけない。
初心者のうちは、「返ってきたstruct tm*を、そのまま使うだけでよく、解放は不要」と覚えておくとよいでしょう。
4つ目は「時刻の扱いでタイムゾーンを意識しない」ことです。
サーバ側でUTCを使うのか、ユーザにはローカル時刻を見せるのかなど、設計時に決めておかないと、表示がずれたり比較が狂ったりします。
内部処理はUTC(time_tやgmtime)、表示はローカル(localtime)という方針は、多くのシステムで採用されています。
まとめ
この記事では、C言語初心者向けにtime_t型とは何かを解説し、その定義や役割、UTCとの関係、2038年問題まで触れました。
続いて、time関数での現在時刻取得、printfでの安全な表示方法、difftimeによる経過時間計算、struct tmとの相互変換など、実践的な使い方も紹介しました。
最後に、ビット数の違いや構造体tmの扱いなど、ハマりやすいポイントと注意点を整理しました。
time_t型は一度流れを理解すれば非常に便利ですので、サンプルコードを実行しながら少しずつ慣れていってください。
