C言語で スケジュール管理アプリ(ミニ)を作ろう!第5回 ~祝日・休日データとの連携~

スポンサーリンク
C言語

はじめに

前回のVer.4では、登録した予定を日付順に並び替えられるようにした。
今回のVer.5では、予定と祝日・休日を連携させ、祝日・休日と重なる予定が一目で分かるようにする。

祝日・休日データは、以前、本ブログでご紹介した「マルチ言語(C言語、Go言語、Rust、Kotlin)で祝日対応のカレンダーをつくろう!」企画(multi-pg-lang-calendar)で使っているものと同じ形式をそのまま使う。内閣府の祝日CSV(内閣府の「国民の祝日・休日」のCSV)をUTF-8変換した holidays.csv を、このアプリのフォルダにコピーするだけで連携できる。

マルチ言語(C言語、Go言語、Rust、Kotlin)で祝日対応のカレンダーをつくろう!(第1日目)

対応するGitHubリポジトリは以下の通りである。

Rocky-Seven/c-language-studies 内の schedule-mini-app/ver5_holiday_link/

Ver.4からの変更点

  • 起動時に holidays.csv を読み込む。ファイルが無い場合はエラーにせず、祝日連携なしで続行する。
  • 予定の追加・編集時、入力した日付が祝日・休日と一致する場合、参考情報として祝日名を表示する(入力を止めるものではない)。
  • 一覧表示で、祝日・休日と重なる予定には祝日・休日名を添えて表示する。

祝日データの構造体

祝日は「年・月・日・名称」を持つ、Schedule とよく似た構造体として表現する。

/* 1件の祝日を表す構造体。祝日カレンダー企画(multi-pg-lang-calendar)と同じ形式。 */
typedef struct {
    int year;
    int month;
    int day;
    char name[MAX_HOLIDAY_NAME];
} Holiday;

Holiday holidays[MAX_HOLIDAYS];
int holiday_count = 0;

holidays.csvの読み込み

内閣府の祝日CSVは、1行目がヘッダー行(項目名)で、2行目以降が 年/月/日,祝日名 という形式になっている。multi-pg-lang-calendar のC言語版と同じ考え方で、カンマの位置をstrchrで探し、その前後を日付・名称として読み取る。

/*
 * 祝日データ(holidays.csv)を読み込む。
 * 「マルチ言語で祝日対応のカレンダーをつくろう!」企画(multi-pg-lang-calendar)で
 * 使っているものと同じ形式(内閣府CSV由来、1行目はヘッダー)を想定している。
 * ファイルが無い場合は、祝日連携なしで続行する(エラー扱いにはしない)。
 */
void load_holidays(void) {
    FILE *fp = fopen(HOLIDAY_FILE, "r");
    if (fp == NULL) {
        printf("祝日データ(%s)が見つかりませんでした。祝日連携なしで続行します。\n", HOLIDAY_FILE);
        return;
    }

    char line[MAX_HOLIDAY_LINE];

    /* 1行目はヘッダー行(項目名)のため読み飛ばす。 */
    fgets(line, sizeof(line), fp);

    while (fgets(line, sizeof(line), fp) != NULL && holiday_count < MAX_HOLIDAYS) {
        line[strcspn(line, "\n")] = '\0';
        line[strcspn(line, "\r")] = '\0';

        char *comma = strchr(line, ',');
        if (comma == NULL) {
            continue;
        }

        *comma = '\0';
        char *date_part = line;
        char *name_part = comma + 1;

        int year, month, day;

        /* 内閣府CSVは"年/月/日"形式。念のため"年-月-日"にも対応しておく。 */
        int result = sscanf(date_part, "%d/%d/%d", &year, &month, &day);
        if (result != 3) {
            result = sscanf(date_part, "%d-%d-%d", &year, &month, &day);
        }

        if (result != 3 || month < 1 || month > 12 || day < 1 || day > 31) {
            continue;
        }

        Holiday *h = &holidays[holiday_count];
        h->year = year;
        h->month = month;
        h->day = day;
        strncpy(h->name, name_part, MAX_HOLIDAY_NAME - 1);
        h->name[MAX_HOLIDAY_NAME - 1] = '\0';
        holiday_count++;
    }

    fclose(fp);
    printf("祝日データを読み込みました: %d件\n", holiday_count);
}

*comma = '\0'; で、読み込んだ1行の文字列をカンマの位置で2つに分断している。こうすると line の先頭からは日付部分だけの文字列として、comma + 1 からは名称部分だけの文字列として、それぞれ扱えるようになる。

日付部分は 年/月/日 形式を基本としつつ、sscanf が失敗した場合は 年-月-日 形式も試す。これは schedules.txt の読み込みと同様、想定外の行が混ざっていても、そこだけを読み飛ばして処理を続けられるようにするためである。

祝日の検索

ある年月日が祝日かどうかを調べる関数を用意する。

/*
 * 指定した年月日が祝日であれば、その名称を返す。祝日でなければNULLを返す。
 */
const char *find_holiday_name(int year, int month, int day) {
    for (int i = 0; i < holiday_count; i++) {
        if (holidays[i].year == year && holidays[i].month == month && holidays[i].day == day) {
            return holidays[i].name;
        }
    }
    return NULL;
}

祝日の件数は多くても1年あたり20件前後、holidays.csv 全体でも1000件程度なので、先頭から順に探す単純な線形探索で十分な速度が出る。

予定の追加・編集時に祝日を知らせる

read_schedule_input の中で、日付の妥当性チェックが通った直後に find_holiday_name を呼び出し、該当すれば参考情報として表示する。

    /* 祝日と重なる場合は、参考情報として知らせる(入力を止めるものではない)。 */
    const char *holiday_name = find_holiday_name(s->year, s->month, s->day);
    if (holiday_name != NULL) {
        printf("この日は祝日です(%s)。\n", holiday_name);
    }

祝日だからといって予定を登録できなくする理由は無いため、あくまで「気づき」を与えるだけの表示にとどめている。

一覧表示に祝日名を添える

list_schedules でも同じ find_holiday_name を使い、祝日と重なる予定には [祝: 祝日名] を追加で表示する。

/* 祝日と重なる予定には、一覧表示の末尾に祝日名を添える。 */
void list_schedules(void) {
    if (schedule_count == 0) {
        printf("登録されている予定はありません。\n");
        return;
    }

    printf("\n--- 予定一覧 ---\n");
    for (int i = 0; i < schedule_count; i++) {
        Schedule *s = &schedules[i];
        const char *holiday_name = find_holiday_name(s->year, s->month, s->day);

        if (holiday_name != NULL) {
            printf("%d: %04d-%02d-%02d  %s  [祝: %s]\n", i + 1, s->year, s->month, s->day, s->title, holiday_name);
        } else {
            printf("%d: %04d-%02d-%02d  %s\n", i + 1, s->year, s->month, s->day, s->title);
        }
    }
}

サンプルのholidays.csv

このリポジトリには、動作確認用として、日付が年によって変わらない祝日(元日、建国記念の日、天皇誕生日、昭和の日、憲法記念日、みどりの日、こどもの日、山の日、文化の日、勤労感謝の日)だけを集めた小さなサンプル holidays.csv を同梱している。

国民の祝日・休日月日,国民の祝日・休日名称
2026/1/1,元日
2026/2/11,建国記念の日
2026/2/23,天皇誕生日
2026/4/29,昭和の日
2026/5/3,憲法記念日
2026/5/4,みどりの日
2026/5/5,こどもの日
2026/8/11,山の日
2026/11/3,文化の日
2026/11/23,勤労感謝の日

成人の日や海の日のように年によって日付が変わるもの、春分の日・秋分の日のように年によって日付が変わるものは、サンプルには含めていない。実際に使う場合は、multi-pg-lang-calendar 側で用意した内閣府CSV(UTF-8変換済み)をこのフォルダにコピーすることで、全ての祝日と連携できる。

動作確認

holidays.csv を用意した状態で、憲法記念日(5/3)に予定を追加する。

番号を入力してください: 1
年月日を入力してください(例: 2026 8 1 または 20260801): 2026 5 3
この日は祝日です(憲法記念日)。
予定を入力してください: 家族で外出
予定を追加しました。

一覧表示すると、祝日名が添えられて表示される。

--- 予定一覧 ---
1: 2026-05-03  家族で外出  [祝: 憲法記念日]

holidays.csv が無い状態で起動すると、次のように表示され、祝日連携なし(Ver.4相当)の動作になる。

祝日データ(holidays.csv)が見つかりませんでした。祝日連携なしで続行します。

次回予告

ここまででC言語版のVer.1〜Ver.5が完成し、追加・一覧表示・編集・削除・保存/読み込み・日付順ソート・祝日連携という一通りの機能が揃った。
次回以降は、Go・Kotlin版への移植を予定している。

コメント

タイトルとURLをコピーしました