【C言語】時刻を扱う(time・localtime・strftimeなど)

C言語における時刻の扱い方の解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、C言語での「時刻の扱い方」について解説していきます!

C言語では時刻を扱うための標準ライブラリ関数が用意されており、それを利用することで時刻を扱うことが可能となります。

ということで、その時刻を扱うための標準ライブラリ関数とともに、C言語での時刻の扱い方について解説していきたいと思います!

time.h をインクルードする

まず、前述の通りC言語には時刻を扱うための標準ライブラリ関数が用意されており、この関数は time.h というヘッダーファイルに定義されています。そのため、時刻を扱う際には 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 秒からの経過秒数として扱われます。

UNIX時間の説明図

もしかしたら環境によっては 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 型
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型の説明図

後述で説明しますが、この struct tm 型のデータは time_t 型のデータを localtime 関数等で変換することにより取得することができます。

time_t 型の時刻を取得する(time 関数)

ここからは、時刻を扱うために利用する関数の紹介をしていきます。

スポンサーリンク

time 関数

最初に説明するのが time 関数で、この time 関数は実行したタイミングの UNIX 時間を取得する関数となります。

UNIX時間の説明図

返却値の型は time_t となります。

time関数
time_t time(time_t *tloc);

time 関数は “実行したタイミング” の UNIX 時間を返却する関数になります。例えば下記のように実行すれば、実行したタイミングの UNIX 時間が変数 unix_time に格納されることになります。

time関数の実行例1
time_t unix_time;
unix_time = time(NULL);

上記では引数に NULL を指定していますが、NULL 以外のアドレスを指定した場合、time 関数実行時には引数で指定したアドレスにも UNIX 時間が格納されるようになっています。つまり、次のように time 関数を実行したとしても、上記の実行例と同様の結果を得ることができます。

time関数の実行例2
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 関数の引数に指定することで、プログラム起動ごとに毎回異なる乱数列を得ることもできるようになります。

13221

struct tm 型の時刻を取得する(gmtime 関数・localtime 関数)

続いて struct tm 型の時刻のデータを取得する関数として gmtimelocaltime を紹介します。

スポンサーリンク

gmtime

gmtime は引数で指定されたアドレスに格納されている time_t 型のデータを struct tm 型のデータに変換する関数になります。

[codebox title="gmtime関数"]
struct tm *gmtime(const time_t *timeptr);

gmtime 関数の引数には time_t 型のデータへのアドレスを指定します。そして、gmtime 関数の返却値は struct tm 型のデータへのアドレスとなり、このアドレスには「引数で指定したアドレスの time_t 型のデータ」を struct tm 型に変換した結果のデータが格納されることになります。

time_t型のデータをstruct tm型のデータに変換する様子

前述の通り、time_t 型のデータは time 関数によって取得できるため、time 関数で取得したデータのアドレスを指定するのが通常の使い方になります。

また、時刻を扱う型を利用する(time_t 型と struct tm 型) でも説明したように、struct tm 型には tm_yeartm_montm_mday といったメンバが存在し、これらを利用して年・月・日等を取得するようなことも可能となります。

gmtime関数の実行例
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 では時差を考慮せずそのまま変換される)。

localtime関数の実行例
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 型のデータのアドレスを指定します。

ctime関数
char *ctime(const time_t *timeptr);

返却値は変換後の文字列における「先頭の文字」のアドレスとなります。

ctime関数の説明図

引数に指定されたアドレスの time_t 型のデータを変換する関数ですので、事前に time 関数等で time_t 型のデータを取得しておく必要があります。

また、取得できる文字列は下記のようなフォーマットのものになります(曜日と月は英語かつ短縮版の文字列となります)。取得される文字列は「現地時間に変換された時刻に基づくもの」である点と、「改行文字が含まれる」点に注意が必要になります。

曜日 月 日 時:分:秒 年\n

例えば、下記のような処理を実行した場合、

ctime関数の実行例
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 型のデータのアドレスを指定します。

asctime関数
char *ctime(const struct tm *timeptr);

ctime 関数同様に、返却値は変換後の文字列における「先頭の文字」のアドレスとなります。

ctime関数の説明図

引数に指定されたアドレスの struct tm 型のデータを変換する関数ですので、事前に gmtime 関数や localime 等で struct tm 型のデータを取得しておく必要があります。

例えば、下記のような処理を実行した場合、

asctime関数の実行例
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 関数

ここまで時刻を文字列として取得するための関数として ctimeasctime を紹介しました。

前述の通り、これらの関数では、取得できる文字列のフォーマットは固定となります。

Wed Jan 11 06:40:18 2023
 

異なるフォーマットの文字列を取得したい場合に便利なのが、ここで紹介する strftime 関数となります。

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 関数を実行すれば、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関数の返却値の説明図

ちょっと引数が複雑である 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 関数を実行した場合、

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つの時刻の差を求める関数であり、例えば処理の開始と終了時の時刻の差分を求めることで、処理にかかる時間を求めるようなことも可能となります。

difftime関数
double difftime(time_t timer1, time_t timer0);

返却値が double となっていますが、基本的には time_t で管理される時刻の単位は秒なので、difftime で取得できる結果も秒単位の差分となるはずです。

例えば下記のように difftime を実行して 処理 に要する時間を算出したとしても、ミリ秒単位の時間までは取得できないので注意が必要です(difftime 関数からの返却値の小数点以下は 0 となる)。

difftime関数の実行例
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 をインクルードすることで使用することが可能となります)。

timespec_get関数
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 には 100tv_nsec には 125000000 が格納されるイメージとなります。

timespec_get 関数の利用例は下記の通りで、この関数を利用すれば秒未満の処理時間も計測することができるようになります。

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 という関数も用意されており、この関数に関しては下記ページで解説していますので、興味があれば是非読んでみてください!扱うデータが単なる経過時間ではないという点に注意が必要だと思います。

time関数とclock関数の違い解説ページのアイキャッチ 【C言語】時間計測に用いる time 関数と clock 関数の違いは?

同じカテゴリのページ一覧を表示