C言語で現在日時を「2025/12/12」のように整った形で表示したい場面はとても多いです。
そのときに便利なのがstrftime関数です。
本記事では、time関数やtm構造体との関係から、YYYY/MM/DD形式を一発で表示する具体的なサンプルまで、図解とコードを交えながら詳しく解説します。
書式指定子の一覧表も掲載しますので、コピペしながら実践的に使えるようになります。
C言語のstrftimeとは
strftimeの基本概要と特徴

C言語のstrftime関数は、日付や時刻を表す構造体(tm構造体)から、指定したフォーマットの文字列を生成するための関数です。
標準ライブラリのtime.hに定義されており、OS や環境に依存せずに利用できます。
ポイントを文章で整理すると、次のような特徴があります。
strftimeは、内部的に扱われる「機械向きの時間情報」を「人間が読みやすい文字列」に変換するための仕上げ役であり、特にログ出力や画面表示で頻繁に使用されます。
time関数・localtime関数との関係

strftimeは単独では使わず、通常はtime関数やlocaltime関数などと組み合わせて利用します。
まずtime関数で現在時刻をtime_t型として取得し、そのtime_tをlocaltime関数でstruct tmに変換します。
このstruct tmを、最後にstrftimeで文字列に整形する、という流れになります。
この3つの関数は、次のような役割分担をしています。
- time関数: 現在時刻を「秒数」などの内部表現で取得します。
- localtime関数: 内部表現の時刻を「年」「月」「日」などの構造体に分解します。
- strftime関数:分解された時間情報を、指定した書式の文字列に変換します。
この流れを理解しておくと、後のサンプルコードも読みやすくなります。
日付フォーマットを文字列に変換する仕組み

strftimeは、"%Y/%m/%d"のような書式文字列を1文字ずつ読みながら、'%'で始まる部分を書式指定子として解釈し、tm構造体の対応するメンバーで置き換えていきます。
たとえば、次のような変換が行われます。
%Y→ 西暦4桁(例: 2025)%m→ 月(2桁、0埋め、01〜12)%d→ 日(2桁、0埋め、01〜31)
この結果、"%Y/%m/%d"は"2025/12/12"という文字列に展開されます。
strftimeは内部でバッファに結果を書き込むため、バッファサイズの指定や戻り値の確認が重要になります。
この点については後ほど詳しく説明します。
strftimeの基本的な使い方
strftimeの関数プロトタイプと引数

まず、公式なプロトタイプを確認します。
time.hに以下のように宣言されています。
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
各引数の役割は次の通りです。
s
出力先となる文字列バッファへのポインタです。ここに、整形された日付文字列が書き込まれます。max
バッファのサイズを文字数(終端の'\0'も含む)で指定します。オーバーフローを防ぐために必須です。format
書式指定子を含むフォーマット文字列です。たとえば"%Y/%m/%d"のように指定します。tm
日付・時刻情報を格納したstruct tmへのポインタです。通常はlocaltimeやgmtimeで取得した値を渡します。
戻り値のsize_tは、実際に書き込まれた文字数(終端の'\0'を含まない)です。
0の場合は、バッファサイズが足りなかったか、環境によっては一部の書式を処理できなかったことを意味します。
tm構造体の準備手順

strftimeを使うためには、入力となるstruct tmを用意する必要があります。
一般的なケースでは、timeとlocaltimeを組み合わせて現在時刻のtmを取得します。
代表的な取得パターンは次の通りです。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now; // 現在時刻を格納する変数(time_t型)
struct tm *ptm; // 分解された時刻を指すポインタ
time(&now); // 現在時刻を取得(エポックからの秒数など)
ptm = localtime(&now); // ローカル時刻のstruct tm*を取得
// ptm->tm_year, ptm->tm_mon, ptm->tm_mday などが使えるようになる
printf("Year since 1900: %d\n", ptm->tm_year);
printf("Month (0-11): %d\n", ptm->tm_mon);
printf("Day (1-31): %d\n", ptm->tm_mday);
return 0;
}
上記では、tm構造体の実体はライブラリ内部にあり、localtimeはそのアドレスだけを返しています。
構造体のメンバーは以下のような意味を持ちます。
| メンバー名 | 型 | 意味 |
|---|---|---|
| tm_year | int | 1900年からの年数(例: 2025年なら125) |
| tm_mon | int | 月(0〜11、0が1月、11が12月) |
| tm_mday | int | 日(1〜31) |
| tm_hour | int | 時(0〜23) |
| tm_min | int | 分(0〜59) |
| tm_sec | int | 秒(0〜60、うるう秒を考慮) |
| tm_wday | int | 曜日(0〜6、0が日曜日) |
| tm_yday | int | 年内の通算日(0〜365、0が1月1日) |
| tm_isdst | int | サマータイムフラグ(>0:夏時間、0:非適用など) |
自分で任意の日付を構築してstrftimeに渡したい場合は、手動でtm構造体を埋めることもできます。
ただし、その場合はmktimeで正規化するのが安全です。
現在日時を取得してYYYY/MM/DDで表示するサンプルコード

ここではもっともよく使うパターンとして、現在日時を取得し、YYYY/MM/DD形式で表示するサンプルコードを示します。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now; // 現在時刻を格納するための変数
struct tm *ptm; // 分解された時刻(ローカル時刻)へのポインタ
char buf[64]; // 出力用文字列バッファ
// 現在時刻をtime_t型で取得
time(&now);
// time_tをローカル時間のstruct tm*に変換
ptm = localtime(&now);
if (ptm == NULL) {
// 変換に失敗した場合の簡易エラーメッセージ
fprintf(stderr, "localtime failed\n");
return 1;
}
// "%Y/%m/%d" 形式で日付を文字列に変換
// 例: "2025/12/12"
if (strftime(buf, sizeof(buf), "%Y/%m/%d", ptm) == 0) {
// 0 が返った場合は、バッファ不足などで失敗
fprintf(stderr, "strftime failed (buffer too small?)\n");
return 1;
}
// 結果を表示
printf("Today: %s\n", buf);
return 0;
}
上記は実行環境により出力内容が変わりますが、形式としては次のようになります。
Today: 2025/12/12
このサンプルでは、YYYY/MM/DD形式の文字列を一発で生成しています。
"%Y/%m/%d"という書式指定がその役割を担っています。
バッファサイズと戻り値の扱い方

strftimeを安全に使うには、バッファサイズと戻り値を正しく扱うことが非常に重要です。
関数は、次のような条件で動作します。
- 書式展開後の文字列がmax−1文字以下であれば、その文字列を
sに書き込み、終端に'\0'を付けて返します。 - 文字列がmax−1文字を超える場合、文字列を
sに書き込まず、戻り値として0を返します。
したがって、戻り値が0かどうかを必ずチェックするのが望ましいです。
次のような書き方をしておくと安全です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *ptm;
char buf[32]; // わざと小さめのバッファ
time(&now);
ptm = localtime(&now);
// 日付と時刻を含む長めのフォーマット
size_t len = strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S %Z", ptm);
if (len == 0) {
// バッファが小さすぎる可能性が高い
fprintf(stderr, "strftime failed: buffer too small (size=%zu)\n", sizeof(buf));
return 1;
}
printf("Now: %s (length=%zu)\n", buf, len);
return 0;
}
このように、十分なサイズのバッファを確保しつつ、戻り値が0かどうかをチェックすることで、安全で堅牢なコードになります。
特に、ログフォーマットに多くの情報を詰め込むときは、バッファサイズに余裕を持たせることが重要です。
strftimeの書式指定子一覧
日付(年月日)の主な書式指定子

strftimeには多数の書式指定子がありますが、まずは年月日に関する代表的なものを整理します。
環境により対応状況が異なる場合がありますが、POSIXなどで標準的に使われるものを中心に挙げます。
| 書式指定子 | 出力例 | 説明 |
|---|---|---|
| %Y | 2025 | 4桁の西暦(0000〜9999) |
| %y | 25 | 西暦の下2桁(00〜99) |
| %m | 12 | 月(2桁、0埋め、01〜12) |
| %-m | 12 (環境依存) | 月(0埋めなし、1〜12、GNU拡張など) |
| %d | 03 | 日(2桁、0埋め、01〜31) |
| %-d | 3 (環境依存) | 日(0埋めなし、1〜31、GNU拡張など) |
| %e | 3 | 日(先頭が空白で埋められる、” 1″〜”31″) |
| %j | 337 | 年内通算日(001〜366) |
YYYY/MM/DD形式で表示したい場合は、基本的に"%Y/%m/%d"を覚えておけば十分です。
0埋めをしたくない場合などは、環境が対応していれば"%Y/%-m/%-d"のように書くこともありますが、ポータビリティを考えると"%Y/%m/%d"を使う方が無難です。
時刻(時分秒)の主な書式指定子

次に、時刻(時分秒)に関する代表的な書式指定子です。
| 書式指定子 | 出力例 | 説明 |
|---|---|---|
| %H | 09 | 24時間表記の時(0埋め、00〜23) |
| %I | 09 | 12時間表記の時(0埋め、01〜12) |
| %M | 05 | 分(0埋め、00〜59) |
| %S | 07 | 秒(0埋め、00〜60) |
| %T | 09:05:07 | 24時間表記の時刻、%H:%M:%Sと同じ |
| %R | 09:05 | 24時間表記の時刻、%H:%Mと同じ |
| %p | AM | 午前/午後(ロケール依存、”AM”/”PM”など) |
これらを組み合わせることで、次のような表現が可能です。
"%H:%M:%S"→"09:05:07""%Y/%m/%d %H:%M"→"2025/12/03 09:05""%H時%M分%S秒"→"09時05分07秒"
日本語を含めたフォーマットも、そのまま文字列中に日本語を記述するだけで利用できます。
曜日・月名などの文字列表現

strftimeは、曜日名や月名を文字列として出力することもできます。
これらはロケールに依存するため、システムのロケール設定次第で日本語や英語になります。
| 書式指定子 | 出力例(ロケールによる) | 説明 |
|---|---|---|
| %a | Wed / 水 | 曜日の省略形 |
| %A | Wednesday / 水曜日 | 曜日の完全な名前 |
| %b | Dec / 12月 | 月の省略形 |
| %B | December / 12月 | 月の完全な名前 |
| %h | Dec | %bと同じ(互換用) |
日本語ロケールが有効な環境では、"%Y年%m月%d日(%a)"などと指定すると、"2025年12月03日(水)"のように曜日が日本語で出力される場合があります。
ロケール設定については後述します。
ロケールに依存する書式の注意点

strftimeの書式指定子の中には、ロケールに依存して書式そのものが変わるものがあります。
代表的なものは次の通りです。
| 書式指定子 | 意味 | 例(ロケールによって変化) |
|---|---|---|
| %x | ロケール固有の日付表記 | “2025/12/03” や “12/03/25” など |
| %X | ロケール固有の時刻表記 | “09:05:07” や “9:05:07 AM” など |
| %c | ロケール固有の日付と時刻の表記 | “2025年12月3日(水) 9時05分07秒” など |
| %p | 午前/午後を表す文字列 | “AM”/”PM” や “午前”/”午後” など |
これらを使用すると、環境ごとに異なる文字列が生成されます。
そのため、ログフォーマットやファイル名など、環境に依存せず一定の形式にしたい場合は、%Y, %m, %d, %H などの「固定フォーマット」を積極的に使うことが重要です。
必要に応じて、setlocale関数でロケールを明示的に指定することもできます。
たとえば日本語ロケールを有効にしてからstrftimeを使う例は次のようになります。
#include <stdio.h>
#include <time.h>
#include <locale.h>
int main(void) {
time_t now;
struct tm *ptm;
char buf[128];
// 日本語ロケール(例: UTF-8)を有効化
// 実際に使えるロケール名は環境依存です
setlocale(LC_TIME, "ja_JP.UTF-8");
time(&now);
ptm = localtime(&now);
// ロケール依存の書式を試す
strftime(buf, sizeof(buf), "%c", ptm);
printf("Locale-dependent: %s\n", buf);
return 0;
}
ロケール設定はシステム環境と密接に関連しますので、ミドルウェアやライブラリ側のロケール設定との干渉にも注意が必要です。
YYYY/MM/DD形式での表示テクニック
YYYY/MM/DDを一発で表示する書式指定例

YYYY/MM/DD形式の出力は、strftimeでは次のフォーマット1つだけで実現できます。
"%Y/%m/%d"
これを使った最小限のサンプルは以下のようになります。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *ptm;
char date[16]; // "YYYY/MM/DD" は 10文字なので16あれば十分
time(&now);
ptm = localtime(&now);
// YYYY/MM/DD形式の文字列を生成
if (strftime(date, sizeof(date), "%Y/%m/%d", ptm) == 0) {
fprintf(stderr, "strftime failed\n");
return 1;
}
printf("Date: %s\n", date);
return 0;
}
このように、1つの書式文字列で希望の形式を一発で生成できるため、日付表示の定番パターンとして多用されます。
0埋め・2桁表示を揃えるフォーマット

strftimeの多くの数値系書式指定子は、デフォルトで0埋めを行います。
たとえば、1月は"01"と出力されます。
日付をきれいに2桁で揃えたい場合は、%mや%dをそのまま使えばよく、特に追加設定は不要です。
一方、0埋めをしたくない場合は、環境によってはGNU拡張として%-mや%-dが利用できます。
- 0埋めあり(ポータブル)
"%Y/%m/%d"→"2025/01/05"
- 0埋めなし(環境依存のGNU拡張など)
"%Y/%-m/%-d"→"2025/1/5"
- 日本語を混在させる場合
"%Y年%m月%d日"→"2025年01月05日""%Y年%-m月%-d日"→"2025年1月5日"(環境依存)
ポータビリティを重視する場合は、0埋めを受け入れるか、0埋めなし表示がどうしても必要ならsnprintfなどと組み合わせて自力で整形するのが安全です。
日付と時刻を組み合わせたフォーマット例

strftimeの強みは、日付と時刻を自在に組み合わせられることです。
具体的な利用シーンごとに、代表的なフォーマット例を挙げます。
- ログ向け(読みやすさ重視)
- フォーマット:
"%Y/%m/%d %H:%M:%S" - 例:
"2025/12/12 23:59:59" - 特徴: 日付と時刻をスペースで区切ることで視認性が高まります。
- フォーマット:
- ファイル名向け(記号を避ける)
- フォーマット:
"%Y%m%d_%H%M%S" - 例:
"20251212_235959" - 特徴: スラッシュ
'/'やコロン':'を含めず、ファイルシステムに優しい名前になります。
- フォーマット:
- 人向け日本語表示
- フォーマット:
"%Y年%m月%d日 %H時%M分" - 例:
"2025年12月12日 23時59分" - 特徴: インターフェース上での日本語表示に適しています。
- フォーマット:
これらは、いずれも<YYYY/MM/DD>部分に“%Y/%m/%d”を組み合わせていることがわかります。
用途ごとに時刻部分の書式を変えつつ、日付部分は同じ形式を維持することで、一貫性のある表示を実現できます。
よくあるエラーとstrftimeの注意点

最後に、strftimeを使う際によくある落とし穴と注意点をまとめます。
バッファサイズ不足で出力されない
最も多い問題は、バッファサイズが小さくてstrftimeが0を返し、結果として空文字列のままになってしまうケースです。
このとき、printfなどで出力しても何も表示されません。
改善策としては、次の点を心がけます。
- バッファは余裕を持って大きめに確保する
たとえば日付と時刻を含む典型的なフォーマットなら、64〜128バイト程度あれば十分なことが多いです。 - 戻り値が
0でないか必ず確認する
これにより、バッファ不足やその他の不具合を早期に検知できます。
localtimeの戻り値をチェックしない
localtimeやgmtimeは、失敗時にNULLを返す可能性があります。
NULLポインタをstrftimeに渡すと未定義動作になりますので、必ずチェックしましょう。
ptm = localtime(&now);
if (ptm == NULL) {
// エラー処理
}
ロケール依存書式を仕様として固定してしまう
%xや%cなどのロケール依存書式は、人間向けの一時的な表示には便利ですが、ログフォーマットやファイル名などの「仕様」として固定する用途には向きません。
環境によって形式が変わるため、後からパースしにくくなります。
このような用途では、“%Y/%m/%d %H:%M:%S” のような固定フォーマットを使用することが推奨されます。
スレッドセーフティ(localtimeとの組み合わせ)
一部の環境では、localtimeはスレッドセーフではないことがあります。
そのため、マルチスレッド環境ではlocaltime_rやgmtime_rなどのスレッドセーフなバージョンを使用し、自前のstruct tmに結果を書き込むと安心です。
strftime自体は、引数として渡されたstruct tmを読み取るだけなので、そのstruct tmをどう用意するかが重要になります。
まとめ
strftimeは、tm構造体から人間が読みやすい日付・時刻文字列を生成するための標準関数です。
time関数とlocaltime関数で現在時刻を取得し、“%Y/%m/%d”という書式指定子を使うことで、YYYY/MM/DD形式を一発で表示できます。
バッファサイズと戻り値のチェック、ロケール依存書式の扱いに注意しながら、ログ出力やファイル名、UI表示などに柔軟に活用してみてください。
