#!/usr/bin/env bash # Snapshot Public Holidays + School Holidays for all 16 German Bundeslaender # from openholidaysapi.org. The result is committed to the repo and imported # at first DB boot by school-service. Re-run yearly (or whenever the next # school year's data needs to be added). # # Usage: bash scripts/calendar-snapshot.sh [FIRST_YEAR] [LAST_YEAR] # defaults: current year .. current year + 2 # # Output: school-service/internal/seed/calendar_holidays.json # shape: [{ region, event_type, name_de, name_en, start_date, end_date }, ...] set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" OUT="$ROOT/school-service/internal/seed/calendar_holidays.json" mkdir -p "$(dirname "$OUT")" START_YEAR="${1:-$(date +%Y)}" END_YEAR="${2:-$((START_YEAR + 2))}" API="https://openholidaysapi.org" # DE-XX codes for all 16 Bundeslaender (alphabetical). REGIONS=( "DE-BW" "DE-BY" "DE-BE" "DE-BB" "DE-HB" "DE-HH" "DE-HE" "DE-MV" "DE-NI" "DE-NW" "DE-RP" "DE-SL" "DE-SN" "DE-ST" "DE-SH" "DE-TH" ) if ! command -v jq >/dev/null 2>&1; then echo "jq is required (brew install jq)" >&2 exit 1 fi TMP=$(mktemp) trap 'rm -f "$TMP"' EXIT echo '[]' > "$TMP" fetch() { local endpoint="$1" region="$2" year="$3" curl -sf -G "$API/$endpoint" \ --data-urlencode "countryIsoCode=DE" \ --data-urlencode "languageIsoCode=DE" \ --data-urlencode "validFrom=${year}-01-01" \ --data-urlencode "validTo=${year}-12-31" \ --data-urlencode "subdivisionCode=$region" \ || echo '[]' } # Map OpenHolidaysAPI shape → our DB schema. The API returns an array of: # { id, startDate, endDate, type, name: [{ language, text }], ... } # We keep DE name as canonical, EN name if present, plus dates and a typed # event_type discriminator. PublicHolidays and SchoolHolidays come from two # separate endpoints. normalise_jq=' map({ region: $region, event_type: $event_type, name_de: ((.name // []) | map(select(.language == "DE")) | .[0].text // ""), name_en: ((.name // []) | map(select(.language == "EN")) | .[0].text // null), start_date: .startDate, end_date: .endDate }) | map(select(.name_de != "")) ' for region in "${REGIONS[@]}"; do for year in $(seq "$START_YEAR" "$END_YEAR"); do echo " $region $year — public" >&2 fetch "PublicHolidays" "$region" "$year" \ | jq --arg region "$region" --arg event_type "public_holiday" "$normalise_jq" \ | jq -s --slurpfile existing "$TMP" '$existing[0] + .[0]' > "$TMP.new" mv "$TMP.new" "$TMP" echo " $region $year — school" >&2 fetch "SchoolHolidays" "$region" "$year" \ | jq --arg region "$region" --arg event_type "school_holiday" "$normalise_jq" \ | jq -s --slurpfile existing "$TMP" '$existing[0] + .[0]' > "$TMP.new" mv "$TMP.new" "$TMP" done done # Deduplicate (the API sometimes returns overlapping rows for events that # straddle a year boundary) and sort for a stable diff. jq 'unique_by({region, event_type, name_de, start_date}) | sort_by([.region, .start_date])' \ "$TMP" > "$OUT" echo echo "Wrote $(jq length "$OUT") events to $OUT"