C言語のtime()関数の使い方を詳しく解説【時間、時刻処理】

397, 2022-01-26

目次

C言語のtime()関数の使い方

C言語で時間を扱いたい場合はtime()関数とその周辺の関数群を使います。
time()関数とその周辺の関数群の扱い方を覚えておけば、C言語で時間を扱えるようになります。

C言語の時間を扱う関数はいろいろクセも多いので、扱う場合はその点に注意が必要です。
この記事ではtime()関数を始めとして周辺の関数群の使い方を解説します。

関連記事

time()関数の使い方

C言語のtime()関数はtime.hをインクルードすると使うことができます。

#include <time.h>

time_t time(time_t *tloc);

time()関数はtime_tのポインタ型の引数を1つ取り、time_t型の戻り値を返します。
time_t型は整数です。環境によってバイト数は異なります。
この関数はPOSIX時間(UNIX時間)からの経過時間を返します。

POSIX時間とは「1970-01-01 00:00:00 +0000 (UTC)」のことを指します。
このPOSIX時間からの経過秒数(エポック秒)を返すのがtime()関数です。

time()は引数が指定されている場合はその引数に値を格納します。
引数がNULLの場合は戻り値で値を返します。

time_t t;
time(&t);
time_t t = time(NULL);

どちらの方法を使うかは実際のコードによります。
実際にtime()関数を使ったコードは↓になります。

#include <stdio.h>
#include <time.h>

int main(void) {
    time_t t = time(NULL);  // POSIX時間からの経過時間を取得

    printf("t: %ld\n", t);  // 秒数を出力
    printf("sizeof time_t: %ld\n", sizeof(time_t));  // time_tのバイト数を出力

    return 0;
}

↑のコードをコンパイルして実行すると↓の結果になります。

t: 1640167501
sizeof time_t: 8

執筆時点の経過秒数は1640167501秒になっています。
また筆者の環境ではtime_tのサイズは8バイトになっています。
ちなみにこのtime_tのサイズが4バイトになっている環境は、いわゆる「2038年問題」で問題が起こる場合があります。
4バイト(符号付き32ビット整数)の値の範囲は最大で2,147,483,647でこれは2038/1/19 03:14:07までになります。
つまり4バイトではこの時刻を超えた場合に値があふれてしまうことになります。
そのためプログラムが誤動作を起こす可能性があります。

(^ _ ^)

4バイトじゃダメじゃったか

(・ v ・)

そうみたいね

ctime()で経過時間を文字列にする

time()関数で時刻を取ることができました。
あとはこれを見やすい文字列にしたいところです。
簡易的な方法としてctime()を使った方法があります。

#include <time.h>

char *ctime(const time_t *timep);

ctime()関数は引数に指定された時刻から文字列を生成して返します。
ctime()は関数内部の文字列のメモリをポインタで返します。
そのため非スレッドセーフです。マルチスレッド環境下では使用に注意が必要です。

#include <stdio.h>
#include <time.h>

int main(void) {
    time_t t = time(NULL);  // POSIX時間からの経過時間を取得
    const char *s = ctime(&t);  // 時刻を文字列に変換
    printf("%s\n", s);  // 文字列を出力
    return 0;
}

↑のコードをコンパイルして実行すると↓のような結果になります。

Wed Dec 22 19:40:56 2021

ctime()は簡単で便利な関数ですが、時刻のフォーマットを指定できないなど融通さはあまりありません。
また非スレッドセーフなのでマルチスレッド環境下では使えないという問題もあります。

経過時間をlocaltime()で時刻用の構造体に変換する

time()関数で得た経過時間を構造体に変換できると融通が利いて色々便利そうです。
そのための関数にgmtime()localtime()があります。

#include <time.h>

struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);

これらの関数はtime_tのポインタ型の引数を取り、struct tm構造体へのポインタを返します。
gmtime()GMT時間localtime()時差を含めたローカル時間に変換します。

struct tm構造体は↓のような構造になっています。

struct tm {
    int tm_sec;    // 秒 (0-60)
    int tm_min;    // 分 (0-59)
    int tm_hour;   // 時間 (0-23)
    int tm_mday;   // 月内の日付 (1-31)
    int tm_mon;    // 月 (0-11)
    int tm_year;   // 年 - 1900
    int tm_wday;   // 曜日 (0-6, 日曜 = 0)
    int tm_yday;   // 年内通算日 (0-365, 1月1日 は 0)
    int tm_isdst;  // 夏時間
};

localtimeを使うと↓のように時刻を取得できます。

#include <stdio.h>
#include <time.h>

int main(void) {
    const char *days[] = {"日", "月", "火", "水", "木", "金", "土"};
    time_t t = time(NULL);
    struct tm *tm = localtime(&t);  

    printf("%04d年%02d月%02d日(%s)%02d時%02d分%02d秒\n",
        tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
        days[tm->tm_wday], tm->tm_hour, tm->tm_min, tm->tm_sec
    );
    return 0;
}

↑のコードをコンパイルして実行すると↓のような結果になります。

2021年12月22日(水)20時11分30秒

struct tm構造体で注意すべき属性はtm_year, tm_mon, tm_wdayです。
tm_yearは1900年からの経過年数になっているので、現在の年数にしたい場合は↑のように1900を足します。
また月を表すtm_mon0から数えますので現在の月を得たい場合は1を足します。
tm_wdayは曜日を表す整数ですが、これは0(日曜日)から始まります。

gmtime()localtime()はともに非スレッドセーフで、マルチスレッド環境下では意図しない結果になる場合があります。
その場合はスレッドセーフなgmtime_r()localtime_r()を使います。

#include <time.h>

struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime_r(const time_t *timep, struct tm *result);

localtime_r()は第2引数にstruct tm構造体へのポインタを取ります。
このポインタの構造体に結果が保存されます。
使い方は↓の通りです。

#include <stdio.h>
#include <time.h>

int main(void) {
    const char *days[] = {"日", "月", "火", "水", "木", "金", "土"};
    time_t t = time(NULL);
    struct tm tm;
    localtime_r(&t, &tm);  

    printf("%04d年%02d月%02d日(%s)%02d時%02d分%02d秒\n",
        tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
        days[tm.tm_wday], tm.tm_hour, tm.tm_min, tm.tm_sec
    );
    return 0;
}

実行結果は↓になります。

2021年12月22日(水)20時12分34秒

tm構造体を書式で文字列にする

strftime()を使うとstruct tm構造体を書式で文字列に変換できます。

#include <stdio.h>

size_t strftime(char *buf, size_t bufsize, const char *format, const struct tm *tm);

strftime()は第1引数に文字配列のバッファ、第2引数にバッファのサイズ、第3引数に書式、第4引数にstruct tm構造体のポインタを取ります。
strftime()はバッファのバイト数が足りている場合はバッファのサイズを返します。
バッファのバイト数が足りなかった場合は0を返し、その場合のバッファの内容は未定義になります。

#include <stdio.h>
#include <time.h>

int main(void) {
    time_t t = time(NULL);  // 経過時間を得る
    struct tm tm;
    char buf[100];

    // 経過時間をローカル時間に変換する
    localtime_r(&t, &tm);

    // 書式に従ってtm構造体を文字列に変換する
    strftime(buf, sizeof buf, "%Y-%m-%d (%a) %H:%M:%S", &tm);

    printf("%s\n", buf);

    return 0;
}

↑のコードをコンパイルして実行すると↓のような結果になります。

2021-12-22 (Wed) 20:22:18

書式に使える指定子は色々ありますが、↑のコードで使っている指定子は以下の通りです。

  • %Y ... 年

  • %m ... 月

  • %d ... 日

  • %a ... 曜日

  • %H ... 時

  • %M ... 分

  • %S ... 秒

便利関数gettimef()を定義する

strftime()ラッパーを作っておくといざという時に簡単に時刻を文字列で取得できます。

#include <stdio.h>
#include <time.h>

/**
 * 書式で現在時刻を取得する
 *
 * @param buf 保存先バッファ
 * @param bufsize バッファサイズ
 * @param fmt 書式
 * @return 成功したらバッファのバイト数、バイト数が足りなかったら0
 */
size_t gettimef(char *buf, size_t bufsize, const char *fmt) {
    time_t t = time(NULL);
    struct tm tm;
    localtime_r(&t, &tm);
    return strftime(buf, bufsize, fmt, &tm);
}

int main(void) {
    char buf[100];
    gettimef(buf, sizeof buf, "%Y-%m-%d");

    printf("%s\n", buf);
    // 2021-12-22

    return 0;
}

mktimeでtm構造体を経過時間にする

mktime()を使うとstruct tm構造体をtime_tにすることができます。

#include <time.h>

time_t mktime(struct tm *tm);

mktime()関数は引数にstruct tm構造体へのポインタを取ります。
返り値はtime_tです。

#include <stdio.h>
#include <time.h>

int main(void) {
    struct tm tm = {
        .tm_sec = 1,    // 秒 (0-60)
        .tm_min = 2,    // 分 (0-59)
        .tm_hour = 3,   // 時間 (0-23)
        .tm_mday = 4,   // 月内の日付 (1-31)
        .tm_mon = 5,    // 月 (0-11)
        .tm_year = 2022 - 1900,   // 年 - 1900
        .tm_wday = 0,   // 曜日 (0-6, 日曜 = 0)
    };

    time_t t = mktime(&tm);  // 構造体を経過時間に変換
    printf("%ld\n", t);

    return 0;
}

↑のコードをコンパイルして実行すると↓の結果になります。

1654279321

おわりに

今回はtime()関数とその周辺の関数について解説しました。
時間を扱うプログラムはけっこう繊細です。
この記事を読んでバグを作らないようにしておきましょう。

(^ _ ^)

時間処理を甘く見るプログラマーは

(・ v ・)

痛い目を見る



この記事のアンケートを送信する