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

スポンサーリンク
Go

今年も残り少なくなってきた。そろそろ来年のカレンダーの準備をする時期ではないだろうか。

本ブログでもこれまでプログラミング学習の一環として、カレンダー作りについてご紹介している。

日付を表示することは比較的容易である。ただし、これではカレンダーとしては不十分である。
やはり、祝日を表示できれば実用性がかなり高まる。

そこで、内閣府の祝日CSVをダウンロードして、プログラムと同じフォルダに保存する方法で祝日対応カレンダーを作っていこう。

内閣府の祝日CSV
https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv

  • 昭和30年以降の全祝日データ
  • 政府公式なので信頼性が高い
  • プログラムから直接ダウンロード可能

メリット: 公式データ、自動更新可能
デメリット: ネットワーク接続が必要

C言語だけでなく、GO言語、Rust、Kotlinも含めたマルチ言語で挑戦してみよう。
なお、各言語の特徴は以下の通りである。

項目CGoRustKotlin
パラダイム手続き型、低レベル手続き的/並行志向マルチパラダイム(手続き・関数・SYSTEM)オブジェクト指向+関数型要素
メモリ管理手動(malloc/free)ガベージコレクション(GC)所有権/借用(所有権システム)、GC無しJVM/NativeのGC(JVMが主流)
Null安全なし(ヌル参照は危険)nil がある(型により扱い)Option 型で明示的(ヌルなし)言語レベルで null 安全(? 型)
例外(例外処理)なし(戻り値で扱うのが一般)例外はあるが慣例はエラー戻り値(error例外はあまり使わず Result/Option を多用例外あり(JVM互換)
エラー処理の慣習戻り値(error codes)明示的な error 戻り値Result<T,E>/ ? 演算子例外(try/catch)
並行処理モデルOSスレッド(pthreads等)goroutine + channel(軽量)OSスレッド + async/futures(非同期)コルーチン(軽量)、JVMスレッド
コンパイル / 実行形態AOT ネイティブ(gcc/clang)AOT ネイティブ(高速なビルド)AOT ネイティブ(最適化高い)主にJVMバイトコード(JVM上で実行)、Kotlin/Nativeあり
ビルドツール / パッケージ管理make / cmake / 外部ツール多様go ツール(go modules)cargo(標準で統合)Maven/Gradle(IDEサポート充実)
標準ライブラリ最小限(低レベル)豊富(ネットワーク・並行処理に強い)充分(システム寄りだが拡張性高い)非常に豊富(JVMエコシステム利用可能)
FFI(他言語との連携)非常に良い(C互換)cgo 経由でCと連携非常に良い(Cヘッダを直接呼べる等)Javaとの相互運用性が極めて高い
実行時パフォーマンス非常に高い(最適化次第)高速(GC影響あり)Cに匹敵〜同等(零コスト抽象)JVMのオーバーヘッド有り(JIT最適化で高速化)
デフォルトの安全性(型/メモリ)低い中〜高(ただし安全性と簡潔さのトレードオフ)高(安全が設計目標)高(null安全・型システム)
学習曲線低レベル知識必要で中〜高比較的緩やか(簡潔で学びやすい)やや高い(所有権・借用の理解)Java経験者には低め(近い思想)
典型的な用途OS、組み込み、ドライバ、クロスプラットフォーム低レイヤネットワークサーバ、クラウド、ツールシステムプログラミング、高性能アプリ、WebAssemblyAndroid、サーバー(JVM)、マルチプラットフォーム開発
長所(概観)最小ランタイム・高速・移植性高いシンプル・並行処理が書きやすい・ビルドが高速メモリ安全+高速・抽象の性能コストが低いJVMエコシステム・null安全・開発生産性高い
短所(概観)安全機構が乏しく手間がかかるジェネリクス歴が浅く抽象表現に制約あり(改善済)学習コストが高い・所有権周りが厳格ネイティブ性能はJVMに依存・大規模JVMの起動コスト

開発環境は、GitHubのCodespacesを利用する。
GitHub Codespacesで開発環境を作る方法は以下の通りである。

GitHub Codespacesで開発環境を作る方法

ステップ1:GitHubアカウントを作成(持っていない場合)

  1. https://github.com にアクセスしよう。
  2. 「Sign up」をクリックしよう。
  3. メールアドレス、パスワード、ユーザー名を入力しよう。
  4. メールで届く認証コードを入力しよう。

ステップ2:新しいリポジトリを作成

2-1. リポジトリ作成ページを開く

  1. GitHubにログインしよう。
  2. 左上の「Top repositories 」の隣の「New」ボタン をクリックしよう。

2-2. リポジトリの設定

以下のように入力しよう。

  • Repository name(リポジトリ名): Multi-PG-Lang Calendar
    • 好きな名前でOKだが、英数字とハイフンのみ使える。
    • 日本語は使えない。
  • Description(説明)
    • 空欄でOK。
  • Public / Private
    • Public: 誰でも見られる(推奨)
    • Private: 自分だけが見られる
  • 「Add a README file」は必ずチェックしよう。
  • 「Add .gitignore」と「Choose a license」は選択不要である。

2-3. リポジトリを作成

緑色の 「Create repository」 ボタンをクリックしよう。

→ リポジトリが作成される。


ステップ3:Codespacesを起動

3-1. Codespacesを開く

  1. 作成したリポジトリのページで、緑色の 「<> Code」 ボタンをクリックしよう。
  2. 「Codespaces」 タブを選択しよう。
  3. 「Create codespace on main」 をクリックしよう。

3-2. 起動を待つ

  • 初回は1〜2分かかる。
  • VSCode(コードエディタ)が開く。

スケジュール

第1日目:C言語、Go言語、Rust
第2日目:Kotlin、Git & Push

第1日目で重い処理を完了

  • Rustのビルドが一番時間がかかる
  • 3言語が完成した状態で終了

第2日目は軽め

  • Kotlinのみ追加
  • Git操作で完了

進捗が見やすい

  • 1日目終了時点で75%完成
  • 2日目で完全完成

今回は1日目のご紹介となる。
なお、作業を自動化するスクリプトを用意した。

スクリプト① C言語とGo言語セットアップ
スクリプト② Rust追加セットアップ

スクリプト① C言語とGo言語セットアップ

#!/bin/bash

# ========================================
# Multi-PG-Lang Calendar
# C言語とGo言語 完全セットアップスクリプト
# このスクリプト1つで全て完了します
# ========================================

echo "🚀 Multi-PG-Lang Calendar - C & Go Complete Setup"
echo "=================================================="
echo ""

# ========================================
# Step 1: 基準ディレクトリ設定
# ========================================

echo "📍 Step 1: 基準ディレクトリ設定"
echo "-----------------------------------"

if [ -d "/workspaces" ]; then
    BASE_DIR="/workspaces"
    echo "✅ GitHub Codespaces環境"
else
    BASE_DIR="$HOME"
    echo "✅ ローカル環境"
fi

PROJECT_DIR="$BASE_DIR/multi-pg-lang-calendar"
echo "プロジェクトディレクトリ: $PROJECT_DIR"
echo ""

# ========================================
# Step 2: プロジェクトディレクトリ作成
# ========================================

echo "📁 Step 2: プロジェクトディレクトリ作成"
echo "-----------------------------------"

if [ -d "$PROJECT_DIR" ]; then
    echo "⚠️  既存ディレクトリを削除します: $PROJECT_DIR"
    rm -rf "$PROJECT_DIR"
fi

mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
echo "✅ 作成完了: $(pwd)"
echo ""

# ========================================
# Step 3: ディレクトリ構造作成
# ========================================

echo "📁 Step 3: ディレクトリ構造作成"
echo "-----------------------------------"

mkdir -p c go data scripts docs bin

echo "✅ ディレクトリ構造:"
ls -la
echo ""

# ========================================
# Step 4: 祝日データダウンロード+UTF-8変換
# ========================================

echo "📥 Step 4: 祝日データダウンロード+UTF-8変換"
echo "-----------------------------------"

if curl -o data/holidays_sjis.csv https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv 2>/dev/null; then
    echo "✅ ダウンロード成功"
    
    # UTF-8変換
    if command -v iconv &> /dev/null; then
        iconv -f SHIFT_JIS -t UTF-8 data/holidays_sjis.csv > data/holidays.csv 2>/dev/null
        echo "✅ UTF-8変換成功 (iconv)"
    elif command -v nkf &> /dev/null; then
        nkf -w data/holidays_sjis.csv > data/holidays.csv
        echo "✅ UTF-8変換成功 (nkf)"
    else
        mv data/holidays_sjis.csv data/holidays.csv
        echo "⚠️  変換ツールなし、そのまま使用"
    fi
    
    rm -f data/holidays_sjis.csv
    
    echo "   ファイル: data/holidays.csv"
    echo "   行数: $(wc -l < data/holidays.csv)"
    echo "   サイズ: $(ls -lh data/holidays.csv | awk '{print $5}')"
else
    echo "❌ ダウンロード失敗"
fi

echo ""

# ========================================
# Step 5: C言語ソースコード作成
# ========================================

echo "📝 Step 5: C言語ソースコード作成"
echo "-----------------------------------"

cat > c/calendar.c << 'C_SOURCE_EOF'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define MAX_HOLIDAYS 1100

typedef struct {
    int year;
    int month;
    int day;
    char name[100];
} Holiday;

Holiday holidays[MAX_HOLIDAYS];
int holiday_count = 0;

const char* weekdays[] = {"日", "月", "火", "水", "木", "金", "土"};

char* trim(char* str) {
    char* end;
    while(isspace((unsigned char)*str)) str++;
    if(*str == 0) return str;
    end = str + strlen(str) - 1;
    while(end > str && isspace((unsigned char)*end)) end--;
    end[1] = '\0';
    return str;
}

int load_holidays_from_file(const char* filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        return 0;
    }

    char line[512];
    int line_num = 0;
    
    if (fgets(line, sizeof(line), fp) != NULL) {
        line_num++;
    }

    while (fgets(line, sizeof(line), fp) != NULL && holiday_count < MAX_HOLIDAYS) {
        line_num++;
        
        line[strcspn(line, "\n")] = 0;
        line[strcspn(line, "\r")] = 0;
        
        char* trimmed_line = trim(line);
        if (strlen(trimmed_line) == 0) continue;

        char date_str[50] = "";
        char name[100] = "";
        
        char *comma = strchr(trimmed_line, ',');
        if (comma == NULL) continue;
        
        int date_len = comma - trimmed_line;
        if (date_len >= sizeof(date_str) || date_len == 0) continue;
        
        strncpy(date_str, trimmed_line, date_len);
        date_str[date_len] = '\0';
        strcpy(name, comma + 1);
        
        char* clean_date = trim(date_str);
        char* clean_name = trim(name);
        
        if (strlen(clean_date) == 0 || strlen(clean_name) == 0) continue;
        
        int year = 0, month = 0, day = 0;
        int parsed = 0;
        
        if (sscanf(clean_date, "%d/%d/%d", &year, &month, &day) == 3) {
            parsed = 1;
        } else if (sscanf(clean_date, "%d-%d-%d", &year, &month, &day) == 3) {
            parsed = 1;
        }
        
        if (parsed && year >= 1900 && year <= 2100 && 
            month >= 1 && month <= 12 && day >= 1 && day <= 31) {
            
            holidays[holiday_count].year = year;
            holidays[holiday_count].month = month;
            holidays[holiday_count].day = day;
            strncpy(holidays[holiday_count].name, clean_name, sizeof(holidays[holiday_count].name) - 1);
            holidays[holiday_count].name[sizeof(holidays[holiday_count].name) - 1] = '\0';
            
            holiday_count++;
        }
    }

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

int is_holiday(int year, int month, int day, char* holiday_name) {
    for (int i = 0; i < holiday_count; i++) {
        if (holidays[i].year == year && 
            holidays[i].month == month && 
            holidays[i].day == day) {
            if (holiday_name != NULL) {
                strcpy(holiday_name, holidays[i].name);
            }
            return 1;
        }
    }
    return 0;
}

int get_days_in_month(int year, int month) {
    int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    if (month == 2) {
        if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
            return 29;
        }
    }
    return days[month - 1];
}

int get_weekday(int year, int month, int day) {
    if (month < 3) {
        year--;
        month += 12;
    }
    int h = (day + (13 * (month + 1)) / 5 + year + year / 4 - year / 100 + year / 400) % 7;
    return (h + 6) % 7;
}

void print_calendar(int year, int month) {
    printf("\n        %d年 %d月\n", year, month);
    printf("----------------------------\n");
    
    for (int i = 0; i < 7; i++) {
        printf(" %s ", weekdays[i]);
    }
    printf("\n");
    printf("----------------------------\n");
    
    int first_day = get_weekday(year, month, 1);
    int days_in_month = get_days_in_month(year, month);
    
    for (int i = 0; i < first_day; i++) {
        printf("    ");
    }
    
    int current_weekday = first_day;
    for (int day = 1; day <= days_in_month; day++) {
        int is_hol = is_holiday(year, month, day, NULL);
        
        if (is_hol) {
            printf("%3d*", day);
        } else {
            printf("%3d ", day);
        }
        
        current_weekday++;
        if (current_weekday == 7) {
            printf("\n");
            current_weekday = 0;
        }
    }
    
    if (current_weekday != 0) {
        printf("\n");
    }
    printf("----------------------------\n");
    
    printf("\n【祝日】\n");
    int found = 0;
    for (int i = 0; i < holiday_count; i++) {
        if (holidays[i].year == year && holidays[i].month == month) {
            printf("  %2d日: %s\n", holidays[i].day, holidays[i].name);
            found = 1;
        }
    }
    if (!found) {
        printf("  なし\n");
    }
    printf("\n");
}

int main() {
    int year, month;
    
    printf("=== 月間カレンダー(祝日対応版)C言語 ===\n\n");
    
    const char* filenames[] = {
        "holidays.csv",
        "../data/holidays.csv",
        "data/holidays.csv"
    };
    
    int loaded = 0;
    for (int i = 0; i < 3 && !loaded; i++) {
        loaded = load_holidays_from_file(filenames[i]);
        if (loaded) break;
    }
    
    if (!loaded) {
        printf("祝日データなしで続行します。\n");
    }
    
    printf("\n年を入力してください (例: 2025): ");
    if (scanf("%d", &year) != 1) {
        printf("入力エラー\n");
        return 1;
    }
    
    printf("月を入力してください (1-12): ");
    if (scanf("%d", &month) != 1) {
        printf("入力エラー\n");
        return 1;
    }
    
    if (month < 1 || month > 12) {
        printf("月は1から12の間で入力してください。\n");
        return 1;
    }
    
    print_calendar(year, month);
    
    return 0;
}
C_SOURCE_EOF

echo "✅ c/calendar.c 作成完了"
echo ""

# ========================================
# Step 6: C言語 Makefile 作成
# ========================================

echo "📝 Step 6: C言語 Makefile 作成"
echo "-----------------------------------"

cat > c/Makefile << 'MAKE_EOF'
CC = gcc
CFLAGS = -Wall -O2
TARGET = calendar

all: $(TARGET)

$(TARGET): calendar.c
	$(CC) $(CFLAGS) -o $(TARGET) calendar.c

clean:
	rm -f $(TARGET)

run: $(TARGET)
	./$(TARGET)
MAKE_EOF

echo "✅ c/Makefile 作成完了"
echo ""

# ========================================
# Step 7: Go言語ソースコード作成
# ========================================

echo "📝 Step 7: Go言語ソースコード作成"
echo "-----------------------------------"

cat > go/calendar.go << 'GO_SOURCE_EOF'
package main

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

type Holiday struct {
	Year  int
	Month int
	Day   int
	Name  string
}

var holidays []Holiday
var weekdays = []string{"日", "月", "火", "水", "木", "金", "土"}

const holidayURL = "https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv"
const holidayFile = "holidays.csv"

func downloadAndConvertHolidayFile() error {
	fmt.Println("内閣府から祝日データをダウンロード中...")
	
	resp, err := http.Get(holidayURL)
	if err != nil {
		return fmt.Errorf("ダウンロードエラー: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("HTTPエラー: %d", resp.StatusCode)
	}

	tmpFile := "holidays_sjis.csv"
	out, err := os.Create(tmpFile)
	if err != nil {
		return fmt.Errorf("ファイル作成エラー: %v", err)
	}

	_, err = io.Copy(out, resp.Body)
	out.Close()
	if err != nil {
		return fmt.Errorf("保存エラー: %v", err)
	}

	cmd := exec.Command("iconv", "-f", "SHIFT_JIS", "-t", "UTF-8", tmpFile)
	output, err := cmd.Output()
	if err != nil {
		os.Rename(tmpFile, holidayFile)
		fmt.Println("⚠️  UTF-8変換をスキップしました")
	} else {
		err = os.WriteFile(holidayFile, output, 0644)
		if err != nil {
			return fmt.Errorf("UTF-8ファイル作成エラー: %v", err)
		}
		os.Remove(tmpFile)
		fmt.Println("✅ UTF-8に変換しました")
	}

	fmt.Printf("祝日データを保存しました: %s\n", holidayFile)
	return nil
}

func loadHolidaysFromFile(filename string) error {
	file, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	lineNum := 0
	
	if scanner.Scan() {
		lineNum++
	}

	for scanner.Scan() {
		lineNum++
		line := strings.TrimSpace(scanner.Text())
		
		if line == "" {
			continue
		}

		parts := strings.Split(line, ",")
		if len(parts) < 2 {
			continue
		}

		dateStr := strings.TrimSpace(parts[0])
		name := strings.TrimSpace(parts[1])

		dateStr = strings.ReplaceAll(dateStr, "/", "-")
		dateParts := strings.Split(dateStr, "-")
		
		if len(dateParts) != 3 {
			continue
		}

		year, err1 := strconv.Atoi(dateParts[0])
		month, err2 := strconv.Atoi(dateParts[1])
		day, err3 := strconv.Atoi(dateParts[2])

		if err1 != nil || err2 != nil || err3 != nil {
			continue
		}

		holidays = append(holidays, Holiday{
			Year:  year,
			Month: month,
			Day:   day,
			Name:  name,
		})
	}

	return scanner.Err()
}

func isHoliday(year, month, day int) (bool, string) {
	for _, h := range holidays {
		if h.Year == year && h.Month == month && h.Day == day {
			return true, h.Name
		}
	}
	return false, ""
}

func printCalendar(year, month int) {
	fmt.Printf("\n        %d年 %d月\n", year, month)
	fmt.Println("----------------------------")

	for _, wd := range weekdays {
		fmt.Printf(" %s ", wd)
	}
	fmt.Println()
	fmt.Println("----------------------------")

	firstDay := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.Local)
	firstWeekday := int(firstDay.Weekday())
	
	lastDay := firstDay.AddDate(0, 1, -1)
	daysInMonth := lastDay.Day()

	for i := 0; i < firstWeekday; i++ {
		fmt.Print("    ")
	}

	currentWeekday := firstWeekday
	for day := 1; day <= daysInMonth; day++ {
		isHol, _ := isHoliday(year, month, day)
		
		if isHol {
			fmt.Printf("%3d*", day)
		} else {
			fmt.Printf("%3d ", day)
		}

		currentWeekday++
		if currentWeekday == 7 {
			fmt.Println()
			currentWeekday = 0
		}
	}

	if currentWeekday != 0 {
		fmt.Println()
	}
	fmt.Println("----------------------------")

	fmt.Println("\n【祝日】")
	found := false
	for _, h := range holidays {
		if h.Year == year && h.Month == month {
			fmt.Printf("  %2d日: %s\n", h.Day, h.Name)
			found = true
		}
	}
	if !found {
		fmt.Println("  なし")
	}
	fmt.Println()
}

func main() {
	fmt.Println("=== 月間カレンダー(祝日対応版)Go言語 ===\n")

	err := loadHolidaysFromFile(holidayFile)
	if err != nil {
		fmt.Printf("ローカルファイル '%s' が見つかりません。\n", holidayFile)
		fmt.Print("内閣府から祝日データをダウンロードしますか? (y/n): ")
		
		var response string
		fmt.Scan(&response)
		
		if strings.ToLower(response) == "y" {
			if err := downloadAndConvertHolidayFile(); err != nil {
				fmt.Printf("ダウンロード失敗: %v\n", err)
				fmt.Println("祝日データなしで続行します。")
			} else {
				if err := loadHolidaysFromFile(holidayFile); err != nil {
					fmt.Printf("読み込みエラー: %v\n", err)
				} else {
					fmt.Printf("祝日データを読み込みました: %d件\n", len(holidays))
				}
			}
		} else {
			fmt.Println("祝日データなしで続行します。")
		}
	} else {
		fmt.Printf("祝日データを読み込みました: %d件\n", len(holidays))
	}

	var year, month int
	fmt.Print("\n年を入力してください (例: 2025): ")
	fmt.Scan(&year)

	fmt.Print("月を入力してください (1-12): ")
	fmt.Scan(&month)

	if month < 1 || month > 12 {
		fmt.Println("月は1から12の間で入力してください。")
		return
	}

	printCalendar(year, month)
}
GO_SOURCE_EOF

echo "✅ go/calendar.go 作成完了"
echo ""

# ========================================
# Step 8: Go module 初期化
# ========================================

echo "📝 Step 8: Go module 初期化"
echo "-----------------------------------"

cd go
go mod init calendar 2>/dev/null
echo "✅ Go module 初期化完了"
cd ..
echo ""

# ========================================
# Step 9: シンボリックリンク作成
# ========================================

echo "🔗 Step 9: シンボリックリンク作成"
echo "-----------------------------------"

cd c
ln -sf ../data/holidays.csv holidays.csv
echo "✅ c/holidays.csv -> ../data/holidays.csv"
cd ..

cd go
ln -sf ../data/holidays.csv holidays.csv
echo "✅ go/holidays.csv -> ../data/holidays.csv"
cd ..

echo ""

# ========================================
# Step 10: .gitignore 作成
# ========================================

echo "📝 Step 10: .gitignore 作成"
echo "-----------------------------------"

cat > .gitignore << 'GIT_EOF'
# Compiled binaries
c/calendar
go/calendar
*.o

# Go
go.sum

# Data
data/holidays_sjis.csv

# OS
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/
*.swp
GIT_EOF

echo "✅ .gitignore 作成完了"
echo ""

# ========================================
# Step 11: README.md 作成
# ========================================

echo "📝 Step 11: README.md 作成"
echo "-----------------------------------"

cat > README.md << 'README_EOF'
# 🗓️ Multi-PG-Lang Calendar - C & Go

Calendar App implemented in C and Go  
C言語とGoで実装した祝日対応カレンダー

## ✨ Features

- 🌐 C言語とGo言語で同じ機能を実装
- 📥 内閣府の祝日データ対応(UTF-8変換済み)
- 🚀 GitHub Codespaces で即座に試せる
- 📊 2言語の比較が可能

## 🚀 Quick Start

### Build & Test (自動)
```bash
./scripts/build-test.sh
```

### Individual Usage

#### C
```bash
cd c
make
echo -e "2025\n5" | ./calendar
```

#### Go
```bash
cd go
go run calendar.go
# または
go build -o calendar calendar.go
echo -e "2025\n5" | ./calendar
```

## 📁 Directory Structure
```
multi-pg-lang-calendar/
├── c/              # C言語実装
│   ├── calendar.c
│   ├── Makefile
│   └── holidays.csv -> ../data/holidays.csv
├── go/             # Go実装
│   ├── calendar.go
│   ├── go.mod
│   └── holidays.csv -> ../data/holidays.csv
├── data/           # 共通データ(祝日CSV)
│   └── holidays.csv
└── scripts/        # スクリプト
    └── build-test.sh
```

## 📄 License

MIT License
README_EOF

echo "✅ README.md 作成完了"
echo ""

# ========================================
# Step 12: ビルド・テストスクリプト作成
# ========================================

echo "📝 Step 12: ビルド・テストスクリプト作成"
echo "-----------------------------------"

cat > scripts/build-test.sh << 'BUILD_TEST_EOF'
#!/bin/bash

GREEN='\033[0;32m'
RED='\033[0;31m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"

cd "$PROJECT_ROOT"

echo -e "${YELLOW}🔨 Building C and Go...${NC}"
echo "======================================"
echo ""

# C言語ビルド
echo -e "${BLUE}📦 Building C...${NC}"
echo "-----------------------------------"
cd "$PROJECT_ROOT/c"

if make clean && make; then
    echo -e "${GREEN}✅ C build successful${NC}"
    ls -lh calendar
else
    echo -e "${RED}❌ C build failed${NC}"
    exit 1
fi
echo ""

# Goビルド
echo -e "${BLUE}📦 Building Go...${NC}"
echo "-----------------------------------"
cd "$PROJECT_ROOT/go"

if go build -o calendar calendar.go; then
    echo -e "${GREEN}✅ Go build successful${NC}"
    ls -lh calendar
else
    echo -e "${RED}❌ Go build failed${NC}"
    exit 1
fi
echo ""

cd "$PROJECT_ROOT"

# テスト実行
echo "======================================"
echo -e "${YELLOW}🧪 Testing (2025年5月)${NC}"
echo "======================================"
echo ""

TEST_INPUT="2025
5"

# Cテスト
echo -e "${BLUE}--- C言語 ---${NC}"
cd "$PROJECT_ROOT/c"
echo "$TEST_INPUT" | ./calendar
echo ""

# Goテスト
echo -e "${BLUE}--- Go言語 ---${NC}"
cd "$PROJECT_ROOT/go"
echo "$TEST_INPUT" | ./calendar
echo ""

cd "$PROJECT_ROOT"

echo "======================================"
echo -e "${GREEN}✅ All tests complete!${NC}"
echo "======================================"
BUILD_TEST_EOF

chmod +x scripts/build-test.sh
echo "✅ scripts/build-test.sh 作成完了"
echo ""

# ========================================
# Step 13: ビルド実行
# ========================================

echo "🔨 Step 13: ビルド実行"
echo "-----------------------------------"

# C言語ビルド
echo "C言語をビルド中..."
cd c
make clean > /dev/null 2>&1
make

if [ $? -eq 0 ]; then
    echo "✅ C言語ビルド成功"
else
    echo "❌ C言語ビルド失敗"
fi

cd ..

# Goビルド
echo "Go言語をビルド中..."
cd go
go build -o calendar calendar.go

if [ $? -eq 0 ]; then
    echo "✅ Go言語ビルド成功"
else
    echo "❌ Go言語ビルド失敗"
fi

cd ..

echo ""

# ========================================
# Step 14: テスト実行
# ========================================

echo "🧪 Step 14: テスト実行 (2025年5月)"
echo "-----------------------------------"
echo ""

TEST_INPUT="2025
5"

# Cテスト
echo "=== C言語 ==="
cd c
echo "$TEST_INPUT" | ./calendar
cd ..
echo ""

# Goテスト
echo "=== Go言語 ==="
cd go
echo "$TEST_INPUT" | ./calendar
cd ..

echo ""

# ========================================
# 完了メッセージ
# ========================================

echo "=========================================="
echo "✨ セットアップ完了!"
echo "=========================================="
echo ""
echo "📁 プロジェクト: $PROJECT_DIR"
echo ""
echo "📝 次のステップ:"
echo "-----------------------------------"
echo "  1. プロジェクトディレクトリに移動:"
echo "     cd $PROJECT_DIR"
echo ""
echo "  2. 再ビルド・テスト:"
echo "     ./scripts/build-test.sh"
echo ""
echo "  3. C言語を手動実行:"
echo "     cd c"
echo "     make"
echo "     echo -e '2025\n5' | ./calendar"
echo ""
echo "  4. Go言語を手動実行:"
echo "     cd go"
echo "     go run calendar.go"
echo ""
echo "=========================================="
echo ""

スクリプト② Rust追加セットアップ

#!/bin/bash

# ========================================
# Multi-PG-Lang Calendar
# スクリプト②:Rust専用セットアップ
# このスクリプト1つでRustが完全に動作します
# ========================================

echo "🚀 Multi-PG-Lang Calendar - Part 2: Rust Setup"
echo "================================================"
echo ""

# ========================================
# Step 1: プロジェクトルート検出
# ========================================

echo "📍 Step 1: プロジェクトルート検出"
echo "-----------------------------------"

if [ -d "/workspaces/multi-pg-lang-calendar" ]; then
    PROJECT_ROOT="/workspaces/multi-pg-lang-calendar"
elif [ -d "$HOME/multi-pg-lang-calendar" ]; then
    PROJECT_ROOT="$HOME/multi-pg-lang-calendar"
else
    echo "❌ プロジェクトディレクトリが見つかりません"
    echo "   Part 1 (C & Go) のセットアップを先に実行してください"
    exit 1
fi

cd "$PROJECT_ROOT"
echo "✅ プロジェクトルート: $(pwd)"
echo ""

# ========================================
# Step 2: Rustディレクトリ作成
# ========================================

echo "📁 Step 2: Rustディレクトリ作成"
echo "-----------------------------------"

mkdir -p rust

echo "✅ ディレクトリ作成完了"
echo ""

# ========================================
# Step 3: Rustインストール確認
# ========================================

echo "🔧 Step 3: Rustインストール確認"
echo "-----------------------------------"

if command -v cargo &> /dev/null; then
    echo "✅ Rust already installed"
    rustc --version
    cargo --version
else
    echo "Installing Rust..."
    
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
    
    # 環境変数を読み込み
    export PATH="$HOME/.cargo/bin:$PATH"
    if [ -f "$HOME/.cargo/env" ]; then
        source "$HOME/.cargo/env"
    fi
    
    if command -v cargo &> /dev/null; then
        echo "✅ Rust installed successfully"
        rustc --version
        cargo --version
    else
        echo "❌ Rust installation failed"
        exit 1
    fi
fi

echo ""

# ========================================
# Step 4: Rustソースコード作成
# ========================================

echo "📝 Step 4: Rustソースコード作成"
echo "-----------------------------------"

cd rust

# 環境変数を確実に読み込み
export PATH="$HOME/.cargo/bin:$PATH"
if [ -f "$HOME/.cargo/env" ]; then
    source "$HOME/.cargo/env"
fi

# Cargo プロジェクト初期化
if [ ! -f "Cargo.toml" ]; then
    cargo init --name calendar 2>/dev/null
    echo "✅ Rust project initialized"
fi

# Cargo.toml 作成
cat > Cargo.toml << 'TOML_EOF'
[package]
name = "calendar"
version = "0.1.0"
edition = "2021"

[dependencies]
reqwest = { version = "0.11", features = ["blocking"] }
encoding_rs = "0.8"
TOML_EOF

echo "✅ Cargo.toml 作成完了"

# main.rs 作成
cat > src/main.rs << 'RUST_SOURCE_EOF'
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};

#[derive(Debug, Clone)]
struct Holiday {
    year: i32,
    month: i32,
    day: i32,
    name: String,
}

const WEEKDAYS: [&str; 7] = ["日", "月", "火", "水", "木", "金", "土"];
const HOLIDAY_URL: &str = "https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv";
const HOLIDAY_FILE: &str = "holidays.csv";

fn download_and_convert_holiday_file() -> Result<(), Box<dyn std::error::Error>> {
    println!("内閣府から祝日データをダウンロード中...");
    
    let response = reqwest::blocking::get(HOLIDAY_URL)?;
    let bytes = response.bytes()?;
    
    let (decoded, _, _) = encoding_rs::SHIFT_JIS.decode(&bytes);
    
    let mut file = File::create(HOLIDAY_FILE)?;
    file.write_all(decoded.as_bytes())?;
    
    println!("祝日データを保存しました: {}", HOLIDAY_FILE);
    println!("✅ UTF-8に変換しました");
    Ok(())
}

fn load_holidays_from_file(filename: &str) -> Result<Vec<Holiday>, Box<dyn std::error::Error>> {
    let file = File::open(filename)?;
    let reader = BufReader::new(file);
    let mut holidays = Vec::new();
    let mut line_num = 0;

    for line in reader.lines() {
        line_num += 1;
        let line = line?;
        
        if line_num == 1 {
            continue;
        }
        
        let trimmed_line = line.trim();
        if trimmed_line.is_empty() {
            continue;
        }

        let parts: Vec<&str> = trimmed_line.split(',').collect();
        if parts.len() < 2 {
            continue;
        }

        let date_str = parts[0].trim().replace('/', "-");
        let name = parts[1].trim().to_string();

        let date_parts: Vec<&str> = date_str.split('-').collect();
        if date_parts.len() != 3 {
            continue;
        }

        match (
            date_parts[0].parse::<i32>(),
            date_parts[1].parse::<i32>(),
            date_parts[2].parse::<i32>(),
        ) {
            (Ok(year), Ok(month), Ok(day)) => {
                if year >= 1900 && year <= 2100 && month >= 1 && month <= 12 && day >= 1 && day <= 31 {
                    holidays.push(Holiday { year, month, day, name });
                }
            }
            _ => {}
        }
    }

    Ok(holidays)
}

fn is_holiday(holidays: &[Holiday], year: i32, month: i32, day: i32) -> Option<String> {
    holidays
        .iter()
        .find(|h| h.year == year && h.month == month && h.day == day)
        .map(|h| h.name.clone())
}

fn is_leap_year(year: i32) -> bool {
    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}

fn get_days_in_month(year: i32, month: i32) -> i32 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => if is_leap_year(year) { 29 } else { 28 },
        _ => 0,
    }
}

fn get_weekday(year: i32, month: i32, day: i32) -> i32 {
    let mut y = year;
    let mut m = month;
    
    if m < 3 {
        y -= 1;
        m += 12;
    }
    
    let h = (day + (13 * (m + 1)) / 5 + y + y / 4 - y / 100 + y / 400) % 7;
    (h + 6) % 7
}

fn print_calendar(holidays: &[Holiday], year: i32, month: i32) {
    println!("\n        {}年 {}月", year, month);
    println!("----------------------------");

    for wd in WEEKDAYS.iter() {
        print!(" {} ", wd);
    }
    println!();
    println!("----------------------------");

    let first_weekday = get_weekday(year, month, 1);
    let days_in_month = get_days_in_month(year, month);

    for _ in 0..first_weekday {
        print!("    ");
    }

    let mut current_weekday = first_weekday;
    for day in 1..=days_in_month {
        let is_hol = is_holiday(holidays, year, month, day).is_some();
        
        if is_hol {
            print!("{:3}*", day);
        } else {
            print!("{:3} ", day);
        }

        current_weekday += 1;
        if current_weekday == 7 {
            println!();
            current_weekday = 0;
        }
    }

    if current_weekday != 0 {
        println!();
    }
    println!("----------------------------");

    println!("\n【祝日】");
    let mut month_holidays: Vec<_> = holidays
        .iter()
        .filter(|h| h.year == year && h.month == month)
        .collect();
    
    month_holidays.sort_by_key(|h| h.day);
    
    if month_holidays.is_empty() {
        println!("  なし");
    } else {
        for h in month_holidays {
            println!("  {:2}日: {}", h.day, h.name);
        }
    }
    println!();
}

fn read_line() -> String {
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();
    input.trim().to_string()
}

fn main() {
    println!("=== 月間カレンダー(祝日対応版)Rust ===\n");

    let mut holidays = Vec::new();
    
    match load_holidays_from_file(HOLIDAY_FILE) {
        Ok(data) => {
            holidays = data;
            println!("祝日データを読み込みました: {}件", holidays.len());
        }
        Err(_) => {
            println!("ローカルファイル '{}' が見つかりません。", HOLIDAY_FILE);
            print!("内閣府から祝日データをダウンロードしますか? (y/n): ");
            io::stdout().flush().unwrap();
            
            let response = read_line();
            
            if response.to_lowercase() == "y" {
                match download_and_convert_holiday_file() {
                    Ok(_) => {
                        match load_holidays_from_file(HOLIDAY_FILE) {
                            Ok(data) => {
                                holidays = data;
                                println!("祝日データを読み込みました: {}件", holidays.len());
                            }
                            Err(e) => {
                                println!("読み込みエラー: {}", e);
                            }
                        }
                    }
                    Err(e) => {
                        println!("ダウンロードエラー: {}", e);
                        println!("祝日データなしで続行します。");
                    }
                }
            } else {
                println!("祝日データなしで続行します。");
            }
        }
    }

    print!("\n年を入力してください (例: 2025): ");
    io::stdout().flush().unwrap();
    let year: i32 = read_line().parse().unwrap_or(2025);

    print!("月を入力してください (1-12): ");
    io::stdout().flush().unwrap();
    let month: i32 = read_line().parse().unwrap_or(1);

    if !(1..=12).contains(&month) {
        println!("月は1から12の間で入力してください。");
        return;
    }

    print_calendar(&holidays, year, month);
}
RUST_SOURCE_EOF

echo "✅ rust/src/main.rs 作成完了"
echo ""

cd "$PROJECT_ROOT"

# ========================================
# Step 5: シンボリックリンク作成
# ========================================

echo "🔗 Step 5: シンボリックリンク作成"
echo "-----------------------------------"

cd rust
ln -sf ../data/holidays.csv holidays.csv
echo "✅ rust/holidays.csv -> ../data/holidays.csv"
cd "$PROJECT_ROOT"

echo ""

# ========================================
# Step 6: Rustビルド
# ========================================

echo "🔨 Step 6: Rustビルド(5-10分かかります)"
echo "-----------------------------------"

cd rust

# 環境変数を再確認
export PATH="$HOME/.cargo/bin:$PATH"
if [ -f "$HOME/.cargo/env" ]; then
    source "$HOME/.cargo/env"
fi

echo "Rustをビルド中..."
echo "(初回は依存関係のダウンロードに時間がかかります)"
echo ""

if cargo build --release; then
    echo ""
    echo "✅ Rustビルド成功"
    ls -lh target/release/calendar
else
    echo ""
    echo "❌ Rustビルド失敗"
    cd "$PROJECT_ROOT"
    exit 1
fi

cd "$PROJECT_ROOT"
echo ""

# ========================================
# Step 7: Rustテスト実行
# ========================================

echo "🧪 Step 7: Rustテスト実行 (2025年5月)"
echo "-----------------------------------"
echo ""

cd rust
echo -e "2025\n5" | ./target/release/calendar
cd "$PROJECT_ROOT"

echo ""

# ========================================
# Step 8: .gitignore 更新
# ========================================

echo "📝 Step 8: .gitignore 更新"
echo "-----------------------------------"

cat > .gitignore << 'GIT_EOF'
# Compiled binaries
c/calendar
go/calendar
kotlin/calendar.jar
rust/target/

# Build artifacts
*.o
*.class

# Go
go.sum

# Rust
Cargo.lock

# Data
data/holidays_sjis.csv

# OS
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/
*.swp
GIT_EOF

echo "✅ .gitignore 更新完了"
echo ""

# ========================================
# 完了メッセージ
# ========================================

echo "=========================================="
echo "✨ Rustセットアップ完了!"
echo "=========================================="
echo ""
echo "📁 プロジェクト: $PROJECT_ROOT"
echo ""
echo "✅ Rust: ビルド成功"
echo ""
echo "📝 次のステップ:"
echo "-----------------------------------"
echo "  1. Rustを手動実行:"
echo "     cd $PROJECT_ROOT/rust"
echo "     echo -e '2025\n5' | ./target/release/calendar"
echo ""
echo "  2. Kotlinをセットアップ:"
echo "     ./setup-kotlin.sh"
echo ""
echo "  3. 全言語を一括テスト (Kotlin完了後):"
echo "     cd $PROJECT_ROOT"
echo "     ./scripts/build-test-all.sh"
echo ""
echo "=========================================="
echo ""

実行手順

第1日目:C言語、Go言語、Rustのセットアップ

以下は、ターミナルで実行しよう。(各コマンドを順次実行)

# 作業ディレクトリに移動(GitHub Codespacesの場合)
cd /workspaces

# ========================================
# スクリプト① C言語とGo言語セットアップ
# ========================================

# 1. ファイルを作成
nano setup-c-go.sh

# 2. スクリプト①の内容をコピー&ペースト
#    (Ctrl+Shift+V で貼り付け)

# 3. 保存して終了
#    Ctrl+O → Enter → Ctrl+X

# 4. 実行権限を付与
chmod +x setup-c-go.sh

# 5. 実行
./setup-c-go.sh

# ========================================
# スクリプト② Rust追加セットアップ
# ========================================

# 1. ファイルを作成
nano setup-rust.sh

# 2. スクリプト②の内容をコピー&ペースト

# 3. 保存して終了(Ctrl+O → Enter → Ctrl+X)

# 4. 実行権限を付与
chmod +x setup-rust.sh

# 5. 実行
./setup-rust.sh

実行例①

(略)
======================================
🧪 Testing (2025年5月)
======================================

--- C言語 ---
=== 月間カレンダー(祝日対応版)C言語 ===

祝日データを読み込みました: 1050件

年を入力してください (例: 2025): 月を入力してください (1-12): 
        2025年 5月
----------------------------
 日  月  火  水  木  金  土 
----------------------------
                  1   2   3*
  4*  5*  6*  7   8   9  10 
 11  12  13  14  15  16  17 
 18  19  20  21  22  23  24 
 25  26  27  28  29  30  31 
----------------------------

【祝日】
   3日: 憲法記念日
   4日: みどりの日
   5日: こどもの日
   6日: 休日


--- Go言語 ---
=== 月間カレンダー(祝日対応版)Go言語 ===

祝日データを読み込みました: 1050件

年を入力してください (例: 2025): 月を入力してください (1-12): 
        2025年 5月
----------------------------
 日  月  火  水  木  金  土 
----------------------------
                  1   2   3*
  4*  5*  6*  7   8   9  10 
 11  12  13  14  15  16  17 
 18  19  20  21  22  23  24 
 25  26  27  28  29  30  31 
----------------------------

【祝日】
   3日: 憲法記念日
   4日: みどりの日
   5日: こどもの日
   6日: 休日


======================================
✅ All tests complete!

実行例②

(略)
🧪 Step 7: Rustテスト実行 (2025年5月)
-----------------------------------

=== 月間カレンダー(祝日対応版)Rust ===

祝日データを読み込みました: 1050件

年を入力してください (例: 2025): 月を入力してください (1-12): 
        2025年 5月
----------------------------
 日  月  火  水  木  金  土 
----------------------------
                  1   2   3*
  4*  5*  6*  7   8   9  10 
 11  12  13  14  15  16  17 
 18  19  20  21  22  23  24 
 25  26  27  28  29  30  31 
----------------------------

【祝日】
   3日: 憲法記念日
   4日: みどりの日
   5日: こどもの日
   6日: 休日


📝 Step 8: .gitignore 更新
-----------------------------------
✅ .gitignore 更新完了

==========================================
✨ Rustセットアップ完了!
==========================================

次回は、「第2日目:Kotlin、Git & Pushのセットアップ」をご紹介しよう。

コメント

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