閉じる

【C言語】 asctimeの使い方入門|日時を文字列に変換する方法

C言語で日時を文字列として扱う際、標準ライブラリのasctimeはとても手軽に使える関数です。

ただし、シンプルな反面、制約や注意点が多い関数でもあります。

本記事では、asctimeの基本的な役割から具体的なコード例、スレッドセーフ性などの注意点、さらに実務における代替案まで、図解を交えながら段階的に解説していきます。

asctimeとは|C言語の日時変換関数の概要

asctimeの基本機能と役割

asctimeは、C言語で日時情報を表すstruct tm構造体を、人が読める形式の文字列へ変換する標準ライブラリ関数です。

日時処理の全体像とasctimeの位置づけ

時間処理では、一般的に次の3種類の表現を行き来しながら処理します。

  1. time_t
    1970-01-01 00:00:00(UTC)からの経過秒数で表した、機械向けの整数値です。
  2. struct tm
    年・月・日・時・分・秒などに分解された構造体で、人間にも理解しやすい形式です。
  3. 文字列
    画面表示やログ出力、ファイル保存などに用いる、フォーマット済みのテキストです。

このうちstruct tm → 文字列の変換を担当するのがasctimeです。

プログラム内部で計算・変換したstruct tmを、そのまま簡易的に文字列化したいときに便利です。

asctimeとstrftimeの違い

同じくstruct tmを文字列に変換する関数として、strftimeがあります。

両者は目的が似ていますが、性質はかなり異なります。

文章で整理すると次のようになります。

asctime

  • フォーマットが完全に固定で、変更できません。
  • 内部に持つ静的なバッファに結果を書き込み、そのポインタを返します。
  • 出力文字列の長さを制御できません。
  • 実装によってはスレッドセーフでないことがあります。

一方、strftime

  • フォーマット文字列を指定して、出力形式を柔軟に変更できます。
  • 呼び出し側が用意したバッファに書き込むため、バッファサイズを自分で管理できます。
  • C標準ライブラリの設計として、スレッドセーフに扱いやすい関数です(実装依存の部分はあります)。

簡単に試したい・一時的なデバッグ表示をしたい場合はasctime、本番コードや柔軟なフォーマットが必要な場合はstrftimeという使い分けが基本になります。

asctimeが返す文字列フォーマット

asctimeが返す文字列のフォーマットは、C標準でおおよそ次の形式と決められています。

"Www Mmm dd hh:mm:ss yyyy\n\0"

それぞれの意味は次の通りです。

  • Www: 曜日(3文字、例: Sun, Mon, Tue …)
  • Mmm: 月名(3文字、例: Jan, Feb, Mar …)
  • dd: 日付(2桁、先頭に空白が入る場合があります)
  • hh: 時(00〜23)
  • mm: 分(00〜59)
  • ss: 秒(00〜60、うるう秒対応)
  • yyyy: 西暦4桁
  • 最後に'\n''\0'が続きます。

典型的な一例は次のようになります。

"Wed Jun 21 18:25:30 2023\n"

末尾に改行文字'\n'が入る点が非常に重要です。

標準出力にそのままprintfすると、自動的に行が折り返される反面、ログの組み立てなどで意図しない改行が紛れ込むことがあります。

asctimeの使い方

必要なヘッダと関数プロトタイプ

まず、asctimeを使うために必要なヘッダファイルと、関数プロトタイプを確認します。

インクルードすべきヘッダ

asctimeはtime.hに宣言されていますので、次のヘッダをインクルードします。

C言語
#include <time.h>

関数プロトタイプ

C標準で定義されているasctimeのプロトタイプは次の通りです。

C言語
char *asctime(const struct tm *timeptr);

ポイントを整理すると、次のようになります。

  • 引数はconst struct tm *で、読み取り専用のstruct tmへのポインタです。
  • 戻り値は、内部の静的バッファを指すchar *です。
  • この戻り値をfreeしてはいけませんし、自分でmallocしたメモリでもありません。

戻り値の文字列は、その後の別の呼び出しや関数により上書きされる可能性があるため、必要であれば自分でバッファにコピーして使う必要があります。

struct tmからasctimeで文字列に変換する手順

asctimeを用いて現在時刻を文字列に変換する典型的な流れは、次のようなステップになります。

実際の処理の順序を文章で説明すると、次のようになります。

  1. 現在時刻を取得する
    time_t型変数に、time関数で現在時刻を取得します。
  2. time_tをstruct tmに変換する
    localtimeまたはgmtimeを使って、time_tからstruct tmへ変換します。
    1. localtime: ローカルタイム(タイムゾーンを考慮した時間)
    2. gmtime: 協定世界時(UTC)
  3. struct tmをasctimeで文字列に変換する
    2で得られたstruct tm *を、asctimeに渡します。
  4. 結果の文字列を表示・利用する
    戻り値のchar *printfなどに渡して表示します。

asctimeの具体的なコード例

ここでは、現在のローカル時刻をasctimeで文字列に変換して表示する、最も基本的なサンプルコードを示します。

C言語
#include <stdio.h>  // printf
#include <time.h>   // time, localtime, asctime

int main(void)
{
    time_t now;             // 現在時刻を保持する変数(time_t型)
    struct tm *tm_now;      // 分解した時刻(struct tm)へのポインタ
    char *timestr;          // asctimeが返す文字列へのポインタ

    // 現在時刻を取得する
    // time(&now) は1970-01-01 00:00:00(UTC) からの経過秒数を now に格納します
    time(&now);

    // time_t をローカル時間の struct tm に変換する
    // localtime は内部で静的な struct tm を使用し、そのアドレスを返します
    tm_now = localtime(&now);
    if (tm_now == NULL) {
        // 何らかの理由で変換に失敗した場合
        printf("localtime failed.\n");
        return 1;
    }

    // struct tm を asctime で文字列に変換する
    // 戻り値は静的バッファへのポインタなので、free はしてはいけません
    timestr = asctime(tm_now);
    if (timestr == NULL) {
        // NULL が返る可能性は実装依存ですが、チェックしておくと安全です
        printf("asctime failed.\n");
        return 1;
    }

    // 結果を表示する
    // timestr の末尾には改行文字('\n')が含まれている点に注意してください
    printf("Current time is: %s", timestr);

    return 0;
}

上のプログラムを実行したときの出力例は、次のようになります。

実行結果
Current time is: Wed Jun 21 18:25:30 2023

実際の日時に応じて、曜日・月・時刻は変わります。

asctimeが返す文字列の扱い方

asctimeの戻り値は「扱い方に注意が必要」です。

重要なポイントを整理します。

返される文字列は静的バッファ

asctimeが返す文字列は、関数内部で管理している静的バッファに格納されています。

呼び出すたびに同じ領域が再利用されるため、次のような性質があります。

  • ポインタ自体は有効のままですが、内容は次のasctime呼び出しで上書きされます。
  • freeしてはいけません。
  • 複数スレッドから同時に使うと、競合して意図しない結果になる可能性があります。

必要なら自前バッファにコピーする

asctimeが返した文字列を、後で参照したい・複数の結果を同時に保持したい場合は、自分でバッファを用意してコピーしておきます。

C言語
#include <stdio.h>
#include <time.h>
#include <string.h> // strcpy

int main(void)
{
    time_t now;
    struct tm *tm_now;
    char *timestr;
    char buf[32];   // asctime の出力をコピーするためのバッファ

    time(&now);
    tm_now = localtime(&now);
    if (tm_now == NULL) {
        printf("localtime failed.\n");
        return 1;
    }

    timestr = asctime(tm_now);
    if (timestr == NULL) {
        printf("asctime failed.\n");
        return 1;
    }

    // 自前のバッファにコピーする
    // 実装によりますが、asctime の出力は 26文字 + '\0' 程度で収まることが多いです
    // ただし、標準では厳密なバイト数は規定されていません
    strcpy(buf, timestr);

    printf("asctime からの文字列 : %s", timestr);
    printf("コピーした文字列    : %s", buf);

    return 0;
}

このようにコピーしておけば、後続のasctime呼び出しに影響されず文字列を保持できます。

asctimeの注意点と制約

スレッドセーフでない理由

asctimeは多くの実装でスレッドセーフではありません

理由はシンプルで、内部に持つ静的なバッファを共有しているからです。

マルチスレッド環境で、次のような状況が起こり得ます。

  1. スレッドAがasctimeを呼び出し、静的バッファに"時刻A"を書き込む。
  2. まだスレッドAが結果を使い終えていないうちに、スレッドBがasctimeを呼び出し、同じバッファに"時刻B"を書き込む。
  3. スレッドAが再びバッファを参照すると、期待していた「時刻A」ではなく「時刻B」が入っている

このようにデータ競合が簡単に起きるため、マルチスレッドプログラムではasctimeの直接使用は避けるか、ミューテックスなどで保護する必要があります。

返されるバッファの上書きタイミング

静的バッファがいつ上書きされるのかを、もう少し具体的に見ておきます。

同一スレッド内での複数回呼び出し

同じスレッドでasctimeを複数回続けて呼ぶと、最後に呼び出した結果だけがバッファに残ることになります。

C言語
#include <stdio.h>
#include <time.h>

int main(void)
{
    time_t t1 = 0;             // 1970-01-01 00:00:00 UTC
    time_t t2 = t1 + 3600;     // 1時間後

    struct tm *tm1 = gmtime(&t1);
    struct tm *tm2 = gmtime(&t2);

    char *s1 = asctime(tm1);
    char *s2 = asctime(tm2);

    // s1 と s2 はどちらも「同じ静的バッファ」を指しています

    printf("s1: %s", s1);
    printf("s2: %s", s2);

    return 0;
}

期待としてはs1が「1970-01-01 00:00:00」、s2が「1970-01-01 01:00:00」と表示されてほしいところですが、実際にはどちらのprintfも最後の結果(01:00:00)を表示する可能性が高いです。

これは戻り値ポインタが同じバッファを指しているためであり、そのバッファは2回目のasctime呼び出しで上書きされてしまうからです。

localtimeなど他の関数との組み合わせ

注意すべきなのは、asctimeだけでなくlocaltime・gmtime・ctimeなども、同様に静的バッファを返す場合があることです。

多くの実装では、以下の関数はすべて内部で静的な領域を共有している、もしくは別々に用意しているものの、やはり静的であることが多いです。

  • asctime
  • ctime
  • localtime
  • gmtime

このため、asctimeの結果は後続のctime呼び出しなどで上書きされる可能性があります。

安全に使うには、必要なタイミングで必ずコピーしておくことが大切です。

バッファオーバーフローとasctime_r

asctimeには、古い実装においてバッファオーバーフローの危険性が指摘されてきました。

具体的には、struct tmのメンバーに異常に大きな値が入っている場合などに、想定より長い文字列を生成してしまう可能性があります。

asctime_rによる安全な代替

多くのUnix系システムでは、スレッドセーフでかつバッファを呼び出し側が用意するasctime_rという拡張関数が提供されています。

C言語
#include <stdio.h>
#include <time.h>

int main(void)
{
    time_t now;
    struct tm *tm_now;
    char buf[32];  // 呼び出し側が用意するバッファ

    time(&now);
    tm_now = localtime(&now);
    if (tm_now == NULL) {
        printf("localtime failed.\n");
        return 1;
    }

    // asctime_r は buf に結果を書き込みます
    // 戻り値は buf へのポインタ、失敗時は NULL です
    if (asctime_r(tm_now, buf) == NULL) {
        printf("asctime_r failed.\n");
        return 1;
    }

    printf("Current time is: %s", buf);

    return 0;
}
実行結果
Current time is: Wed Jun 21 18:25:30 2023

asctime_rはPOSIX拡張であり、Windowsなど環境によっては利用できない場合があります。

その場合はstrftimeを使うか、自前でバッファサイズチェックを行う必要があります。

ロケール(ローカライズ)に関する注意点

asctimeの出力は、曜日や月名が英語3文字で固定されています。

これはロケールに依存しない形式であるとも言えますが、日本語表記などにしたい場合には不便です。

  • 「Wed Jun 21 18:25:30 2023」のように、常に英語表記になる。
  • ロケール設定(例: setlocale)で「水 6月 21 …」といった日本語表示にはなりません。
  • 日付形式も「YYYY/MM/DD」のように変えることはできません。

言語や地域に合わせた日時表記が必要なアプリケーションでは、asctimeだけでは不十分であり、後述するstrftimeやその他のライブラリと組み合わせる必要があります。

asctimeの代替と実務的な使い分け

strftimeで柔軟な日時フォーマットを行う方法

実務的には、日時の文字列化にはstrftimeを使うことがほとんどです。

strftimeは、printfスタイルのフォーマット指定に似た書式文字を使い、出力形式を細かく制御できます。

strftimeの基本的な使い方

C言語
#include <stdio.h>
#include <time.h>

int main(void)
{
    time_t now;
    struct tm *tm_now;
    char buf[64];  // 出力先バッファ

    time(&now);
    tm_now = localtime(&now);
    if (tm_now == NULL) {
        printf("localtime failed.\n");
        return 1;
    }

    // 「YYYY-MM-DD HH:MM:SS」形式でフォーマットする例
    // %Y: 西暦4桁, %m: 月(01-12), %d: 日(01-31)
    // %H: 時(00-23), %M: 分(00-59), %S: 秒(00-60)
    if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm_now) == 0) {
        // バッファに収まりきらなかった場合、0 が返ります
        printf("strftime failed (buffer too small?).\n");
        return 1;
    }

    printf("Formatted time: %s\n", buf);

    return 0;
}
実行結果
Formatted time: 2023-06-21 18:25:30

strftimeの利点は次の通りです。

  • フォーマット文字列を変えるだけで、多様な日時形式を扱える。
  • バッファサイズを自分で指定するため、バッファオーバーフローのリスクを低減できる。
  • ロケールに応じて、曜日や月名を日本語で表示させる、といったことも可能です。

asctimeとctimeの使い分け

ctimeは、time_tから直接asctime形式の文字列を得るための関数です。

内部的にはlocaltime + asctimeを行っているようなイメージで捉えるとわかりやすいです。

C言語
#include <stdio.h>
#include <time.h>

int main(void)
{
    time_t now;
    char *timestr;

    time(&now);

    // time_t から直接文字列に変換する
    timestr = ctime(&now);
    if (timestr == NULL) {
        printf("ctime failed.\n");
        return 1;
    }

    printf("Current time is: %s", timestr);

    return 0;
}

使い分けの目安は次のようになります。

  • ctime: すでにtime_tがあり、簡単に文字列へ変換したいだけのとき。
  • asctime: struct tmを自前で構築・編集していて、その内容を文字列化したいとき。

どちらも静的バッファを返す点・フォーマットが固定である点は共通しているため、注意点もほぼ同じです。

実務でasctimeを使うべきケース・避けるケース

最後に、実務的な観点でasctimeをいつ使うべきか、いつ避けるべきかをまとめます。

asctimeを使うのが向いているケース

  • シンプルなデバッグ出力
    プログラムの一時的な確認用として、printf("t = %s\n", asctime(tm));のように気軽に使うのは有用です。
  • 簡易ツールやサンプルコード
    教材や検証用プログラムなど、可読性と簡潔さを優先する場面では、asctimeのシンプルさが役に立ちます。
  • スレッドが1つだけの小さなプログラム
    マルチスレッドやロケール対応が不要な、小規模なユーティリティであれば、実害が少ない場合があります。

asctimeの使用を避けるべきケース

  • 本番運用されるサーバー・アプリケーション
    ログフォーマットの統一、タイムゾーン対応、ローカライズ、保守性を考えるとstrftimeなどの柔軟な手段を選ぶべきです。
  • マルチスレッド環境
    スレッドセーフでない関数を使うと、原因のわかりにくい不具合を生むリスクがあります。asctime_rやstrftimeなど、スレッドセーフな手段を選ぶ方が安全です。
  • 国際化・ローカライズが必要なアプリケーション
    日本語や他言語への対応、ユーザーごとのロケール設定に応じた日時表示など、高度な表示制御が必要な場面ではasctimeは不適切です。
  • ログ形式が厳密に決まっているシステム
    たとえば「ISO 8601形式(2025-12-12T10:20:30+09:00)でなければならない」といった要件がある場合、固定フォーマットのasctimeはそもそも使えません。

結論として、asctimeは「学習用・デバッグ用・簡易用途」には便利ですが、「本番コード・汎用ライブラリ・長期運用するシステム」では避け、strftimeや専用ライブラリへの置き換えを検討すべき関数だと言えます。

まとめ

asctimeは、C言語標準ライブラリに含まれるstruct tmから人間が読める文字列へ変換するためのシンプルな関数です。

ヘッダtime.hをインクルードし、localtimegmtimeで得たstruct tmを渡すだけで、固定フォーマットの日時文字列を簡単に得られます。

一方で、内部静的バッファによる非スレッドセーフ性フォーマット固定ロケール非対応など、多くの制約があることも理解しておく必要があります。

実務ではstrftimeやasctime_rなどの代替を軸にしつつ、asctimeは主にデバッグや簡易用途で限定的に使う、というスタンスで活用すると良いでしょう。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!