今年も残り少なくなってきた。そろそろ来年のカレンダーの準備をする時期ではないだろうか。
本ブログでもこれまでプログラミング学習の一環として、カレンダー作りについてご紹介している。
日付を表示することは比較的容易である。ただし、これではカレンダーとしては不十分である。
やはり、祝日を表示できれば実用性がかなり高まる。
そこで、内閣府の祝日CSVをダウンロードして、プログラムと同じフォルダに保存する方法で祝日対応カレンダーを作っていこう。
内閣府の祝日CSVhttps://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv
- 昭和30年以降の全祝日データ
- 政府公式なので信頼性が高い
- プログラムから直接ダウンロード可能
メリット: 公式データ、自動更新可能
デメリット: ネットワーク接続が必要
C言語だけでなく、GO言語、Rust、Kotlinも含めたマルチ言語で挑戦してみよう。
なお、各言語の特徴は以下の通りである。
| 項目 | C | Go | Rust | Kotlin |
|---|---|---|---|---|
| パラダイム | 手続き型、低レベル | 手続き的/並行志向 | マルチパラダイム(手続き・関数・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、組み込み、ドライバ、クロスプラットフォーム低レイヤ | ネットワークサーバ、クラウド、ツール | システムプログラミング、高性能アプリ、WebAssembly | Android、サーバー(JVM)、マルチプラットフォーム開発 |
| 長所(概観) | 最小ランタイム・高速・移植性高い | シンプル・並行処理が書きやすい・ビルドが高速 | メモリ安全+高速・抽象の性能コストが低い | JVMエコシステム・null安全・開発生産性高い |
| 短所(概観) | 安全機構が乏しく手間がかかる | ジェネリクス歴が浅く抽象表現に制約あり(改善済) | 学習コストが高い・所有権周りが厳格 | ネイティブ性能はJVMに依存・大規模JVMの起動コスト |
開発環境は、GitHubのCodespacesを利用する。
GitHub Codespacesで開発環境を作る方法は以下の通りである。
GitHub Codespacesで開発環境を作る方法
ステップ1:GitHubアカウントを作成(持っていない場合)
- https://github.com にアクセスしよう。
- 「Sign up」をクリックしよう。
- メールアドレス、パスワード、ユーザー名を入力しよう。
- メールで届く認証コードを入力しよう。
ステップ2:新しいリポジトリを作成
2-1. リポジトリ作成ページを開く
- GitHubにログインしよう。
- 左上の「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を開く
- 作成したリポジトリのページで、緑色の 「<> Code」 ボタンをクリックしよう。
- 「Codespaces」 タブを選択しよう。
- 「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のセットアップ」をご紹介しよう。




コメント