C言語で現在時刻を扱うとき、time_t型やctime関数は必ずといってよいほど登場します。
ただ、初心者の方にとっては「数値の時刻」と「人間が読める日付文字列」のつながりがイメージしづらいところです。
本記事では、ctimeでtime_tを読みやすい文字列に変換する基本から、「〇年〇月〇日〇時〇分」形式に整える実践的な方法まで、順を追って丁寧に解説します。
ctimeとは何か
ctimeの基本仕様と役割
C言語のctime関数は、「time_t型で表された時刻」を「人間が読める1行の文字列」に変換する標準ライブラリ関数です。
time_tは秒数による機械的な表現ですが、そのままでは人が読みづらいため、ctimeで例えば次のような文字列に変換します。
変換例
Wed Nov 22 13:45:10 2025\n\0
このように、曜日・月名・日・時刻・年が1行の英語表記で並んだ文字列が得られます。
ctimeは「とりあえず時刻を表示したい」ときに最も手軽に使える関数ですが、出力形式の自由度は高くありません。
後半で、より柔軟な方法との違いも説明します。
time_tとカレンダー時刻(年月日・時刻)の関係
C言語では、内部的な時刻表現と人間向けのカレンダー表現を分けて考えます。
- 内部表現:
time_t型
多くの環境では、ある基準時刻からの経過秒数を表す整数型です。基準時刻は一般的に1970-01-01 00:00:00(UTC)です。 - カレンダー表現: 年・月・日・時・分・秒など
struct tm構造体で表現されます。
概念的な関係は次のようになります。
time_t(秒数)
→ (変換)
→ struct tm(年月日・時刻の各フィールド)
→ (整形)
→ 「2025年11月22日13時45分」などの文字列
ctimeは、この「time_t → 文字列」の変換を一度に行う関数です。
ただし出力形式は固定で、英語表記の日付になります。
C標準ライブラリでのctimeの宣言と使い方
ctime関数はtime.hヘッダで宣言されています。
標準的な宣言は次の通りです。
#include <time.h>
char *ctime(const time_t *timer);
主なポイントは次の通りです。
- 引数は
time_t型へのポインタconst time_t *timerです。 - 戻り値は「静的な内部バッファ」へのポインタであり、プログラマが
freeで解放してはいけません。 - 失敗時には
NULLを返します。
典型的な使い方は、次のようにtimeで現在時刻をtime_tとして取得し、ctimeで文字列に変換して標準出力に表示する形です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now; // 現在時刻を格納する変数
char *str_time; // 文字列へのポインタ
time(&now); // 現在時刻を取得
str_time = ctime(&now); // time_t → 文字列へ変換
if (str_time == NULL) {
// 変換に失敗した場合のエラーメッセージ
printf("ctime failed.\n");
return 1;
}
// 変換結果を表示
printf("%s", str_time);
return 0;
}
このプログラムは環境によって異なりますが、例えば次のような出力になります。
Sat Nov 22 13:45:10 2025
ここまでがctimeの基本的な役割と位置づけです。
ctimeを使ったtime_tからの文字列変換
time関数で現在時刻(time_t)を取得する手順
現在時刻を取得するには、time関数を使います。
宣言はtime.hにあります。
#include <time.h>
time_t time(time_t *timer);
使い方は2通りあります。
time_t変数を用意し、そのアドレスを渡す方法NULLを渡し、戻り値のtime_tを受け取る方法
初心者の方には、「変数を用意してアドレスを渡す」方法の方が、ポインタと戻り値の関係を理解しやすいと思います。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
// 現在時刻をtime_tとして取得する
if (time(&now) == (time_t)-1) {
// 取得に失敗した場合
printf("time failed.\n");
return 1;
}
printf("time_t値: %ld\n", (long)now);
return 0;
}
time_t値: 1769118310
この数値は、基準時刻からの秒数を表しており、人間にとってはそのままでは分かりづらいので、次にctimeで読みやすくします。
ctimeでtime_tを「文字列」に変換する基本コード例
timeで取得したtime_tを、そのままctimeに渡す基本的なコード例を示します。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now; // 現在時刻(内部表現)
char *str_time; // 文字列表現へのポインタ
// 現在時刻を取得
if (time(&now) == (time_t)-1) {
printf("time failed.\n");
return 1;
}
// time_t → 文字列へ変換
str_time = ctime(&now);
// ctimeは失敗するとNULLを返す可能性がある
if (str_time == NULL) {
printf("ctime failed.\n");
return 1;
}
// 結果を表示
printf("現在時刻(文字列): %s", str_time);
// str_timeは内部バッファを指しているので、freeしてはいけない
// free(str_time); // ← これは絶対にNG
return 0;
}
現在時刻(文字列): Sat Nov 22 13:45:10 2025
ここまでで「time → ctime → printf」の基本パターンが理解できれば、時刻表示の第一歩はクリアです。
ctimeが返す文字列の形式
ctimeが返す文字列の形式は、C標準規格でほぼ決まっています。
典型的には次のようになります。
Www Mmm dd hh:mm:ss yyyy\n\0
各部分の意味は次の通りです。
| 部分 | 意味 | 例 |
|---|---|---|
Www | 曜日の英語略称 | Sun, Mon, Tue, Wed, Thu, Fri, Sat |
Mmm | 月の英語略称 | Jan, Feb, Mar, …, Dec |
dd | 日(2桁) | 01〜31 |
hh | 時(2桁・24時間制) | 00〜23 |
mm | 分(2桁) | 00〜59 |
ss | 秒(2桁) | 00〜60(うるう秒) |
yyyy | 西暦年(4桁) | 2025など |
\n | 改行 | |
\0 | 文字列終端 |
具体的には、次のような文字列になります。
Sat Nov 22 13:45:10 2025\n\0
この形式は固定で、日本語の「年」「月」「日」などは入りません。
そのため、日本語形式にしたい場合は、後述のstrftimeなどを使う必要があります。
文字列末尾の改行文字(\n)に注意するポイント
ctimeの戻り値の文字列には末尾に\n(改行)が含まれています。
つまり、実際の配列内容はおおよそ次のようになっています。
"Sat Nov 22 13:45:10 2025\n\0"
そのため、printfで次のように書くと、改行が2回入ってしまいます。
printf("%s\n", str_time); // str_timeの中にすでに\nがある
プログラム例でprintf("%s", str_time);と書いていたのは、このためです。
よくある混乱として「なんとなく空行が1行多い」などの現象があります。
原因がctimeの末尾の改行であることを覚えておくと、デバッグしやすくなります。
改行を削除して使いたい場合は、末尾の\nを\0で置き換える処理を自分で行います。
#include <stdio.h>
#include <time.h>
#include <string.h>
int main(void) {
time_t now;
char *str_time;
time(&now);
str_time = ctime(&now);
if (str_time == NULL) {
printf("ctime failed.\n");
return 1;
}
// 末尾の'\n'を探して'\0'に置き換える
char *p = strchr(str_time, '\n'); // 改行文字の位置を探す
if (p != NULL) {
*p = '\0'; // 終端文字に置き換える
}
printf("改行なし: %s\n", str_time);
return 0;
}
改行なし: Sat Nov 22 13:45:10 2025
このように改行を消せば、他の文字列と連結しやすくなります。
「〇年〇月〇日〇時〇分」形式に整形する方法
struct tmへの変換(localtime)とctimeとの違い
ctimeは「time_t → 1つの固定形式の文字列」に変換してくれますが、出力形式の自由度がありません。
一方で、struct tmに変換すると「年・月・日・時・分・秒などを個別のメンバとして扱える」ようになります。
struct tmへの変換にはlocaltimeなどの関数を使います。
#include <time.h>
struct tm *localtime(const time_t *timer);
localtimeの主な役割は、time_tを「ローカル時刻のカレンダー表現」に変換することです。
戻り値は静的なstruct tmへのポインタです。
struct tmの主要なメンバは次の通りです。
| フィールド | 意味 | 値の範囲 |
|---|---|---|
tm_year | 西暦年から1900を引いた値 | 例えば2025年なら125 |
tm_mon | 月(0〜11) | 0が1月、11が12月 |
tm_mday | 日(1〜31) | |
tm_hour | 時(0〜23) | |
tm_min | 分(0〜59) | |
tm_sec | 秒(0〜60) |
たとえば、「2025年11月22日13時45分10秒」の場合、struct tmは概ね次のような値を持ちます。
tm_year = 2025 - 1900 = 125tm_mon = 10(0始まりなので11月は10)tm_mday = 22tm_hour = 13tm_min = 45tm_sec = 10
ctimeは内部で「time_t → struct tm → 文字列」という流れをまとめて行っているイメージを持つと分かりやすいです。
struct tmから年月日・時刻を取り出す方法
localtimeで取得したstruct tmから、各フィールドを取り出して、自分でprintfなどで整形することもできます。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *lt; // local timeへのポインタ
time(&now);
// time_t → struct tm(ローカル時間)に変換
lt = localtime(&now);
if (lt == NULL) {
printf("localtime failed.\n");
return 1;
}
// tm_yearは1900年からの年数、tm_monは0〜11(月)
int year = lt->tm_year + 1900; // 西暦年に変換
int month = lt->tm_mon + 1; // 1〜12に変換
int day = lt->tm_mday;
int hour = lt->tm_hour;
int min = lt->tm_min;
int sec = lt->tm_sec;
// 日本語で年月日時分秒を表示
printf("%d年%d月%d日 %d時%d分%d秒\n",
year, month, day, hour, min, sec);
return 0;
}
2025年11月22日 13時45分10秒
このようにstruct tmを使えば、日本語を含む任意の形式で表示が可能になります。
ただし、より複雑な書式(ゼロ埋め、曜日名、24時間・12時間など)をきれいに書くためには、次に説明するstrftimeを使う方が便利です。
strftimeで「〇年〇月〇日〇時〇分」にフォーマットする
strftime関数は、struct tmから指定した書式に従った文字列を生成する関数です。
宣言は次の通りです。
#include <time.h>
size_t strftime(char *s, size_t maxsize,
const char *format,
const struct tm *timeptr);
主な引数の意味は次のようになります。
s: 出力先の文字配列maxsize: 配列sのサイズ(バイト数)format: 書式文字列(例:"%Y年%m月%d日 %H時%M分")timeptr: 変換元のstruct tmへのポインタ
「〇年〇月〇日〇時〇分」形式に整形する具体例を示します。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *lt;
char buf[64]; // 出力バッファ(十分なサイズを確保)
time(&now);
// time_t → struct tmへ変換(ローカル時刻)
lt = localtime(&now);
if (lt == NULL) {
printf("localtime failed.\n");
return 1;
}
// 「YYYY年MM月DD日 HH時MM分」形式で整形
// %Y: 4桁の西暦年、%m: 2桁の月、%d: 2桁の日
// %H: 2桁の時(24時間制)、%M: 2桁の分
if (strftime(buf, sizeof(buf),
"%Y年%m月%d日 %H時%M分",
lt) == 0) {
// バッファサイズ不足などで0が返る
printf("strftime failed.\n");
return 1;
}
printf("整形結果: %s\n", buf);
return 0;
}
整形結果: 2025年11月22日 13時45分
このようにstrftimeを使うと、「〇年〇月〇日〇時〇分」といった日本語形式が簡単に作れます。
よく使う書式指定子の例を簡単にまとめておきます。
| 指定子 | 意味 | 例 |
|---|---|---|
%Y | 西暦年(4桁) | 2025 |
%m | 月(2桁) | 01〜12 |
%d | 日(2桁) | 01〜31 |
%H | 時(24時間制・2桁) | 00〜23 |
%M | 分(2桁) | 00〜59 |
%S | 秒(2桁) | 00〜60 |
和暦・24時間表示など出力形式をカスタマイズするコツ
strftimeを応用すると、出力形式を自由にカスタマイズできます。
ここでは、いくつか代表的なカスタマイズ方法を紹介します。
24時間表示と12時間表示
strftimeでは、次のように24時間制と12時間制を使い分けられます。
- 24時間制:
%H(00〜23) - 12時間制:
%I(01〜12)と%p(AM/PM)
たとえば「13時45分」を「午後01時45分」のように表示したい場合、地域設定(ロケール)によりますが、次のような書式が考えられます。
strftime(buf, sizeof(buf), "%p %I時%M分", lt);
ただし%cst-code>%pで日本語の「午前」「午後」が出るかどうかはロケール設定に依存します。
環境によっては「AM」「PM」になることもあります。
曜日を含めた表示
strftimeには曜日用の指定子もあります。
%a: 曜日の省略名(例: Sun, Mon)%A: 曜日の完全名(例: Sunday)
日本語の「(土)」のような形式にしたい場合は、自分で配列を用意するのが確実です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *lt;
char buf[64];
const char *week_jp[] = {"日", "月", "火", "水", "木", "金", "土"};
time(&now);
lt = localtime(&now);
if (lt == NULL) {
printf("localtime failed.\n");
return 1;
}
// 基本部分をstrftimeで作る
if (strftime(buf, sizeof(buf),
"%Y年%m月%d日 %H時%M分",
lt) == 0) {
printf("strftime failed.\n");
return 1;
}
// tm_wdayは0(日)〜6(土)
int wday = lt->tm_wday;
// 曜日を付けて表示(例: 2025年11月22日 13時45分(土))
printf("%s(%s)\n", buf, week_jp[wday]);
return 0;
}
2025年11月22日 13時45分(土)
曜日のような「ロケール依存の要素」は、自分でテーブルを持つとコントロールしやすいです。
和暦表示の考え方
標準のstrftimeには、汎用的な「和暦」サポートはありません。
和暦表示をしたい場合は、自分で西暦と元号の対応を計算する必要があります。
簡略的な例として、令和のみ対応するコードイメージを示します(実務で使う場合は元号の境界などを厳密に扱う必要があります)。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *lt;
int year, month, day;
int reiwa_year; // 令和何年かを格納
time(&now);
lt = localtime(&now);
if (lt == NULL) {
printf("localtime failed.\n");
return 1;
}
year = lt->tm_year + 1900;
month = lt->tm_mon + 1;
day = lt->tm_mday;
// 非常に単純化した令和判定(2019年5月1日以降を令和とする)
if (year > 2019 ||
(year == 2019 && (month > 5 || (month == 5 && day >= 1)))) {
reiwa_year = year - 2018; // 2019年が令和元年
printf("令和%d年%d月%d日\n", reiwa_year, month, day);
} else {
// 令和以前は単純に西暦で表示(簡略例)
printf("%d年%d月%d日(令和以前)\n", year, month, day);
}
return 0;
}
令和7年11月22日
このように和暦は「西暦→元号」の自前ロジックが必要であることに注意してください。
ctime使用時の注意点とよくある落とし穴
返されるポインタと内部バッファの扱い
ctimeの戻り値は「ライブラリ内部で静的に確保されたバッファ」へのポインタです。
これには次のような重要な性質があります。
- プログラマが
freeしてはいけない
バッファは自分でmallocしたものではないため、解放してはいけません。解放すると未定義動作となります。 - 次に
ctimeを呼び出すと同じバッファに上書きされる
複数回ctimeを呼び出すと、以前の結果は新しい結果で上書きされます。
例として、2つのtime_tをctimeで変換し、それぞれの文字列を保存したつもりが、実は同じ内容になってしまうケースを考えてみます。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t t1 = 0; // 1970-01-01 00:00:00(UTC)付近
time_t t2 = 24 * 60 * 60; // 1日後
char *s1 = ctime(&t1);
char *s2 = ctime(&t2);
// s1とs2は同じ内部バッファを指している可能性が高い
printf("s1: %s", s1);
printf("s2: %s", s2);
return 0;
}
出力例(環境によりますが):
s1: Fri Jan 2 00:00:00 1970
s2: Fri Jan 2 00:00:00 1970
このように、s1とs2が同じ内容になってしまうことがあります。
必要に応じて自分のバッファにstrcpyなどでコピーしてから使うのが安全です。
#include <stdio.h>
#include <time.h>
#include <string.h>
int main(void) {
time_t t1 = 0;
time_t t2 = 24 * 60 * 60;
char buf1[32];
char buf2[32];
// ctimeの結果を自前のバッファにコピーする
strncpy(buf1, ctime(&t1), sizeof(buf1) - 1);
buf1[sizeof(buf1) - 1] = '\0';
strncpy(buf2, ctime(&t2), sizeof(buf2) - 1);
buf2[sizeof(buf2) - 1] = '\0';
printf("buf1: %s", buf1);
printf("buf2: %s", buf2);
return 0;
}
内部バッファのライフタイムと所有権を意識することが、ctimeを安全に扱う鍵です。
スレッド安全性
ctimeとlocaltimeは、多くの処理系でスレッド安全ではありません。
理由はどちらも内部で静的なバッファを使っており、複数スレッドから同時に利用するとデータが競合する可能性があるからです。
スレッド安全な代替関数として、POSIX環境などでは次の関数が用意されていることがあります。
ctime_r:ctimeのスレッド安全版localtime_r:localtimeのスレッド安全版
これらは呼び出し側がバッファやstruct tmを用意して渡す形になっています。
例としてctime_rの使い方の雰囲気を示します(環境によってシグネチャが異なることがあります)。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
char buf[32]; // 呼び出し側がバッファを用意
char *ret;
time(&now);
// ctime_r(スレッド安全版)の呼び出し例
ret = ctime_r(&now, buf);
if (ret == NULL) {
printf("ctime_r failed.\n");
return 1;
}
printf("結果: %s", buf);
return 0;
}
マルチスレッドプログラムで時刻を扱う場合は、可能ならctime_rやlocaltime_rなどの再入可能版を使うことを検討してください。
一方で、初心者がまず1スレッドの練習をする段階では、通常のctimeやlocaltimeで問題ありません。
UTCとローカル時刻の違い
時刻を扱うときに混乱しやすいポイントが、UTC(協定世界時)とローカル時刻の違いです。
timeが返すtime_tは、基本的にUTCを基準とした秒数です。ctimeはローカル時刻として文字列を生成します。localtimeもローカル時刻のstruct tmを返します。- UTCの
struct tmが欲しい場合はgmtimeを使います。
UTCベースの扱いをしたい場合の例を示します。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *utc;
char buf[64];
time(&now);
// time_t → struct tm(UTC)に変換
utc = gmtime(&now);
if (utc == NULL) {
printf("gmtime failed.\n");
return 1;
}
// UTCで整形
if (strftime(buf, sizeof(buf),
"%Y-%m-%d %H:%M:%S UTC",
utc) == 0) {
printf("strftime failed.\n");
return 1;
}
printf("UTC時刻: %s\n", buf);
return 0;
}
UTC時刻: 2025-11-22 04:45:10 UTC
日本(UTC+9)では、ローカル時刻とは9時間の差があることが分かります。
ctimeやlocaltimeは「ローカル時刻」、gmtimeは「UTC」という区別を頭に入れておくと、時間のズレで悩みにくくなります。
C言語初心者が混乱しやすいポイントの整理
最後に、C言語初心者がctimeや時間処理で混乱しやすいポイントを整理しておきます。
- time_tが何を表しているか分からない
→ 多くの環境で「1970-01-01 00:00:00(UTC)からの秒数」です。直接見て意味が分からなくても問題はなく、ctimeやlocaltime経由で人間向け形式に変換して使います。 - ctimeの戻り値をfreeしてしまう
→ctimeの戻り値はライブラリ内部の静的バッファです。絶対にfreeしないでください。必要ならstrcpyなどで自前の配列にコピーします。 - ctimeの文字列の末尾に改行が入っているのを忘れる
→ すでに\nが含まれているので、printf("%s\n", s);と書くと改行が2つになります。改行を取り除きたい場合はstrchrで\nを探して\0に置き換えるなどします。 - struct tmの年と月の値に戸惑う
→tm_yearは「1900年からの年数」、tm_monは「0が1月」です。人が読む西暦と月にするにはyear = tm_year + 1900;、month = tm_mon + 1;としてください。 - ctimeとstrftimeの役割の違いがあいまい
→ctimeは「おまかせで1種類の英語フォーマット」、strftimeは「自分で書式指定して自由に整形」と覚えると整理しやすいです。 - UTCとローカル時刻の違いを忘れる
→time/ctime/localtimeは「ローカル時刻」、gmtimeは「UTC」です。サーバとクライアント、ログ出力などで時刻がずれて見えるときは、この違いを疑ってください。
これらのポイントを意識しておけば、「time_t → ctime → struct tm → strftime」という流れを迷わず使いこなせるようになります。
まとめ
C言語で時刻を扱う際、time_tは内部表現として秒数を持ち、ctimeはそれを手軽に英語形式の文字列へ変換してくれる関数です。
「とりあえず現在時刻を表示したい」ならtimeとctimeの組み合わせだけで十分ですが、「〇年〇月〇日〇時〇分」など日本語形式で細かく制御したい場合は、localtimeでstruct tmに変換し、strftimeで書式を指定するのが基本です。
内部バッファの扱いやUTCとローカル時刻の違いなどの注意点を押さえながら、サンプルコードを実際に動かして理解を深めていけば、時刻処理は確実に身につきます。
