このページでは、C言語での「時刻の扱い方」について解説していきます!
C言語では時刻を扱うための標準ライブラリ関数が用意されており、それを利用することで時刻を扱うことが可能となります。
ということで、その時刻を扱うための標準ライブラリ関数とともに、C言語での時刻の扱い方について解説していきたいと思います!
Contents
time.h
をインクルードする
まず、前述の通りC言語には時刻を扱うための標準ライブラリ関数が用意されており、この関数は time.h
というヘッダーファイルに定義されています。そのため、時刻を扱う際には time.h
をインクルードする必要があります。
#include <time.h>
補足しておくと、実は環境によっては他のヘッダーファイルをインクルードすることで “標準ライブラリ関数以外” の時刻を扱うための関数を使用することも可能となります。
ただ、このページでは標準ライブラリ関数のみを紹介していくこととしたいため、time.h
をインクルードした場合の時刻の扱い方のみをを説明していきます。
時刻を扱う型を利用する(time_t
型と struct tm
型)
time.h
をインクルードすることで、時刻を扱う型として time_t
型と struct tm
型が利用できるようになります。
スポンサーリンク
time_t
型
time_t
型は、多くの環境で「UNIX 時間(POSIX 時間とも呼ぶ)」を扱うための型として用意されており、UNIX 時間は協定世界時 (UTC) での 1970 年 1 月 1 日 0 時 0 分 0 秒からの経過秒数として扱われます。
もしかしたら環境によっては time_t
型で扱うデータの意味合いが異なる可能性もありますが、このページでは time_t
型が UNIX 時間を整数として扱う型であることを前提に解説を進めていきます。
この UNIX 時間は後述の time
関数で取得することが可能であり、取得結果は time_t
型のデータとなります。
例えば、私の PC の時刻表示が 2023 年 1 月 10 日 6 時 25 分 44 秒の際に取得した UNIX 時間は 1673299544
となっていました。この UNIX 時間は、前述の通り 1970 年 1 月 1 日 0 時 0 分 0 秒からの経過秒数なのですが、この値だと時刻の情報としてはちょっと扱いにくいと感じる方も多いでしょう。例えば、この UNIX 時間から日付などを算出しようと思えば(先程の例で言えば 2023 年 1 月 10 日 6 時 25 分 44 秒に逆変換する)、各月の日数や閏年の有無などを考慮しながら計算を行う必要があるので不便です。
struct tm
型
この不便さを解消するために「年・月・日・時・分・秒」等に分離した形式で時刻を扱えるよう、time.h
では struct tm
型が定義されています。この struct tm
型は構造体の型であり、下記のように定義されています。
struct tm {
int tm_sec; /* seconds after the minute [0-60] */
int tm_min; /* minutes after the hour [0-59] */
int tm_hour; /* hours since midnight [0-23] */
int tm_mday; /* day of the month [1-31] */
int tm_mon; /* months since January [0-11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday [0-6] */
int tm_yday; /* days since January 1 [0-365] */
int tm_isdst; /* Daylight Savings Time flag */
long tm_gmtoff; /* offset from UTC in seconds */
char *tm_zone; /* timezone abbreviation */
};
各メンバの意味はコメントに記載されているので説明は省略しますが、単なる「協定世界時 (UTC) での 1970 年 1 月 1 日 0 時 0 分 0 秒からの経過秒数」ではなく、年・月・日・時・分・秒を分離して各メンバで管理できるようになっているため、struct tm
型を利用した方が時刻としては扱いやすい場合が多いのではないかと思います。
後述で説明しますが、この struct tm
型のデータは time_t
型のデータを localtime
関数等で変換することにより取得することができます。
time_t
型の時刻を取得する(time
関数)
ここからは、時刻を扱うために利用する関数の紹介をしていきます。
スポンサーリンク
time
関数
最初に説明するのが time
関数で、この time
関数は実行したタイミングの UNIX 時間を取得する関数となります。
返却値の型は time_t
となります。
time_t time(time_t *tloc);
time
関数は “実行したタイミング” の UNIX 時間を返却する関数になります。例えば下記のように実行すれば、実行したタイミングの UNIX 時間が変数 unix_time
に格納されることになります。
time_t unix_time;
unix_time = time(NULL);
上記では引数に NULL
を指定していますが、NULL
以外のアドレスを指定した場合、time
関数実行時には引数で指定したアドレスにも UNIX 時間が格納されるようになっています。つまり、次のように time
関数を実行したとしても、上記の実行例と同様の結果を得ることができます。
time_t unix_time;
time(&unix_time);
time
関数の返却値の利用先
前述の通り、time
関数から得られるのは time_t
型のデータです。ただ、time_t
型のデータは少し年月日や時刻としては扱いにくいです。
そのため、年月日や時刻を扱う際には、time_t
型のデータを struct tm
型のデータに変換し、その struct tm
型のデータを使用することになることが多くなると思います。
では、time_t
型のデータ自体には利用価値が無いかというと、そういうわけではありません。例えば、2つの時刻の差分を求める場合などは、むしろ time_t
型の方が扱いやすいと思います。
また、time_t
型のデータは乱数のシード(種)としても用いることが多いです。詳しくは下記ページで解説していますが、time
関数の返却値を srand
関数の引数に指定することで、プログラム起動ごとに毎回異なる乱数列を得ることもできるようになります。
struct tm
型の時刻を取得する(gmtime
関数・localtime
関数)
続いて struct tm
型の時刻のデータを取得する関数として gmtime
と localtime
を紹介します。
スポンサーリンク
gmtime
gmtime
は引数で指定されたアドレスに格納されている time_t
型のデータを struct tm
型のデータに変換する関数になります。
struct tm *gmtime(const time_t *timeptr);
gmtime
関数の引数には time_t
型のデータへのアドレスを指定します。そして、gmtime
関数の返却値は struct tm
型のデータへのアドレスとなり、このアドレスには「引数で指定したアドレスの time_t
型のデータ」を struct tm
型に変換した結果のデータが格納されることになります。
前述の通り、time_t
型のデータは time
関数によって取得できるため、time
関数で取得したデータのアドレスを指定するのが通常の使い方になります。
また、時刻を扱う型を利用する(time_t 型と struct tm 型) でも説明したように、struct tm
型には tm_year
、tm_mon
、tm_mday
といったメンバが存在し、これらを利用して年・月・日等を取得するようなことも可能となります。
time_t timer;
struct tm *gm_time;
timer = time(NULL);
gm_time = gmtime(&timer);
printf("%s:%d年%d月%d日\n", gm_time->tm_zone, gm_time->tm_year + 1900, gm_time->tm_mon + 1, gm_time->tm_mday);
あくまでも gmtime
関数は “既に取得済みの time_t
型のデータ” から struct tm
型のデータを取得するための関数であり、実行したタイミングの struct tm
型のデータが得られるというわけではない点に注意してください。
localtime
localtime
は、gmtime
同様に引数で指定されたアドレスに格納されている time_t
型のデータを struct tm
型のデータに変換する関数になります。
ただし、localtime
ではこの変換時に「現地時間への変換」も実施されることになります。time
関数から取得される値は UTC における時刻なので、日本の時刻とは時差があります。その時差を考慮して現地時間に変換してくれるのが localtime
関数となります(gmtime
では時差を考慮せずそのまま変換される)。
time_t timer;
struct tm *local_time;
timer = time(NULL);
local_time = localtime(&timer);
printf("%s:%d年%d月%d日\n", local_time->tm_zone, local_time->tm_year + 1900, local_time->tm_mon + 1, local_time->tm_mday);
日本で localtime
関数を実行すれば日本時間に変換された結果が格納された struct tm
型のデータが取得することができますが、他の地域で localtime
関数を実行した場合は、その地域の時間に合わせた結果が格納された struct tm
型のデータが取得されることになると思います(おそらく意図的にタイムゾーンの情報を変更すれば、そのタイムゾーンに合わせた時刻が表示されるようになるはずです)。
時刻を文字列で取得する(ctime
関数・strftime
関数等)
ここまで時刻の情報を time_t
型のデータや struct tm
型のデータとして取得する方法について説明しました。
次は時刻を「文字列」で取得する関数について解説していきます。ただし、これらの関数は直接時刻を文字列で取得するものではなく、time_t
型のデータや struct tm
型のデータを文字列に変換する関数となります。したがって、事前に time_t
型のデータや struct tm
型のデータを取得しておく必要があります。
スポンサーリンク
ctime
関数
ctime
関数は time_t
型のデータを文字列に変換する関数になります。引数には time_t
型のデータのアドレスを指定します。
char *ctime(const time_t *timeptr);
返却値は変換後の文字列における「先頭の文字」のアドレスとなります。
引数に指定されたアドレスの time_t
型のデータを変換する関数ですので、事前に time
関数等で time_t
型のデータを取得しておく必要があります。
また、取得できる文字列は下記のようなフォーマットのものになります(曜日と月は英語かつ短縮版の文字列となります)。取得される文字列は「現地時間に変換された時刻に基づくもの」である点と、「改行文字が含まれる」点に注意が必要になります。
曜日 月 日 時:分:秒 年\n
例えば、下記のような処理を実行した場合、
time_t timer;
char *timer_str;
timer = time(NULL);
timer_str = ctime(&timer);
printf("%s",timer_str);
下記のような出力結果が得られることになります(もちろん実行するタイミングによって表示される時刻は異なります)。
Wed Jan 11 06:40:18 2023
asctime
関数
asctime
関数は struct tm
型のデータを文字列に変換する関数になります。引数には struct_tm
型のデータのアドレスを指定します。
char *ctime(const struct tm *timeptr);
ctime
関数同様に、返却値は変換後の文字列における「先頭の文字」のアドレスとなります。
引数に指定されたアドレスの struct tm
型のデータを変換する関数ですので、事前に gmtime
関数や localime
等で struct tm
型のデータを取得しておく必要があります。
例えば、下記のような処理を実行した場合、
time_t timer;
struct tm *local_time;
char *time_str;
timer = time(NULL);
local_time = localtime(&timer);
time_str = asctime(local_time);
printf("%s",time_str);
下記のような出力結果が得られることになります(もちろん実行するタイミングによって表示される時刻は異なります)。
Wed Jan 11 06:40:18 2023
要は、ctime
関数を利用した時と同じフォーマットの文字列が取得されることになります。
strftime
関数
ここまで時刻を文字列として取得するための関数として ctime
と asctime
を紹介しました。
前述の通り、これらの関数では、取得できる文字列のフォーマットは固定となります。
Wed Jan 11 06:40:18 2023
異なるフォーマットの文字列を取得したい場合に便利なのが、ここで紹介する strftime
関数となります。
size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr);
ちょっと引数がややこしいので、引数の詳細ではなく使い方のポイントのみを説明しておきたいと思います。
まず strftime
関数を実行する前には、取得する文字列を格納するための char
型の配列を用意しておく必要があります。
そして、その配列の先頭アドレスを第1引数 s
に指定し、その配列のサイズ(単位はバイト)を第2引数 maxsize
に指定します。また、第3引数 format
は取得したい文字列のフォーマットを指定します。format
については後述で補足します。さらに、第4引数 timeptr
には struct tm
型のデータのアドレスを指定します。timeptr
には gmtime
関数や localtime
関数の返却値をそのまま指定すれば良いです。
このように strftime
関数を実行すれば、strftime
関数が第4引数 timeptr
のアドレスのデータを第3引数 format
で指定したフォーマットに合わせて文字列に変換し、さらに第1引数 s
のアドレスに変換結果の文字列を格納します。ただし、この際、文字列が格納されるのは最大で maxsize
バイト分のみのデータとなります。
したがって、指定する maxsize
や変換後の文字列の長さによっては、変換後の文字列が全て第1引数 s
のアドレスに格納されない場合があります。この場合、strftime
関数からは 0
が返却されることになります。終端のヌル文字を含めて変換後の文字列が全て第1引数 s
のアドレスに格納された場合は、0
以外の値(変換後の文字列の長さ)が返却されることになります。
つまり、strftime
関数から 0
が返却された場合、第1引数 s
のアドレスに格納されたデータはヌル文字で終端されていないことになり、文字列として扱うデータとしては不十分です。文字列を扱う関数に入力してもうまく動作しない可能性があるので注意してください。
逆に 0
以外の値が返却された場合、第1引数 s
のアドレスに格納されたデータはヌル文字で終端されており、変換後の文字列が全て第1引数 s
のアドレスに指定した配列に格納されていることになります。
ちょっと引数が複雑である strftime
関数となりますが、この関数の便利なところは第3引数 format
で指定したフォーマットに合わせて文字列に変換してくれるところになります。
例えば、第3引数 format
に "%Y/%m/%d (%a) %H:%M:%S"
を指定すれば、struct tm
型のデータの必要なメンバを利用して下記のフォーマットの文字列に変換されることになります。
年/月/日 (曜日) 時:分:秒
つまり、第3引数 format
に指定したフォーマットの %
から始まる各指定子の部分が下記のような情報に置き換えられて文字列に変換されることになります。
%Y
:年%m
:月%d
:日%a
:曜日%H
:時%M
:分%S
:秒
より具体的には、下記のような文字列に変換されることになります。
2023/01/13 (Fri) 06:20:09
もちろん、第3引数 format
への指定を変更すれば、もっと異なるフォーマットに変換することも可能です。例えば下記のように strftime
関数を実行した場合、
time_t timer;
struct tm *local_time;
char time_str[256];
size_t ret;
timer = time(NULL);
local_time = localtime(&timer);
ret = strftime(time_str, 256, "%H:%M:%S %Y/%m/%d %a", local_time);
if (ret > 0) {
printf("%s\n",time_str);
} else {
printf("strftime error\n");
}
今度は下記のような文字列に変換されることになります。
06:20:09 2023/01/13 Fri
先ほどと異なるフォーマットの文字列に変換されていることが確認できると思います。先ほどとフォーマットが異なるのは第3引数 format
への指定を "%H:%M:%S %Y/%m/%d %a"
に変更しているからです。このように strftime
関数では、第3引数 format
の指定を変更することでお好みのフォーマットの文字列に変換することが可能です。
さらに、format
の中に指定可能な%H
や %Y
といった変換指定子は数多く存在します。全種類の変換指定子は man コマンドで調べることもできますし、例えば下記のようなウェブページから確認することもできます。
https://linuxjm.osdn.jp/html/LDP_man-pages/man3/strftime.3.html
スポンサーリンク
時刻の差を求める(difftime
関数)
次は時刻の差を求める関数を紹介しておきます。
difftime
関数
difftime
関数は2つの time_t
型のデータの差分を求める関数になります。time_t
型のデータは時刻となりますので、つまりは2つの時刻の差を求める関数であり、例えば処理の開始と終了時の時刻の差分を求めることで、処理にかかる時間を求めるようなことも可能となります。
double difftime(time_t timer1, time_t timer0);
返却値が double
となっていますが、基本的には time_t
で管理される時刻の単位は秒なので、difftime
で取得できる結果も秒単位の差分となるはずです。
例えば下記のように difftime
を実行して 処理
に要する時間を算出したとしても、ミリ秒単位の時間までは取得できないので注意が必要です(difftime
関数からの返却値の小数点以下は 0
となる)。
time_t start, end;
double diff;
start = time(NULL);
処理
end = time(NULL);
diff = difftime(start, end);
printf("%f\n", diff);
単純に引き算を行うのでも良さそうでもありますが、time_t
型が整数とは異なる特殊な型である可能性もあるため difftime
を利用して差分を求める方が良いです。
ナノ秒単位の時刻を取得する(timespec_get
関数)
先程の difftime
関数の説明時にも触れたように、ここまで紹介してきた関数においては時刻は全て「秒単位」までしか扱うことができません。そのため、秒よりも小さい単位、例えばミリ秒やナノ秒単位の時間の計測などは行えないことになります。
こういった秒よりも小さい単位を扱いたい場合、冒頭でも少し触れた POSIX 環境用の関数や Windows 環境用の関数を利用する手段があります。また、C11 に対応しているコンパイラを利用しているのであれば、C言語の標準ライブラリ関数として timespec_get
関数を利用することも可能となります。
ここでは timespec_get
関数を紹介したいと思います。
timespec_get
関数は struct time ts
型のデータを取得するための関数になります。引数と返却値は下記のように定義されています(この関数も time.h
をインクルードすることで使用することが可能となります)。
int timespec_get(struct timespec *ts, int base);
timespec_get
関数は time
関数同様に、ある時点からの経過時間を取得する関数になります。time
関数の場合は協定世界時 (UTC) における 1970 年 1 月 1 日 0 時 0 分 0 秒からの経過秒数を取得することができますが、timespec
関数の場合、どの時点からの経過時間を取得するのかを引数 base
で指定することが可能です。ただし、おそらく base
に指定可能な値(timespec_get
関数が正常終了する指定値)は TIME_UTC
のみになるのではないかと思います。そして、この TIME_UTC
を引数 base
に指定した場合は協定世界時 (UTC) における 1970 年 1 月 1 日 0 時 0 分 0 秒からの経過時間を取得できることになります。
そうなると、timespec_get
関数は time
関数とほぼ同様の関数であるようにも思えますが、timespec_get
関数は経過時間としてナノ秒単位の値まで取得することが可能です。
より具体的には、この取得した経過時間は “秒以上の値” と “秒未満の値” に分けられ、第1引数で指定するアドレスに struct time ts
型のデータとして格納されます。そして、この struct time ts
型は2つのメンバを持っており、1つ目のメンバである tv_sec
には、前者の “秒以上の値” が秒単位で格納されます。2つ目のメンバである tv_nsec
には、後者の “秒未満の値” がナノ秒単位で格納されます。
例えばですが、協定世界時 (UTC) における 1970 年 1 月 1 日 0 時 0 分 0 秒からの経過時間が 100
秒 + 125
ミリ秒の場合、tv_sec
には 100
、tv_nsec
には 125000000
が格納されるイメージとなります。
timespec_get
関数の利用例は下記の通りで、この関数を利用すれば秒未満の処理時間も計測することができるようになります。
struct timespec ts1, ts0;
int ret;
double diff_s;
long diff_ns;
ret = timespec_get(&ts0, TIME_UTC);
printf("%ld.%09ld\n", ts0.tv_sec, ts0.tv_nsec);
処理
ret = timespec_get(&ts1, TIME_UTC);
printf("%ld.%09ld\n", ts1.tv_sec, ts1.tv_nsec);
diff_s = difftime(ts1.tv_sec, ts0.tv_sec);
diff_ns = ts1.tv_nsec - ts0.tv_nsec;
printf("%f\n", diff_s + (double)diff_ns / 1000000000);
スポンサーリンク
まとめ
このページでは、C言語における「時刻の扱い方」について解説しました!
C言語では、time.h
で定義される標準ライブラリ関数を利用することで時刻を扱うことが可能となります。
time
関数や localtime
関数に関しては使用する機会もあると思いますので使い方などは覚えておくと良いと思います!
また、time.h
には clock
という関数も用意されており、この関数に関しては下記ページで解説していますので、興味があれば是非読んでみてください!扱うデータが単なる経過時間ではないという点に注意が必要だと思います。