閉じる

【C言語】struct tm 構造体についてわかりやすく解説 〜年・月・日・時・分・秒を扱ってみよう〜

C言語で日付や時刻を扱うとき、標準ライブラリのstruct tm構造体を理解しておくと、年月日や時分秒の操作がとても扱いやすくなります。

本記事では、C言語初心者の方に向けて、年・月・日・時・分・秒をどのように保存し、どのように使うのかを、標準関数とサンプルコードを交えながら丁寧に解説します。

日付入力や現在時刻の表示など、実用的な例も取り上げます。

struct tm構造体とは

struct tmとは

C言語の標準ライブラリでは、日付や時刻を部品ごとに分解して扱うための構造体としてstruct tmが定義されています。

これは<time.h>ヘッダで宣言されており、主に次のような目的で使われます。

  • 現在時刻を「年・月・日・時・分・秒」に分けて扱う
  • 日付や時刻をユーザーが入力し、それを標準の時刻型time_tへ変換する
  • 時刻を見やすい文字列に変換する

代表的な宣言は次のような形です(環境によってメンバが増えることがあります)。

C言語
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_yeartm_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_mon0〜11 (0が1月、11が12月)
tm_mday1〜31
tm_hour0〜23
tm_min0〜59
tm_sec0〜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に変換してから代入します。

C言語
#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言語初心者の方は、つい次のように書いてしまいがちです。

C言語
date.tm_year = 2025;  /* 間違い */

この場合、内部的には「西暦3925年」として解釈されてしまう可能性があります。

正しくは次のようにします。

C言語
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してから代入します。

C言語
#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の範囲をとります。

ここは人間の感覚と同じなので、特別なずらしはありません。

C言語
struct tm date;
date.tm_mday = 15;  // 15日

ただし、実際に有効かどうか(例えば2月30日は存在しない)は、struct tmの段階ではチェックされません。

有効な日付かどうかはmktimeなどを通すことで正規化されます。

C言語
#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時)
C言語
struct tm timeinfo;
timeinfo.tm_hour = 14;  // 14時(午後2時)

tm_hourmktimeを使うことで、24を超えた値が補正されることがありますが、基本的には0〜23に収めると考えてください。

tm_min(分)の意味と使い方

tm_minは0〜59の分

tm_minは分を0〜59で表します

C言語
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が許可されています。

C言語
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)
C言語
#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を使います。

C言語
#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の順に使います。

C言語
#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として取得し、それをlocaltimestruct 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

使用例は次の通りです。

C言語
#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に格納し、再度見やすく表示するサンプルを示します。

C言語
#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に変換し、その秒数や曜日を表示する例を示します。

C言語
#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変数にコピーしておく」と覚えておくとよいです。

C言語
struct tm local = *localtime(&now);  // 内容をコピー

mktimeでの正規化に頼りすぎない

mktimeは、例えば「13月」や「2月30日」のような値を、それなりに「正しい日付」へ補正してくれます。

しかし、それに頼りすぎると意図しない日付になっても気づきにくくなります

可能な範囲で、年・月・日・時・分・秒の妥当性は自分でチェックし、そのうえで<mktime>で正規化する、という使い方をおすすめします。

まとめ

この記事では、C言語のstruct tm構造体が、年・月・日・時・分・秒をどのように表現しているかを詳しく解説しました。

中でもtm_yearは「西暦−1900」、tm_monは0始まりという点が最重要ポイントです。

現在時刻の取得にはtimelocaltime、任意の年月日からtime_tを得るにはmktime、文字列化にはstrftimeを組み合わせます。

サンプルを動かしながら、入力→struct tm→time_t→表示という一連の流れに慣れていってください。

数学・数値処理

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!