日時はプログラムの入出力で頻出ですが、表示形式は用途ごとに少しずつ異なります。
C言語ではstrftimeを使うと、任意の書式で日時を文字列に整形できます。
この記事では初心者の方にも分かりやすいように、基礎から実用的な書式例、注意点までを段階的に説明します。
C言語のstrftimeの基本と使い方
strftimeとは
strftimeはstruct tmで表された日時を、指定した書式(フォーマット)の文字列に変換する関数です。
年月日や時分秒、曜日、タイムゾーン、ロケール依存の表記などを柔軟に組み合わせて出力できます。
なお、strftimeはstruct tmを入力に取り、結果を文字配列に書き込みます。
現在時刻の取得(time)やstruct tmへの変換(localtime/gmtime)は別の関数で行い、本記事では最小限のみ触れます。
必要なヘッダ(time.h)と関数の形
strftimeを使うには#include <time.h>が必要です。
ロケール依存の表記(曜日名や月名、AM/PMなど)をユーザー環境に合わせたい場合は#include <locale.h>とsetlocaleを併用します。
関数プロトタイプは次のとおりです。
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
s: 出力先の文字配列(ヌル終端されます)max: 配列のサイズ(バイト数、終端の'\0'込み)format: 書式文字列(例:"%Y-%m-%d %H:%M:%S")tm: 入力となるstruct tmへのポインタ
戻り値は書き込まれた文字数(終端の'\0'を除く)で、収まりきらない場合は0が返ります。
struct tmの準備
struct tmは分解された日時を保持する構造体です。
代表的な準備方法は2つです。
1つ目はtime_tから変換する方法です。
ローカル時刻に変換するlocaltime、UTCに変換するgmtimeを使います(詳細は別記事)。
これらを使えば曜日(wday)や通し日(yday)も自動で埋まるため、%aや%jのような指定子が正しく出せます。
2つ目は自分で値を入れてmktimeで正規化する方法です。
自前でtmを埋めるだけではwday/ydayなどが未定義のままなので、必要ならmktimeで補完・正規化しましょう。
書式文字列の考え方と%の使い方
strftimeの書式文字列は、通常の文字と変換指定子で構成されます。
変換指定子は%Yや%mのように%で始まり、年、月、日、時間などに展開されます。
そのまま%記号を出したい場合は%%を使います。
未知の指定子の挙動は処理系依存なので、仕様にない拡張(%e, %F, %z, %Zなど)を使うときは移植性に注意してください。
バッファサイズと戻り値の扱い
バッファは十分に大きく確保しましょう。
戻り値が0なら「全体が収まらなかった」ことを意味します。
サイズはsize_tで、maxにはsizeof(buffer)を渡すのが定石です。
多言語の月名や曜日名は想定より長いことがあるため、余裕を見た長さ(例: 128〜256文字)が安全です。
現在時刻をローカルタイムに変換し、いくつかの形式で整形してみます。
ロケールをシステム既定に合わせるためsetlocale(LC_TIME, "")を呼んでいます。
#include <stdio.h>
#include <time.h>
#include <locale.h>
int main(void) {
setlocale(LC_TIME, ""); // ロケール依存表記(曜日名・月名・AM/PMなど)を環境に合わせる
time_t now = time(NULL); // 現在のUNIX時刻(秒)
struct tm *lt = localtime(&now); // ローカルタイムの struct tm へ変換
if (!lt) {
perror("localtime failed");
return 1;
}
char buf[128];
// 1) 基本: YYYY-MM-DD HH:MM:SS
if (strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", lt))
printf("Basic: %s\n", buf);
// 2) 曜日・月名(ロケール依存)
if (strftime(buf, sizeof buf, "%A, %B %d, %Y", lt))
printf("Locale names: %s\n", buf);
// 3) 12時間表示とAM/PM
if (strftime(buf, sizeof buf, "%I:%M:%S %p", lt))
printf("12h clock: %s\n", buf);
// 4) タイムゾーン(利用可否は処理系依存)
if (strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S %Z %z", lt))
printf("TZ: %s\n", buf);
// 5) % を出力する
if (strftime(buf, sizeof buf, "progress: 100%%", lt))
printf("Percent sign: %s\n", buf);
return 0;
}
Basic: 2025-10-16 14:23:45
Locale names: 木曜日, 10月 16, 2025
12h clock: 02:23:45 午後
TZ: 2025-10-16 14:23:45 JST +0900
Percent sign: progress: 100%
よく使う書式指定子まとめ
ここでは実務でよく使う指定子を用途別に整理します。
例はあくまで一例で、ロケールやタイムゾーンで変わります。
%z, %Z, %F, %T などはPOSIX拡張で、Windowsなど一部環境では非対応の場合があります。
年月日
年月日の表現は最頻出です。
ゼロ埋めや桁幅に注意します。
| 指定子 | 意味 | 例 |
|---|---|---|
| %Y | 西暦4桁の年 | 2025 |
| %y | 西暦下2桁の年(00–99) | 25 |
| %m | 月(01–12) | 10 |
| %d | 日(01–31) | 16 |
| %j | 年内通し日(001–366) | 289 |
| %C | 世紀(年/100の整数部、00–99) | 20 |
%jはデータ分析やログで年内の経過日を扱う際に便利です。
時分秒
24時間制が基礎で、秒はうるう秒対応で00–60の範囲を取り得ます。
| 指定子 | 意味 | 例 |
|---|---|---|
| %H | 時(00–23) | 14 |
| %M | 分(00–59) | 23 |
| %S | 秒(00–60) | 45 |
| %T | 時刻の短縮表記(= %H:%M:%S、POSIX) | 14:23:45 |
ミリ秒やマイクロ秒は標準のstrftimeでは扱えません。
必要なら別途取得して文字列連結します。
12時間表示とAM/PM
12時間制の表示には%Iと%pを組み合わせます。
| 指定子 | 意味 | 例 |
|---|---|---|
| %I | 時(01–12) | 02 |
| %p | 午前/午後(ロケール依存) | AM, PM または 午前, 午後 |
| %r | 12時間制の時刻(ロケール依存) | 02:23:45 PM |
%pはロケールで表記が変わるため、英語固定にしたいならsetlocale(LC_TIME, "C")を使います。
曜日と月名
文字名はロケールに依存します。
英語圏ではMon/January、日本語ロケールでは月/1月などに変わります。
| 指定子 | 意味 | 例 |
|---|---|---|
| %a | 曜日名(短縮) | Thu, 木 |
| %A | 曜日名(完全) | Thursday, 木曜日 |
| %b | 月名(短縮) | Oct, 10月 |
| %B | 月名(完全) | October, 10月 |
| %w | 曜日番号(0=日〜6=土) | 4 |
言語に応じて長さが大きく変わるため、出力バッファは余裕を持たせます。
タイムゾーンとUTCオフセット
ログや分散システムではUTCオフセットが重要です。
| 指定子 | 意味 | 例 |
|---|---|---|
| %Z | タイムゾーン名(略称、処理系依存) | JST, PST |
| %z | UTCオフセット(+HHMM、処理系依存) | +0900, -0800 |
%z/%Zは非対応の処理系がある点に注意してください。
LinuxやmacOSでは広く使えますが、Windowsではランタイムによって挙動が異なります。
UTC固定にしたい場合はgmtimeでstruct tmを作り、末尾に'Z'を付ける方法が移植性の高い定番です。
ロケール依存の省略形
ロケールに依存した「日付」や「時刻」全体の表記を任せる指定子です。
| 指定子 | 意味 | 例 |
|---|---|---|
| %c | ロケール依存の日時 | Thu Oct 16 14:23:45 2025 |
| %x | ロケール依存の日付 | 10/16/25 または 2025/10/16 |
| %X | ロケール依存の時刻 | 14:23:45 |
表記は環境により大きく変わるため、機械処理向けには非推奨です。
エスケープと%の出力
strftimeの書式ではリテラルの%を出すとき%%を使います。
改行やタブはCの文字列リテラル内で"\n"や"\t"と書けばそのまま入ります。
POSIXでは%nや%tが改行やタブとして解釈される実装もありますが、移植性を重視するならバックスラッシュのエスケープを使うのが無難です。
実用フォーマット例とサンプルコード
YYYY-MM-DD HH:MM:SS
もっとも基本的なログ向け表記です。
数値はゼロ埋めで、文字列比較がそのまま時系列の並びになります。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *lt = localtime(&now);
if (!lt) return 1;
char buf[32];
if (strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", lt))
printf("%s\n", buf);
return 0;
}
2025-10-16 14:23:45
ISO 8601
機械処理で広く使われる国際規格です。
ローカルタイムなら%Y-%m-%dT%H:%M:%S%z、UTCなら末尾にZを付けます。
なお%zのコロン付き+09:00はPOSIX拡張%:zで出せますが移植性に注意してください。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
// 1) ローカルタイムのISO 8601(+HHMM形式)
struct tm *lt = localtime(&now);
char local_iso[64];
if (strftime(local_iso, sizeof local_iso, "%Y-%m-%dT%H:%M:%S%z", lt))
printf("Local ISO8601: %s\n", local_iso);
// 2) UTCのISO 8601(末尾にZ)
struct tm *ut = gmtime(&now);
char utc_iso[64];
if (strftime(utc_iso, sizeof utc_iso, "%Y-%m-%dT%H:%M:%S", ut)) {
// %zはUTCで+0000になりますが、移植性のため明示Zを推奨
printf("UTC ISO8601: %sZ\n", utc_iso);
}
return 0;
}
Local ISO8601: 2025-10-16T14:23:45+0900
UTC ISO8601: 2025-10-16T05:23:45Z
日本語表記
日本語ロケールでは曜日や月の表記が日本語になります。
ただし、ロケール名"ja_JP.UTF-8"はOS設定やインストール状況に依存します。
無難なのはsetlocale(LC_TIME, "")でシステム既定に合わせる方法です。
#include <stdio.h>
#include <time.h>
#include <locale.h>
int main(void) {
setlocale(LC_TIME, ""); // 可能ならja_JP系に設定される
time_t now = time(NULL);
struct tm *lt = localtime(&now);
if (!lt) return 1;
char buf[64];
// 「2025年10月16日(木) 14時23分45秒」のような表記
if (strftime(buf, sizeof buf, "%Y年%m月%d日(%a) %H時%M分%S秒", lt))
printf("%s\n", buf);
return 0;
}
2025年10月16日(木) 14時23分45秒
ログ用タイムスタンプ
ログではソート性とタイムゾーン情報が重要です。
ファイルや集約基盤で扱いやすいようにISO準拠の形を使うのが定番です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *lt = localtime(&now);
char ts[64];
// 例: 2025-10-16T14:23:45+0900 [INFO] Message...
if (strftime(ts, sizeof ts, "%Y-%m-%dT%H:%M:%S%z", lt))
printf("%s [INFO] Server started\n", ts);
// %zにコロンを入れたい(+09:00): 移植性の高い方法
// %z -> +HHMM を +HH:MM に手直しする
char ts2[64];
if (strftime(ts2, sizeof ts2, "%Y-%m-%dT%H:%M:%S%z", lt)) {
size_t len = strlen(ts2);
if (len >= 5) {
// 末尾のHHMMの前から3文字位置にコロンを挿入: ...+HHMM -> ...+HH:MM
// 安全のため別バッファを使う
char fixed[70];
size_t base = len - 5;
memcpy(fixed, ts2, base + 3);
fixed[base + 3] = ':'; // コロン
memcpy(fixed + base + 4, ts2 + base + 3, 2); // MM
fixed[base + 6] = '#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *lt = localtime(&now);
char ts[64];
// 例: 2025-10-16T14:23:45+0900 [INFO] Message...
if (strftime(ts, sizeof ts, "%Y-%m-%dT%H:%M:%S%z", lt))
printf("%s [INFO] Server started\n", ts);
// %zにコロンを入れたい(+09:00): 移植性の高い方法
// %z -> +HHMM を +HH:MM に手直しする
char ts2[64];
if (strftime(ts2, sizeof ts2, "%Y-%m-%dT%H:%M:%S%z", lt)) {
size_t len = strlen(ts2);
if (len >= 5) {
// 末尾のHHMMの前から3文字位置にコロンを挿入: ...+HHMM -> ...+HH:MM
// 安全のため別バッファを使う
char fixed[70];
size_t base = len - 5;
memcpy(fixed, ts2, base + 3);
fixed[base + 3] = ':'; // コロン
memcpy(fixed + base + 4, ts2 + base + 3, 2); // MM
fixed[base + 6] = '\0';
printf("%s [DEBUG] with colon tz\n", fixed);
}
}
return 0;
}
';
printf("%s [DEBUG] with colon tz\n", fixed);
}
}
return 0;
}
2025-10-16T14:23:45+0900 [INFO] Server started
2025-10-16T14:23:45+09:00 [DEBUG] with colon tz
ファイル名に使える日時
Windowsではコロン(:)やスラッシュ(/)がファイル名に使えません。
安全な書式を使いましょう。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *lt = localtime(&now);
char name[64];
// 例: backup_20251016_142345.log
if (strftime(name, sizeof name, "backup_%Y%m%d_%H%M%S.log", lt))
printf("%s\n", name);
return 0;
}
backup_20251016_142345.log
初心者が押さえる注意点とコツ
バッファ不足時の判定
戻り値が0なら失敗です。
月名や曜日名が長くなったりロケールが変わると、想定より長くなることがあります。
次のように判定し、必要ならより大きいバッファで再試行しましょう。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int format_time_dyn(char **out, const char *fmt, const struct tm *tm) {
size_t cap = 64;
char *buf = NULL;
for (;;) {
char *tmp = realloc(buf, cap);
if (!tmp) { free(buf); return 0; }
buf = tmp;
size_t n = strftime(buf, cap, fmt, tm);
if (n > 0) { // 成功
*out = buf;
return 1;
}
// 失敗(収まらない) → 拡張して再試行
if (cap > 4096) { free(buf); return 0; } // 異常系のガード
cap *= 2;
}
}
この方法ならロケールやタイムゾーンに左右されず安全に整形できます。
ロケールの影響
曜日名・月名・AM/PM・%c/%x/%Xはロケールに依存します。
英語固定のログを出したいならsetlocale(LC_TIME, "C")のように明示しましょう。
逆にユーザー向け画面ではsetlocale(LC_TIME, "")でOS既定に合わせるのが自然です。
ロケールが存在しない環境名を指定すると設定に失敗することがあるため、戻り値のチェックを推奨します。
UTCとローカルの違いを意識
分散システムの相互運用にはUTCが有利です。
UTCで出す場合はgmtimeでstruct tmを作り、ISO 8601なら末尾Zを付けます。
人間向けの表示や単一マシン内のログならローカルでも構いませんが、混在させないのが混乱回避のコツです。
先頭ゼロや桁埋めの確認
%mや%dはゼロ埋めです。
ゼロなしで出したいときにPOSIX拡張の%-dや%-mが使える環境もありますが、標準Cでは未規定です。
移植性を重視するなら、出力後に自前でゼロを削るなどの後処理を行います。
テストしやすい固定日時で検証
TDDや再現テストでは固定のstruct tmを作ると便利です。
wday/ydayを正しくしたい場合はmktimeで正規化します。
#include <stdio.h>
#include <time.h>
int main(void) {
struct tm tm = {0};
tm.tm_year = 2025 - 1900; // 年は1900からのオフセット
tm.tm_mon = 9; // 月は0=1月 → 9=10月
tm.tm_mday = 16;
tm.tm_hour = 14;
tm.tm_min = 23;
tm.tm_sec = 45;
// ローカルタイムとして正規化(曜日・通し日などが補完される)
if (mktime(&tm) == (time_t)-1) {
puts("mktime failed");
return 1;
}
char buf[64];
if (strftime(buf, sizeof buf, "%Y-%m-%d (%A) %H:%M:%S [day %j]", &tm))
printf("%s\n", buf);
return 0;
}
2025-10-16 (Thursday) 14:23:45 [day 289]
まとめ
strftimeは「日時を好きな書式で出力する」ための標準APIであり、ログ、ファイル名、ユーザー表示のいずれにも活用できます。
使いこなしの要点は、十分なバッファ確保と戻り値0の検出、ロケールやタイムゾーンの違いの理解、そして移植性に配慮した指定子の選択です。
ISO 8601のような機械可読な形式と人間可読なロケール表記を場面に応じて使い分けることで、正確かつ読みやすい日時表現が実現できます。
さらに精度(ミリ秒など)やパースなどが必要な場合は、別途ライブラリやAPIを組み合わせる設計を検討してください。
