C言語編 第51章 日付と時間

先頭へ戻る

この章の概要

この章の概要です。

プロセッサ時間

プロセッサ時間とは、プログラムの実行が開始されてから、そのプログラムが使用した時間のことです。

同時に他のプログラム(プロセス)が動いている場合、他方が使用した時間の分は含まれません。

現在のプロセッサ時間を取得するには、clock関数を使用します。clock関数は、time.h で、以下のように宣言されています。

clock_t clock(void);

引数はありません。
戻り値は、現在のプロセッサ時間が返されます。ただし、表現できない数になっている場合や、何らかの理由でプロセッサ時間を取得することができない場合には -1(を clock_t型にキャストした値)が返されます。clock_t型は、環境によって異なりますが、何らかの算術型(整数型と浮動小数点型の総称)を typedef したものです。

ここまでは単純なのですが、問題は返されるプロセッサ時間の精度です。
現実のストップウォッチなどをイメージすると分かりますが、何千分の一秒の単位で細かく計測できるものもあれば、秒単位でしか計測できないキッチンタイマーのようなものもあります。それと同じで、コンピュータの世界におけるプロセッサ時間も、環境ごとの精度の違いがあります

実行環境のプロセッサ時間の精度は、CLOCKS_PER_SECマクロで定義されています。このマクロの置換結果は、「現実の1秒が、プロセッサ時間で幾つになるか」を表しています。

例えば、CLOCKS_PER_SECマクロの置換結果が 1000 であれば、現実の1秒は、プロセッサ時間で 1000 であるということです。逆に言えば、プロセッサ時間における 1 は、0.001秒だということになります。もし、clock_t型が整数型であれば、1 よりも細かい単位は表現できないので、その実行環境のプロセッサ時間の精度は 0.001秒単位だということになります。これより細かい時間を表すことは、プロセッサ時間では不可能だということです。

C言語の標準にはない方法を使って、もっと細かい計測ができる可能性はあります。

これらを踏まえて、経過時間を秒単位で調べるプログラムは次のようになります。

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

void process(void);

int main(void)
{
    clock_t begin, end;

    begin = clock();
    process();
    end = clock();

    printf( "result: %f seconds\n", (double)(end - begin) / CLOCKS_PER_SEC ); 

    return 0;
}

void process(void)
{
    char buf[80];

    puts( "何か入力すると終了します。" );
    fgets( buf, sizeof(buf), stdin );
}

実行結果

何か入力すると終了します。
abcde  <-- 入力内容
result: 3.679000 seconds

process関数の呼び出し前後で、clock関数を呼んで、プロセッサ時間を取得しておきます。2つのプロセッサ時間の差を見れば、process関数でどれだけの時間を消費したかを計測できるという訳です。

ここまで解説してきた通り、プロセッサ時間をそのまま使っても意味のある値にはなりません。process関数実行後の値から、実行前の値を引き、その結果を CLOCKS_PER_SECマクロの値で割ることによって、秒単位の時間を得られます。

なお、clock_t型は、環境によって整数型であったり、実数型であったりするため、double型にキャストして取り扱うと常に適切な結果が得られます。

カレンダー時間(暦時間)

カレンダー時間(暦時間)は、「2011年 4月 1日 12時 30分 30秒」のような現実の時刻を表現するものです。具体的には、ある基準時刻からの経過時間で保持されています。多くの環境では、基準時刻を「1970年 1月 1日 0時 0分 0秒」としています。

現在のカレンダー時間を取得するには、time関数を使います。time関数は、time.h に以下のように宣言されています。

time_t time(time_t* timer);

time_t型は、環境によって異なりますが、何らかの算術型を typedef したものです。

結果は戻り値で返される他、引数timer がヌルポインタでなければ、そのメモリアドレスにも格納されます。不要であれば、実引数はヌルポインタで構いません。

time関数で返された値だけを見ても、それがどんな時刻を示しているかは分かりません。基準時刻が環境ごとに異なる可能性がありますし、基準時刻が分かっていても、そこからの経過時間を time_t型で表したときにどんな値になるかは直感的には分からないです。

そこで、他の標準ライブラリ関数を併用して、理解できる形式に変換します。以下の関数が利用できます。

関数名 効果
gmtime カレンダー時間を、協定世界時(UTC) に変換する。
localtime カレンダー時間を、ローカル時間に変換する。
ctime カレンダー時間を、ローカル時間で表現した文字列に変換する。
difftime 2つのカレンダー時間の差を調べる。

ローカル時間協定世界時については、後の項で説明することにして、ここでは difftime関数だけ取り上げておきます。difftime関数は、time.h に以下のように宣言されています。

double difftime(time_t t1, time_t t0);

difftime関数は、t1 - t0 をした結果を double型で返します。結果はいかなる環境でも必ず秒単位です

ごく単純な関数ですが、time_t型の差を求めるとき、普通に減算で行うのではなく、必ずこの関数を使わないといけないことに注意して下さい。time_t型は、算術型の typedef ですから、普通に減算できてしまいますが、どのようなフォーマットで表現されているかは環境によって異なるので、普通の減算は必ずしも正しくありません。

次のサンプルプログラムは、clock関数のところの例と同様、経過時間を求めています。

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

void process(void);

int main(void)
{
    time_t begin, end;

    begin = time( NULL );
    process();
    end = time( NULL );

    printf( "result: %f seconds\n", difftime(end, begin) ); 

    return 0;
}

void process(void)
{
    char buf[80];

    puts( "何か入力すると終了します。" );
    fgets( buf, sizeof(buf), stdin );
}

実行結果

何か入力すると終了します。
abcde
result: 4.000000 seconds

協定世界時(UTC)

協定世界時 (UTC) とは、世界で統一されている標準時のことです。具体的には、イギリスのグリニッジ(経度0度)における時間を基準時としているので、グリニッジ標準時 (GMT) のことでもあります。

協定世界時を直接取得する標準ライブラリ関数はありませんが、time関数の結果を、gmtime関数に渡せば得られます。gmtime関数は、time.h に以下のように宣言されています。

struct tm* gmtime(const time_t *t);

time_t型の値は引数から渡しますが、ポインタ型になっています。少し面倒ですが、time関数の結果を渡したいのなら、time関数の結果を変数に受け取った後、その変数のメモリアドレスを渡す必要があります。

戻り値は、tm構造体のポインタです。tm構造体には多数のメンバがあり、そこに協定世界時を表現する各要素が格納されています。なお、変換に失敗した場合にはヌルポインタが返されます。

戻り値がポインタ型なのが心配になりますが(第33章)、実際そこは注意しなければなりません。このポインタは、静的記憶域期間を持った tm構造体変数を指している可能性があり、2回以上 gmtime関数を呼び出したときに返されるポインタは恐らく同一のものです。そのため、1回目の呼び出しで取得した情報を後で使いたいというケースでは、呼び出し元でコピーを取っておかないと、2回目の呼び出しで上書きされてしまいます。

tm構造体は、time.h に以下のように宣言されています。

struct tm {
    int tm_sec;     /* 秒 (0~61) */
    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;    /* 年始からの通算日 */
    int tm_isdst;   /* 0 なら季節時間無し。1以上で季節時間あり。負数は不明 */
};

上記のメンバがすべて、この名前と型で含まれていることは保証されていますが、これら以外にもメンバが存在している可能性はあります。各メンバの意味について、コメントに書いておきましたが、幾つか補足しなければならない部分もあります。

まず、秒を表す tm_secメンバの最大値は 61(C99 からは 60)まであり得ます。これは、閏秒(うるうびょう)を表現できるようにするための処置です。閏秒は、地球の自転速度がほんのわずかに変化していることで、生まれてしまう誤差を修正するために、挿入あるいは削除される調整のための秒のことを指します。

C99規格からは、上限が 60 に変更されました。以前はうるう秒が 2秒発生することを想定していたようです。

2018年までに秒が削除されたことは無いようです。もし、秒が削除された場合、tm_secメンバの値が 58 までしか増えない分があるはずです。

月を表す tm_monメンバは、0基準になっていますから、日本人感覚とは合致していません。例えば、「3月」なら「2」になります。

年を表す tm_yearメンバは、西暦1900年を基準とした経過年です。例えば、2018年ならば 118 になります。

曜日を表す tm_wdayメンバは、日曜日を 0 として、0~6 の範囲で表現されます。

tm_isdstメンバは、季節によって時間に調整を加えている国・地域のためのフラグになっています。季節時間になっているときは 1以上、なっていないときは 0、不明な場合は負の値です。
季節時間の具体例は、アメリカなどで導入されている夏時間(サマータイム)があります。これは一例ではあ りますが、事実上、季節時間=夏時間と考えても良いようです。
世界協定時としては、季節時間の制度はないので、tm_isdstメンバは常に 0 になっています。tm構造体は他でも使われるので、そちらではこのフラグが意味を持っている可能性があります。

夏時間は、夏は日没が遅いので、仕事を早めに切り上げて、明るい時間を有効活用しようという考えで導入されています。国・地域ごとに詳細は異なりますが、例えば、夏時間に突入する瞬間に、時計の針を 1時間進めることで実現されます。普段 17:00 で終業の会社があるとすると、いつも通りに時計が 17:00 になったときに退社すると、それは本来は 16:00 なので、まだ明るいうちに帰宅できることになります。夏時間期間が終わったら、時計の針を 1時間逆戻りさせます。

次のプログラムは、プログラムを実行した時刻を、協定世界時で出力します。

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

static void printTm(const struct tm* t);

int main(void)
{
    struct tm* utc;
    time_t t;

    t = time( NULL );
    utc = gmtime( &t );

    printTm( utc );

    return 0;
}

void printTm(const struct tm* t)
{
    assert( t != NULL );

    printf( "年       : %d\n", t->tm_year );
    printf( "月       : %d\n", t->tm_mon );
    printf( "日       : %d\n", t->tm_mday );
    printf( "曜日     : %d\n", t->tm_wday );
    printf( "時       : %d\n", t->tm_hour );
    printf( "分       : %d\n", t->tm_min );
    printf( "秒       : %d\n", t->tm_sec );
    printf( "通算日   : %d\n", t->tm_yday );
    printf( "季節時間 : %d\n", t->tm_isdst );
}

実行結果

年       : 118
月       : 2
日       : 26
曜日     : 1
時       : 1
分       : 37
秒       : 56
通算日   : 84
季節時間 : 0

tm構造体の内容は、asctime関数を使って文字列化することができます。asctime関数は、time.h に以下のように宣言されています。

char* asctime(const struct tm* t);

引数に tm構造体を指すポインタを渡すと、文字列化して返却します。文字列のフォーマットは規格で定められており、必ず同じ形式の結果を得られます。

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

int main(void)
{
    struct tm* utc;
    time_t t;

    t = time( NULL );
    utc = gmtime( &t );

    puts( asctime( utc ) );

    return 0;
}

実行結果

Mon Mar 26 01:37:56 2018

ローカル時間

ローカル時間(地域時間)は、特定の国や地域における標準時のことです。日本であれば、明石市を通る東経135度を標準時と定め、全国で統一しています。

ローカル時間を表現するためにも tm構造体が使われます。また、ローカル時間を取得するには、time関数の結果を localtime関数に渡します。この辺りの流れは、世界協定時を得るための流れと似ています。

localtime関数は、time.h に以下のように宣言されています。

struct tm* localtime(const time_t *t);

使い方も注意点も gmtime関数と同じです。

次のプログラムは、プログラムを実行した時刻を、ローカル時間で出力します。

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

void printTm(const struct tm* t);

int main(void)
{
    struct tm* jst;
    time_t t;

    t = time( NULL );
    jst = localtime( &t );

    printTm( jst );

    return 0;
}

void printTm(const struct tm* t)
{
    assert( t != NULL );

    printf( "年       : %d\n", t->tm_year );
    printf( "月       : %d\n", t->tm_mon );
    printf( "日       : %d\n", t->tm_mday );
    printf( "曜日     : %d\n", t->tm_wday );
    printf( "時       : %d\n", t->tm_hour );
    printf( "分       : %d\n", t->tm_min );
    printf( "秒       : %d\n", t->tm_sec );
    printf( "通算日   : %d\n", t->tm_yday );
    printf( "季節時間 : %d\n", t->tm_isdst );
}

実行結果

年       : 118
月       : 2
日       : 26
曜日     : 1
時       : 10
分       : 37
秒       : 56
通算日   : 84
季節時間 : 0

gmtime関数のときのサンプルプログラムを、そのまま localtime関数に変更しただけです。世界協定時とローカル時間との違いが「時」にだけ現れていますが、これは要するに時差です。日本のローカル時間は、世界協定時に比べて「+9時間」の時差があります。

また、ローカル時間の場合には、tm_isdstメンバに季節時間に応じた値が格納される可能性があります。日本では無関係なので、常に 0 のはずです。

localtime関数の結果が、どの地域のローカル時間になるのかは実行環境の設定に依ります。この辺りは、使用している OS の種類などによっても事情が異なるので一概には言えませんが、普通、日本の製品はデフォルトで日本の時間が使われるようになっているはずです。


ローカル時間の場合、time関数の結果を ctime関数に渡せば、文字列化してくれます。ctime関数は、time.h に以下のように宣言されています。

char* ctime(const struct tm* t);

ctime関数がしていることは、localtime関数の戻り値を asctime関数に渡すだけです。そのため、単なるショートカットのような関数に過ぎません。asctime関数が返す文字列のフォーマットは規格で定められているので、ctime関数の結果もまた、常に同じ形式になります。

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

int main(void)
{
    time_t t;

    t = time( NULL );
    puts( ctime(&t) );

    return 0;
}

実行結果

Mon Mar 26 10:37:56 2018


また、mktime関数を使うと、ローカル時間が格納された tm構造体のポインタから、time_t型の値を作り出せます。mktime関数は、time.h に以下のように宣言されています。

time_t mktime(struct tm* t);

この関数は、tm構造体の値を time_t型に変換すると考えても良いのですが、どちらかというと、time_t型の値を作り出すと考えた方が適切です。関数名の「mk」は「make(作る)」から来ています。引数で指定したポインタが指す tm構造体に格納されている情報を、ローカル時間であるとみなし、その時刻と一致する time_t型の値を作って返します。

このとき、メンバ tm_wday(曜日)と tm_yday(年始からの通算日)の値は無視されます。また、これら以外のメンバについては、gmtime関数や localtime関数が返すときのような値の範囲を逸脱していても構いません。例えば、tm_min(分)が 90 になっていても、1時間と 29分のことであると認識されます。

また、メンバtm_isdst(季節時間)の値が、結果の生成に影響を与えます。この値が 1以上なら、季節時間の影響下にあるとして結果を決めますし、0 なら季節時間の影響下にないと判断します。負数の場合は、mktime関数自身が判断を下します。やはり日本では関係ないので、0 を入れておきます。

各メンバの値に問題がなく、time_t型の値を生成できた場合は、メンバ tm_wday(曜日)と tm_yday(年始からの通算日)に、正しい値が格納されます。また、ほかのメンバについても、適切な範囲に収まるように修正されます。例えば、tm_min(分)が 90 になっていたら、tm_min を 30 に直し、tm_hour に +1 するでしょう。

以下は mktime関数の使用例です。

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

int main(void)
{
    struct tm* jst;
    time_t t;

    t = time( NULL );
    printf( "%ld\n", (long int)t );

    jst = localtime( &t );
    printf( "%ld\n", (long int)mktime( jst ) );

    return 0;
}

実行結果

1522030822
1522030822


練習問題

問題① 標準入力から受け取った西暦年から、現在までの年数を求めるプログラムを作って下さい。

問題② プログラムの処理の進行を、5秒間停止させる方法を考えて、試してみて下さい。

問題③ 標準入力から受け取った西暦年・月・日から、その日の曜日を求めて表示するプログラムを作って下さい。


解答ページはこちら

参考リンク



更新履歴

'2018/5/3 章のタイトルを変更(日付と時間、乱数 -> 日付と時間) 乱数に関する話題を、第54章へ移動。

'2018/4/20 「NULL」よりも「ヌルポインタ」が適切な箇所について、「ヌルポインタ」に修正。

'2018/3/26 全面的に文章を見直し、修正を行った。

'2013/8/31 「ローカル時間」のサンプルプログラム内で変数名が間違っていたのを修正。

'2011/4/7 新規作成。



前の章へ(第50章 列挙型)

次の章へ(第52章 可変個引数)

C言語編のトップページへ

Programming Place Plus のトップページへ


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS