C言語で日付や時刻を扱うとき、標準ライブラリのstruct tm構造体を理解しておくと、年月日や時分秒の操作がとても扱いやすくなります。
本記事では、C言語初心者の方に向けて、年・月・日・時・分・秒をどのように保存し、どのように使うのかを、標準関数とサンプルコードを交えながら丁寧に解説します。
日付入力や現在時刻の表示など、実用的な例も取り上げます。
struct tm構造体とは
struct tmとは
C言語の標準ライブラリでは、日付や時刻を部品ごとに分解して扱うための構造体としてstruct tmが定義されています。
これは<time.h>ヘッダで宣言されており、主に次のような目的で使われます。
- 現在時刻を「年・月・日・時・分・秒」に分けて扱う
- 日付や時刻をユーザーが入力し、それを標準の時刻型
time_tへ変換する - 時刻を見やすい文字列に変換する
代表的な宣言は次のような形です(環境によってメンバが増えることがあります)。
struct tm {
int tm_sec; /* 秒 (0〜60) うるう秒を考慮して60まで */
int tm_min; /* 分 (0〜59) */
int tm_hour; /* 時 (0〜23) */
int tm_mday; /* 日 (1〜31) */
int tm_mon; /* 月 (0〜11) 0が1月、11が12月 */
int tm_year; /* 年 (1900年からの経過年数) 例: 2025年なら125 */
int tm_wday; /* 曜日 (0〜6) 日曜が0 */
int tm_yday; /* 年内の通算日 (0〜365) 0が1月1日 */
int tm_isdst; /* 夏時間フラグ (DST) 正なら夏時間、0なら通常、負なら不明 */
};
ここで重要なのは、カレンダーで見慣れた年・月・日と、C言語内部の表現が少し違うことです。
特にtm_yearとtm_monに注意が必要です。
年月日と時刻(時分秒)をどう持っているかの全体像
struct tmは「カレンダー形式」の時刻を表します。
つまり、人が理解しやすい次のような値を保持します。
- 年(cst-code>tm_year)
- 月(cst-code>tm_mon)
- 日(cst-code>tm_mday)
- 時(cst-code>tm_hour)
- 分(cst-code>tm_min)
- 秒(cst-code>tm_sec)
ただし、そのまま西暦の年や1〜12の月ではなく、少しずらした形で保存されています。
次の表で全体像をまとめます。
| メンバ | 意味 | 値の範囲と解釈のポイント |
|---|---|---|
| tm_year | 年 | 西暦年 - 1900 (例: 2025年 → 125) |
| tm_mon | 月 | 0〜11 (0が1月、11が12月) |
| tm_mday | 日 | 1〜31 |
| tm_hour | 時 | 0〜23 |
| tm_min | 分 | 0〜59 |
| tm_sec | 秒 | 0〜60(うるう秒考慮) |
このため、struct tmを使うときには、値を「補正」して扱う場面がよく出てきます。
たとえばtm_yearに2025を直接代入してはいけないなどです。
これらは後ほど詳しく解説します。
struct tmの各メンバ
tm_year(年)の意味と使い方
tm_yearは「1900年からの経過年数」
tm_yearは「西暦そのもの」ではなく「1900年から何年経ったか」を表す整数です。
たとえば次のようになります。
- 1900年 →
tm_year = 0 - 2000年 →
tm_year = 100 - 2025年 →
tm_year = 125
そのため、tm_yearに西暦年をそのまま代入してはいけません。
必ず西暦年 - 1900に変換してから代入します。
#include <stdio.h>
#include <time.h>
int main(void) {
struct tm date;
// 2025年1月1日を設定したい場合
date.tm_year = 2025 - 1900; // 125を代入
date.tm_mon = 0; // 0が1月
date.tm_mday = 1;
printf("tm_year = %d (内部表現)\n", date.tm_year);
// 表示するときは 1900 を足して西暦に戻す
printf("西暦年としては %d 年です\n", date.tm_year + 1900);
return 0;
}
tm_year = 125 (内部表現)
西暦年としては 2025 年です
このように、代入時は「引く」、表示時は「足す」という対応関係になっています。
初心者が間違えやすいポイント
C言語初心者の方は、つい次のように書いてしまいがちです。
date.tm_year = 2025; /* 間違い */
この場合、内部的には「西暦3925年」として解釈されてしまう可能性があります。
正しくは次のようにします。
date.tm_year = 2025 - 1900; /* 正しい */
西暦との変換は常に+1900または-1900を意識することが大切です。
tm_mon(月)の意味と使い方
tm_monは0から始まる月
tm_monは「0が1月」で「11が12月」という0始まりの月番号です。
具体的には次のようになります。
- 1月 →
tm_mon = 0 - 2月 →
tm_mon = 1 - …
- 12月 →
tm_mon = 11
そのため、ユーザーが「1〜12の月」を入力した場合には、-1してから代入します。
#include <stdio.h>
#include <time.h>
int main(void) {
struct tm date;
int year, month, day;
printf("年 月 日 を入力してください (例: 2025 1 15): ");
if (scanf("%d %d %d", &year, &month, &day) != 3) {
printf("入力エラーです\n");
return 1;
}
date.tm_year = year - 1900; // 西暦から内部表現へ
date.tm_mon = month - 1; // 1〜12 → 0〜11 へ変換
date.tm_mday = day;
printf("内部表現: tm_year=%d, tm_mon=%d, tm_mday=%d\n",
date.tm_year, date.tm_mon, date.tm_mday);
printf("表示用 : %d年 %d月 %d日\n",
date.tm_year + 1900, date.tm_mon + 1, date.tm_mday);
return 0;
}
年 月 日 を入力してください (例: 2025 1 15): 2025 1 15
内部表現: tm_year=125, tm_mon=0, tm_mday=15
表示用 : 2025年 1月 15日
月を扱うときの注意
tm_monは0〜11なので、12を入れてしまうと構造体としては「13月」を表すことになります。
ただし、mktime関数を使うと、ある程度自動的に補正してくれる場合があります(後述)。
それでも、基本的には0〜11の範囲に収まるように自分で管理するほうが安全です。
tm_mday(日)の意味と使い方
tm_mdayは1〜31の日付
tm_mdayは月内の日付で、1〜31の範囲をとります。
ここは人間の感覚と同じなので、特別なずらしはありません。
struct tm date;
date.tm_mday = 15; // 15日
ただし、実際に有効かどうか(例えば2月30日は存在しない)は、struct tmの段階ではチェックされません。
有効な日付かどうかはmktimeなどを通すことで正規化されます。
#include <stdio.h>
#include <time.h>
int main(void) {
struct tm date = {0};
date.tm_year = 2025 - 1900;
date.tm_mon = 1; // 2月
date.tm_mday = 30; // 2月30日(本来は存在しない)
// mktimeで正規化
time_t t = mktime(&date);
if (t == (time_t)-1) {
printf("時刻の変換に失敗しました\n");
return 1;
}
printf("正規化後の日付: %d年 %d月 %d日\n",
date.tm_year + 1900, date.tm_mon + 1, date.tm_mday);
return 0;
}
正規化後の日付: 2025年 3月 2日
このように不正な日付は「翌月の日付」などに補正されることがあります。
初心者のうちは、なるべく有効な日付を自分でチェックするようにすると安全です。
tm_hour(時)の意味と使い方
tm_hourは24時間制の0〜23
tm_hourは24時間表記の「時」を0〜23で表現します。
- 0 → 0時(午前0時)
- 12 → 12時(正午)
- 23 → 23時(午後11時)
struct tm timeinfo;
timeinfo.tm_hour = 14; // 14時(午後2時)
tm_hourもmktimeを使うことで、24を超えた値が補正されることがありますが、基本的には0〜23に収めると考えてください。
tm_min(分)の意味と使い方
tm_minは0〜59の分
tm_minは分を0〜59で表します。
struct tm timeinfo;
timeinfo.tm_min = 30; // 30分
60以上の値を入れた場合もmktimeで補正されますが、やはり0〜59に収めるのが原則です。
tm_sec(秒)の意味と使い方
tm_secは0〜60(うるう秒を含む)
tm_secは秒を0〜60で表します。
通常の秒は0〜59ですが、うるう秒により60になる瞬間もあるため、理論上の範囲として60が許可されています。
struct tm timeinfo;
timeinfo.tm_sec = 45; // 45秒
多くのアプリケーションでは0〜59として扱って問題ないことがほとんどです。
うるう秒を厳密に扱う必要がない場合は、0 <= tm_sec && tm_sec <= 59であると考えてよいでしょう。
struct tmと標準ライブラリ関数の使い方
time_tとstruct tmの変換
C言語では、内部的な時刻の表現としてtime_tが使われます。
これは「ある基準日時からの秒数」を表す整数型です。
time_t→ カレンダー形式(struct tm) への変換- カレンダー形式(
struct tm) →time_tへの変換
の両方を行うことができます。
time_t → struct tm
標準ライブラリには、time_tからstruct tmへ変換する関数が用意されています。
localtime: ローカル時間(タイムゾーンを考慮)gmtime: 世界協定時刻(UTC)
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL); // 現在時刻をtime_tで取得
struct tm *lt = localtime(&now); // ローカル時刻に変換
if (lt == NULL) {
printf("localtimeに失敗しました\n");
return 1;
}
printf("現在時刻(ローカル): %d年 %d月 %d日 %d:%d:%d\n",
lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
lt->tm_hour, lt->tm_min, lt->tm_sec);
return 0;
}
現在時刻(ローカル): 2025年 11月 22日 14:03:10
localtimeはstaticな領域の点にも注意が必要です。struct tmへのポインタを返す
複数回呼び出すと、前の結果が上書きされます。
struct tm → time_t
逆に、カレンダー形式のstruct tmからtime_tへ変換したい場合はmktimeを使います。
#include <stdio.h>
#include <time.h>
int main(void) {
struct tm date = {0};
// 2025年1月1日 0:00:00 を設定
date.tm_year = 2025 - 1900;
date.tm_mon = 0; // 1月
date.tm_mday = 1;
date.tm_hour = 0;
date.tm_min = 0;
date.tm_sec = 0;
time_t t = mktime(&date); // struct tm → time_t
if (t == (time_t)-1) {
printf("mktimeに失敗しました\n");
return 1;
}
printf("time_tの値: %ld\n", (long)t);
printf("曜日(0=日): %d\n", date.tm_wday); // mktime後、曜日なども設定される
return 0;
}
time_tの値: 1735689600
曜日(0=日): 3
mktimeを呼び出すと、tm_wdayやtm_yday、tm_isdstなども自動的に埋められるので、曜日などを知りたいときにも便利です。
現在時刻をstruct tmで取得する
現在時刻を取得して、人間に分かりやすい形式(年・月・日・時・分・秒)に分解したい場合は、time → localtimeの順に使います。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now;
struct tm *lt;
// 現在時刻を取得
now = time(NULL);
if (now == (time_t)-1) {
printf("timeに失敗しました\n");
return 1;
}
// 構造体に分解
lt = localtime(&now);
if (lt == NULL) {
printf("localtimeに失敗しました\n");
return 1;
}
printf("現在時刻: %d年 %d月 %d日 %02d:%02d:%02d\n",
lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
lt->tm_hour, lt->tm_min, lt->tm_sec);
return 0;
}
現在時刻: 2025年 11月 22日 14:03:10
このコードではtime(NULL)で現在時刻をtime_tとして取得し、それをlocaltimeでstruct tmに変換しています。
初心者の方はまずこのパターンを覚えるとよいです。
struct tmを文字列に変換する
struct tmを「YYYY-MM-DD hh:mm:ss」といった文字列にしたい場合は、strftimeという関数が便利です。
strftimeの基本
strftimeは、struct tmの内容を、書式指定に従って文字列に変換する関数です。
代表的な変換指定子として、次のようなものがあります。
| 指定子 | 意味 | 例 |
|---|---|---|
| %Y | 西暦(4桁) | 2025 |
| %m | 月(2桁・01〜12) | 01 |
| %d | 日(2桁・01〜31) | 09 |
| %H | 時(24時間制・00〜23) | 14 |
| %M | 分(00〜59) | 07 |
| %S | 秒(00〜60) | 05 |
使用例は次の通りです。
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *lt = localtime(&now);
char buf[64];
if (lt == NULL) {
printf("localtimeに失敗しました\n");
return 1;
}
// "YYYY-MM-DD hh:mm:ss" 形式に整形
if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", lt) == 0) {
printf("strftimeに失敗しました\n");
return 1;
}
printf("現在時刻(文字列): %s\n", buf);
return 0;
}
現在時刻(文字列): 2025-11-22 14:03:10
strftimeを使うことで、自分でprintfの書式を組み立てるより安全かつ柔軟に時刻をフォーマットできます。
C言語初心者向けstruct tmの実用例
struct tmを使った日付と時刻の入力・表示サンプル
ここでは、ユーザーから年・月・日・時・分・秒を入力してもらい、それをstruct tmに格納し、再度見やすく表示するサンプルを示します。
#include <stdio.h>
#include <time.h>
int main(void) {
struct tm dt = {0};
int year, month, day, hour, min, sec;
printf("年 月 日 時 分 秒 を空白区切りで入力してください\n");
printf("例: 2025 11 22 14 30 00\n> ");
// 6つの整数を読み取る
if (scanf("%d %d %d %d %d %d",
&year, &month, &day, &hour, &min, &sec) != 6) {
printf("入力形式が正しくありません\n");
return 1;
}
// struct tm に変換して格納
dt.tm_year = year - 1900; // 西暦年 → tm_year
dt.tm_mon = month - 1; // 1〜12 → 0〜11
dt.tm_mday = day;
dt.tm_hour = hour;
dt.tm_min = min;
dt.tm_sec = sec;
// 表示(補正なしのそのままの値)
printf("内部表現から再計算した日時は:\n");
printf("%d年 %d月 %d日 %02d:%02d:%02d\n",
dt.tm_year + 1900, dt.tm_mon + 1, dt.tm_mday,
dt.tm_hour, dt.tm_min, dt.tm_sec);
return 0;
}
年 月 日 時 分 秒 を空白区切りで入力してください
例: 2025 11 22 14 30 00
> 2025 11 22 14 30 00
内部表現から再計算した日時は:
2025年 11月 22日 14:30:00
このプログラムでは「入力 → struct tm → 表示」という基本的な流れを体験できます。
年月日からtime_tを計算する実装例
次に、ユーザーが入力した年月日をtime_tに変換し、その秒数や曜日を表示する例を示します。
#include <stdio.h>
#include <time.h>
int main(void) {
struct tm dt = {0};
int year, month, day;
time_t t;
const char *wday_names[] = {"日", "月", "火", "水", "木", "金", "土"};
printf("年 月 日 を入力してください (例: 2025 1 1)\n> ");
if (scanf("%d %d %d", &year, &month, &day) != 3) {
printf("入力エラーです\n");
return 1;
}
// struct tmにセット
dt.tm_year = year - 1900;
dt.tm_mon = month - 1;
dt.tm_mday = day;
dt.tm_hour = 0;
dt.tm_min = 0;
dt.tm_sec = 0;
// time_tに変換
t = mktime(&dt);
if (t == (time_t)-1) {
printf("mktimeに失敗しました\n");
return 1;
}
printf("基準時刻からの秒数(time_t) = %ld\n", (long)t);
printf("曜日は %s曜日 です\n", wday_names[dt.tm_wday]);
return 0;
}
年 月 日 を入力してください (例: 2025 1 1)
> 2025 1 1
基準時刻からの秒数(time_t) = 1735689600
曜日は 水曜日 です
このようにmktimeを通すことで、曜日なども簡単に求められることがわかります。
struct tmを使うときの注意点
struct tmは便利ですが、初心者がつまずきやすい注意点もいくつかあります。
年と月のずれ(1900年・0始まり)を忘れない
最も重要なのは次の2点です。
- tm_year = 西暦年 – 1900
- tm_mon = 月(1〜12) – 1
表示するときには逆に+1900、+1を忘れずに行います。
この変換を意識しないと、まったく違う年月日になってしまいます。
localtimeの戻り値の扱い
localtimeは内部のstaticな領域へのポインタを返すため、次のような危険があります。
- 複数回呼び出した結果を同時に保持できない
- 別スレッドから呼び出すと競合する可能性がある(マルチスレッド環境)
初心者向けには「localtimeの結果は、必要なら自分でstruct tm変数にコピーしておく」と覚えておくとよいです。
struct tm local = *localtime(&now); // 内容をコピー
mktimeでの正規化に頼りすぎない
mktimeは、例えば「13月」や「2月30日」のような値を、それなりに「正しい日付」へ補正してくれます。
しかし、それに頼りすぎると意図しない日付になっても気づきにくくなります。
可能な範囲で、年・月・日・時・分・秒の妥当性は自分でチェックし、そのうえで<mktime>で正規化する、という使い方をおすすめします。
まとめ
この記事では、C言語のstruct tm構造体が、年・月・日・時・分・秒をどのように表現しているかを詳しく解説しました。
中でもtm_yearは「西暦−1900」、tm_monは0始まりという点が最重要ポイントです。
現在時刻の取得にはtimeとlocaltime、任意の年月日からtime_tを得るにはmktime、文字列化にはstrftimeを組み合わせます。
サンプルを動かしながら、入力→struct tm→time_t→表示という一連の流れに慣れていってください。
