日時を人が読める形に整える最短経路の1つがasctimeです。
用意したstruct tmを一行の文字列に変換でき、学習段階ではとても便利です。
ただし末尾の改行や静的バッファなど見落としやすい点があります。
この記事では基礎から丁寧に解説し、正しく使うための実例と注意点を示します。
C言語のasctimeとは
何をする関数か
asctimeは、struct tm型の日時情報を人間が読みやすい固定形式の文字列に変換する関数です。
返り値は内部の静的バッファ(後述)を指すchar *で、標準出力にそのまま表示できます。
主な用途は次の通りです。
プログラムのログ出力やデバッグ時に、struct tmの中身を手早く確認するのに向いています。
一方で、書式の自由度は低く、国際化や厳密な体裁が必要な用途ではstrftimeの使用が推奨されます。
出力される文字列の形式
asctimeが返す文字列は概ね次の形です。
Www Mmm dd hh:mm:ss yyyy\n\0
例として「Thu Oct 16 14:32:00 2025」などの形になります。
最後に改行\nが必ず付き、さらに終端のヌル文字\0が続きます。
歴史的には合計26文字(終端含む)という前提が広く知られていますが、文字数や表記の細部に依存したコードは移植性を損ねます。
以下は出力構成の目安です。
| 要素 | 例 | 備考 |
|---|---|---|
| 曜日省略名 | Thu | 3文字。多くの環境で英語の略称。 |
| 月省略名 | Oct | 3文字。多くの環境で英語の略称。 |
| 日 | 16 | 1桁日は先頭に空白になる実装が多いです。 |
| 時刻 | 14:32:00 | 24時間表記。 |
| 年 | 2025 | 西暦4桁が一般的。 |
| 末尾 | 改行+終端 | 最後に\nと\0。 |
ロケールにより略称が変化する実装もあります(後述)。
必要なヘッダとプロトタイプ
asctimeは<time.h>に宣言されています。
#include <time.h>
/* 標準Cのプロトタイプ */
char *asctime(const struct tm *timeptr);
この関数は静的バッファへのポインタを返します。
解放は不要で、解放してはいけません。
asctimeの使い方
最小サンプルコード
もっとも簡単な使い方は、現在時刻からstruct tmを得てasctimeに渡す方法です。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL); // 現在のUNIX時刻(秒)
struct tm *lt = localtime(&now); // ローカル時刻のstruct tmを取得
// 注: localtimeやasctimeは静的領域を使うためスレッドセーフではありません(後述)
char *s = asctime(lt); // 読みやすい文字列に変換
// asctimeの文字列末尾には改行が入っています。printfで'\n'を足さないのがコツです。
printf("現在時刻: %s", s);
return 0;
}
現在時刻: Thu Oct 16 14:32:05 2025
末尾の改行はasctimeが付けるため、printf("%s\n", s)のように重ねて改行すると、空行が1つ増えてしまいます。
struct tmの準備ポイント
asctimeに渡すstruct tmは、各フィールドを妥当な範囲に整える必要があります。
自前で値を詰めるときはmktimeで正規化してtm_wdayやtm_ydayを設定しましょう。
| メンバ | 意味 | 範囲と注意 |
|---|---|---|
| tm_sec | 秒 | 0〜60(うるう秒により60が現れる実装あり) |
| tm_min | 分 | 0〜59 |
| tm_hour | 時 | 0〜23 |
| tm_mday | 日 | 1〜31 |
| tm_mon | 月 | 0〜11(0が1月) |
| tm_year | 年 | 1900年からの年数(例: 2025年→125) |
| tm_wday | 曜日 | 0〜6(0が日曜)。mktime等で算出させるのが安全 |
| tm_yday | 年内通算日 | 0〜365 |
| tm_isdst | 夏時間 | 正なら適用、0で非適用、負で不明(自動判定) |
以下は日付を手動で作り、正規化してからasctimeで表示する例です。
#include <stdio.h>
#include <time.h>
int main(void) {
struct tm tmv = {0}; // すべて0で初期化するのが安全
tmv.tm_year = 2025 - 1900; // 2025年
tmv.tm_mon = 9; // 10月(0が1月)
tmv.tm_mday = 16; // 16日
tmv.tm_hour = 14; // 14時
tmv.tm_min = 32; // 32分
tmv.tm_sec = 0; // 0秒
tmv.tm_isdst = -1; // 夏時間は不明(システムに判定させる)
// mktimeで正規化し、tm_wdayやtm_ydayを埋めてもらう
if (mktime(&tmv) == (time_t)-1) {
// 表現できない日時だった場合の簡易エラーハンドリング
fprintf(stderr, "mktimeに失敗しました\n");
return 1;
}
char *s = asctime(&tmv);
printf("%s", s); // 改行はasctimeが付加済み
return 0;
}
Thu Oct 16 14:32:00 2025
tm_wdayなどを自前で計算せず、mktimeに任せると、曜日名の出力ミスを避けられます。
戻り値の扱い方
asctimeは静的バッファへのポインタを返します。
そのため次の点に注意します。
- freeしてはいけません。所有権は呼び出し側にありません。
- 次の
asctime呼び出しで上書きされます。複数の結果を保持したい場合は、自分のバッファにstrncpyやsnprintfでコピーします。 - 書き換えが必要なら必ずコピー側で行います。返ってきた静的領域の直接変更は推奨されません。
- 実装によっては、表現不可な日時で
NULLを返すことがあります。実用上は稀ですが、返り値のNULLチェックは入れておくと堅牢です。
コピーの例です。
#include <stdio.h>
#include <string.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *lt = localtime(&now);
char *p = asctime(lt);
if (!p) {
fprintf(stderr, "asctimeがNULLを返しました\n");
return 1;
}
char buf[32]; // asctimeの出力は通常26文字程度
strncpy(buf, p, sizeof(buf) - 1); // 安全にコピー
buf[sizeof(buf) - 1] = '#include <stdio.h>
#include <string.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *lt = localtime(&now);
char *p = asctime(lt);
if (!p) {
fprintf(stderr, "asctimeがNULLを返しました\n");
return 1;
}
char buf[32]; // asctimeの出力は通常26文字程度
strncpy(buf, p, sizeof(buf) - 1); // 安全にコピー
buf[sizeof(buf) - 1] = '\0'; // 念のため終端
// 末尾の改行を取り除く(必要な場合)
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[len - 1] = '\0';
}
printf("コピー後: %s\n", buf);
return 0;
}
'; // 念のため終端
// 末尾の改行を取り除く(必要な場合)
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[len - 1] = '#include <stdio.h>
#include <string.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *lt = localtime(&now);
char *p = asctime(lt);
if (!p) {
fprintf(stderr, "asctimeがNULLを返しました\n");
return 1;
}
char buf[32]; // asctimeの出力は通常26文字程度
strncpy(buf, p, sizeof(buf) - 1); // 安全にコピー
buf[sizeof(buf) - 1] = '\0'; // 念のため終端
// 末尾の改行を取り除く(必要な場合)
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[len - 1] = '\0';
}
printf("コピー後: %s\n", buf);
return 0;
}
';
}
printf("コピー後: %s\n", buf);
return 0;
}
コピー後: Thu Oct 16 14:32:05 2025
asctimeの注意点
末尾に改行が入る
asctimeの文字列は末尾に\nを含みます。
そのためprintf("%s\n", asctime(...))とすると改行が2つ続きます。
避けるには次のいずれかを使います。
printf("%s", asctime(...))とし、改行を追加しない。- いったんコピーし、末尾の
\nを取り除く。 strftimeで改行なしの文字列を最初から作る(推奨)。
strftimeを使う例を後述します。
戻り値は静的バッファ
戻り値は静的バッファなので、複数回呼び出すと内容が上書きされます。
下の例では最初の結果を自分のバッファにコピーしないと、二度目の呼び出しで上書きされてしまいます。
#include <stdio.h>
#include <string.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *lt = localtime(&now);
char s1[32], s2[32];
// 1回目の結果を直ちにコピーして保存
snprintf(s1, sizeof(s1), "%s", asctime(lt));
// 2回目: UTCに変換して別の文字列を取得
struct tm *gt = gmtime(&now);
snprintf(s2, sizeof(s2), "%s", asctime(gt));
printf("Local : %s", s1); // 末尾に改行を含む
printf("UTC : %s", s2);
return 0;
}
Local : Thu Oct 16 14:32:05 2025
UTC : Thu Oct 16 05:32:05 2025
コピーしてから別のasctimeを呼ぶ、という順番が安全です。
スレッドセーフではない
asctimeはスレッドセーフではありません。
内部の静的バッファを共有するため、複数スレッドから同時に呼び出すとデータ競合になります。
対策としては次のいずれかを選びます。
strftimeで呼び出し側バッファに書式化する(標準Cで最もポータブル)。- POSIX環境なら
asctime_rを使って呼び出し側バッファへ出力する。 - 一部処理系の拡張
asctime_s(Annex K)を使う。ただし移植性は高くありません。
strftimeでasctimeに近い書式を再現する例です。
#include <stdio.h>
#include <time.h>
#include <locale.h>
int main(void) {
time_t now = time(NULL);
struct tm tmv = *localtime(&now);
char buf[64];
// asctime相当の形式(末尾改行なし)を狙う書式
// %a: 曜日略名, %b: 月略名, %e: 日(先頭空白埋め), %H:%M:%S: 時刻, %Y: 西暦
if (strftime(buf, sizeof(buf), "%a %b %e %H:%M:%S %Y", &tmv) == 0) {
fprintf(stderr, "strftimeに失敗しました\n");
return 1;
}
printf("%s\n", buf); // 改行はここで追加
return 0;
}
Thu Oct 16 14:32:05 2025
この方法はスレッドセーフで、末尾の改行有無も自在です。
ロケール依存と表記
曜日や月の略称はロケールの影響を受ける実装があります。
例えばLC_TIMEを日本語ロケールに設定すると、%aや%bの結果が変わる場合があります。
asctime自体は書式固定で制御しづらく、国際化対応や一貫した表記が必要ならstrftimeを使い、必要に応じてsetlocaleでLC_TIMEを設定してください。
#include <stdio.h>
#include <time.h>
#include <locale.h>
int main(void) {
time_t now = time(NULL);
struct tm tmv = *localtime(&now);
// ロケールをC(英語)に固定
setlocale(LC_TIME, "C");
char buf_c[64];
strftime(buf_c, sizeof(buf_c), "%a %b %e %H:%M:%S %Y", &tmv);
printf("Cロケール: %s\n", buf_c);
// ロケールを(環境に存在すれば)日本語に変更
// 環境により"ja_JP.UTF-8"が存在しない場合があります
if (setlocale(LC_TIME, "ja_JP.UTF-8")) {
char buf_ja[64];
strftime(buf_ja, sizeof(buf_ja), "%a %b %e %H:%M:%S %Y", &tmv);
printf("日本語ロケール: %s\n", buf_ja);
} else {
printf("日本語ロケールはこの環境で利用できません\n");
}
return 0;
}
Cロケール: Thu Oct 16 14:32:05 2025
日本語ロケール: 木 10 16 14:32:05 2025
asctimeはロケールを細かく制御できず、表記の一貫性を保ちにくいため、国際化が必要な場面ではstrftimeが適しています。
書式を変えたいときはstrftime
asctimeは書式を変えられません。
たとえば「YYYY-MM-DD HH:MM:SS」のような形式や、末尾の改行を付けたくない場合、strftimeを用います。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm tmv = *localtime(&now);
char iso[32];
if (strftime(iso, sizeof(iso), "%Y-%m-%d %H:%M:%S", &tmv) == 0) {
fprintf(stderr, "strftimeに失敗しました\n");
return 1;
}
printf("ISO風: %s\n", iso);
return 0;
}
ISO風: 2025-10-16 14:32:05
自在な書式、末尾改行の有無、スレッドセーフ性を同時に満たすのがstrftimeの利点です。
まとめ
asctimeはstruct tmを手早く文字列化するのに便利ですが、末尾の改行、静的バッファ、スレッドセーフでないといった落とし穴があります。
実務では結果を自分のバッファにコピーする、またはstrftimeで直接フォーマットするのが安全です。
さらにstruct tmを自前で組み立てる際はmktimeで正規化し、曜日などの整合性を保つのがポイントです。
まずはasctimeで挙動を掴み、必要に応じてstrftimeへ移行する、という段階的な学習をおすすめします。
