564 lines
21 KiB
Python
564 lines
21 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Stefi custody calendar generator.
|
|
|
|
Based on:
|
|
- Rozsudek 9 Nc 3731/2021-53 (prawomocny 01.06.2022):
|
|
Even ISO weeks: OJCIEC (Mon 8:00 - Mon 8:00)
|
|
Odd ISO weeks: MATKA (Mon 8:00 - Mon 8:00)
|
|
|
|
- Dohoda o uprave pomeru nezletile (effective 01.07.2023):
|
|
Vacation amendments override ISO week rule for specific holiday periods.
|
|
|
|
Usage:
|
|
python stefi_custody_calendar.py --year 2026 --credentials /path/to/gcp-service-account.json [--dry-run]
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from datetime import datetime, date, timedelta
|
|
from typing import Optional
|
|
|
|
from google.oauth2.service_account import Credentials
|
|
from googleapiclient.discovery import build
|
|
|
|
CALENDAR_ID = "ked0ao233h92ogj8l80b7e9a4g@group.calendar.google.com"
|
|
SCOPES = ["https://www.googleapis.com/auth/calendar"]
|
|
TZ = "Europe/Prague"
|
|
COLOR_ID = "9" # blue
|
|
EVENT_SUMMARY = "👧 Stefi z tatą"
|
|
TAG = "(custody-gen)"
|
|
OJCIEC = "OJCIEC"
|
|
MATKA = "MATKA"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Czech school holiday dates per school year
|
|
# Source: MSMT (Ministry of Education) - update yearly
|
|
# Keys are the calendar year in which the holiday *starts*.
|
|
# ---------------------------------------------------------------------------
|
|
HOLIDAYS = {
|
|
2026: {
|
|
# Jarni prazdniny: depends on district. Velke Pritocno (Kladno) region.
|
|
# Update this each year from https://www.stredniskoly.cz/prazdniny.html
|
|
"jarni": {"start": date(2026, 2, 16), "end_school": date(2026, 2, 23)},
|
|
# Velikonoce 2026: Velky patek 3.4, Pondeli velikonocni 6.4
|
|
# Prazdniny: ctvrtek 2.4 - pondeli 6.4
|
|
"velikonoce": {
|
|
"start": date(2026, 4, 2),
|
|
"easter_monday": date(2026, 4, 6),
|
|
"end_school": date(2026, 4, 7), # utery po Vel. pondeli
|
|
},
|
|
# Letni prazdniny: always 1.7 - 31.8
|
|
"letni": {"start": date(2026, 7, 1), "end": date(2026, 8, 31)},
|
|
# First school day after summer
|
|
"first_school_sep": date(2026, 9, 1),
|
|
# Podzimni prazdniny 2026: 29-30.10
|
|
"podzimni": {"start": date(2026, 10, 29), "end_school": date(2026, 11, 2)},
|
|
# Vanocni prazdniny: 23.12 - 3.1
|
|
"vanocni": {"start": date(2026, 12, 23), "end_school": date(2027, 1, 4)},
|
|
},
|
|
2027: {
|
|
"jarni": {"start": date(2027, 2, 15), "end_school": date(2027, 2, 22)},
|
|
# Velikonoce 2027: Velky patek 26.3, Pondeli velikonocni 29.3
|
|
"velikonoce": {
|
|
"start": date(2027, 3, 25),
|
|
"easter_monday": date(2027, 3, 29),
|
|
"end_school": date(2027, 3, 30),
|
|
},
|
|
"letni": {"start": date(2027, 7, 1), "end": date(2027, 8, 31)},
|
|
"first_school_sep": date(2027, 9, 1),
|
|
"podzimni": {"start": date(2027, 10, 28), "end_school": date(2027, 11, 1)},
|
|
"vanocni": {"start": date(2027, 12, 23), "end_school": date(2028, 1, 3)},
|
|
},
|
|
2028: {
|
|
"jarni": {"start": date(2028, 2, 14), "end_school": date(2028, 2, 21)},
|
|
# Velikonoce 2028: Velky patek 14.4, Pondeli velikonocni 17.4
|
|
"velikonoce": {
|
|
"start": date(2028, 4, 13),
|
|
"easter_monday": date(2028, 4, 17),
|
|
"end_school": date(2028, 4, 18),
|
|
},
|
|
"letni": {"start": date(2028, 7, 1), "end": date(2028, 8, 31)},
|
|
"first_school_sep": date(2028, 9, 1),
|
|
"podzimni": {"start": date(2028, 10, 26), "end_school": date(2028, 10, 30)},
|
|
"vanocni": {"start": date(2028, 12, 23), "end_school": date(2029, 1, 3)},
|
|
},
|
|
}
|
|
|
|
|
|
def is_even_year(year: int) -> bool:
|
|
return year % 2 == 0
|
|
|
|
|
|
def iso_week_parent(dt: date) -> str:
|
|
"""Base rule from rozsudek: even ISO week = OJCIEC, odd = MATKA."""
|
|
iso_week = dt.isocalendar()[1]
|
|
return OJCIEC if iso_week % 2 == 0 else MATKA
|
|
|
|
|
|
def get_vacation_events(year: int) -> list[dict]:
|
|
"""
|
|
Generate vacation events per dohoda Clanek II.
|
|
|
|
Dohoda rules (calendar year parity):
|
|
a) Jarni prazdniny:
|
|
- sudy rok (even): OJCIEC, 9:00 first day - 8:00 first school day
|
|
- lichy rok (odd): MATKA
|
|
b) Velikonocni prazdniny:
|
|
- sudy rok (even): MATKA, 9:00 first day - 8:00 day after Easter Monday
|
|
- lichy rok (odd): OJCIEC
|
|
c) Letni prazdniny (always split into 4 blocks of ~15 days):
|
|
- sudy rok (even): OJCIEC 1.7-15.7, MATKA 15.7-1.8, OJCIEC 1.8-15.8, MATKA 15.8-first school day
|
|
- lichy rok (odd): MATKA 1.7-15.7, OJCIEC 15.7-1.8, MATKA 1.8-15.8, OJCIEC 15.8-first school day
|
|
All blocks: 9:00 start, 9:00 end (except last: 8:00 first school day)
|
|
d) Podzimni prazdniny:
|
|
- sudy rok (even): OJCIEC, 9:00 first day - 8:00 first school day
|
|
- lichy rok (odd): MATKA
|
|
e) Vanocni prazdniny:
|
|
- sudy rok (even): MATKA 23.12 9:00 - 26.12 10:00, OJCIEC 26.12 10:00 - 8:00 first school day
|
|
- lichy rok (odd): OJCIEC 23.12 9:00 - 26.12 10:00, MATKA 26.12 10:00 - 8:00 first school day
|
|
"""
|
|
if year not in HOLIDAYS:
|
|
print(f"WARNING: No holiday data for year {year}. Add it to HOLIDAYS dict.")
|
|
return []
|
|
|
|
h = HOLIDAYS[year]
|
|
even = is_even_year(year)
|
|
events = []
|
|
|
|
# a) Jarni prazdniny - sudy=OJCIEC, lichy=MATKA
|
|
jarni_parent = OJCIEC if even else MATKA
|
|
if jarni_parent == OJCIEC:
|
|
events.append({
|
|
"summary": EVENT_SUMMARY,
|
|
"description": (
|
|
f"Dohoda art.II.1.a | {jarni_parent} | Jarni prazdniny | "
|
|
f"{year} ({'sudy' if even else 'lichy'}) | {TAG}"
|
|
),
|
|
"start": {"dateTime": f"{h['jarni']['start']}T09:00:00", "timeZone": TZ},
|
|
"end": {"dateTime": f"{h['jarni']['end_school']}T08:00:00", "timeZone": TZ},
|
|
"colorId": COLOR_ID,
|
|
})
|
|
|
|
# b) Velikonocni prazdniny - sudy=MATKA, lichy=OJCIEC (opposite to jarni!)
|
|
vel_parent = MATKA if even else OJCIEC
|
|
if vel_parent == OJCIEC:
|
|
events.append({
|
|
"summary": EVENT_SUMMARY,
|
|
"description": (
|
|
f"Dohoda art.II.1.b | {vel_parent} | Velikonocni prazdniny | "
|
|
f"{year} ({'sudy' if even else 'lichy'}) | {TAG}"
|
|
),
|
|
"start": {"dateTime": f"{h['velikonoce']['start']}T09:00:00", "timeZone": TZ},
|
|
"end": {"dateTime": f"{h['velikonoce']['end_school']}T08:00:00", "timeZone": TZ},
|
|
"colorId": COLOR_ID,
|
|
})
|
|
|
|
# c) Letni prazdniny - 4 blocks
|
|
letni_start = h["letni"]["start"]
|
|
first_school = h["first_school_sep"]
|
|
blocks = [
|
|
(date(year, 7, 1), date(year, 7, 15), "9:00", "9:00"),
|
|
(date(year, 7, 15), date(year, 8, 1), "9:00", "9:00"),
|
|
(date(year, 8, 1), date(year, 8, 15), "9:00", "9:00"),
|
|
(date(year, 8, 15), first_school, "9:00", "8:00"),
|
|
]
|
|
if even:
|
|
block_parents = [OJCIEC, MATKA, OJCIEC, MATKA]
|
|
else:
|
|
block_parents = [MATKA, OJCIEC, MATKA, OJCIEC]
|
|
|
|
for i, ((bs, be, st, et), parent) in enumerate(zip(blocks, block_parents)):
|
|
if parent == OJCIEC:
|
|
events.append({
|
|
"summary": EVENT_SUMMARY,
|
|
"description": (
|
|
f"Dohoda art.II.1.c | {parent} | Letni prazdniny block {i+1} | "
|
|
f"{year} ({'sudy' if even else 'lichy'}) | {TAG}"
|
|
),
|
|
"start": {"dateTime": f"{bs}T{st}:00", "timeZone": TZ},
|
|
"end": {"dateTime": f"{be}T{et}:00", "timeZone": TZ},
|
|
"colorId": COLOR_ID,
|
|
})
|
|
|
|
# d) Podzimni prazdniny - sudy=OJCIEC, lichy=MATKA
|
|
podz_parent = OJCIEC if even else MATKA
|
|
if podz_parent == OJCIEC:
|
|
events.append({
|
|
"summary": EVENT_SUMMARY,
|
|
"description": (
|
|
f"Dohoda art.II.1.d | {podz_parent} | Podzimni prazdniny | "
|
|
f"{year} ({'sudy' if even else 'lichy'}) | {TAG}"
|
|
),
|
|
"start": {"dateTime": f"{h['podzimni']['start']}T09:00:00", "timeZone": TZ},
|
|
"end": {"dateTime": f"{h['podzimni']['end_school']}T08:00:00", "timeZone": TZ},
|
|
"colorId": COLOR_ID,
|
|
})
|
|
|
|
# e) Vanocni prazdniny - 2 blocks
|
|
vanocni_start = h["vanocni"]["start"]
|
|
vanocni_end_school = h["vanocni"]["end_school"]
|
|
xmas_split = date(year, 12, 26)
|
|
|
|
if even:
|
|
xmas_first, xmas_second = MATKA, OJCIEC
|
|
else:
|
|
xmas_first, xmas_second = OJCIEC, MATKA
|
|
|
|
if xmas_first == OJCIEC:
|
|
events.append({
|
|
"summary": EVENT_SUMMARY,
|
|
"description": (
|
|
f"Dohoda art.II.1.e | {xmas_first} | Vanocni prazdniny 1/2 | "
|
|
f"{year} ({'sudy' if even else 'lichy'}) | {TAG}"
|
|
),
|
|
"start": {"dateTime": f"{vanocni_start}T09:00:00", "timeZone": TZ},
|
|
"end": {"dateTime": f"{xmas_split}T10:00:00", "timeZone": TZ},
|
|
"colorId": COLOR_ID,
|
|
})
|
|
if xmas_second == OJCIEC:
|
|
events.append({
|
|
"summary": EVENT_SUMMARY,
|
|
"description": (
|
|
f"Dohoda art.II.1.e | {xmas_second} | Vanocni prazdniny 2/2 | "
|
|
f"{year}-{year+1} ({'sudy' if even else 'lichy'}) | {TAG}"
|
|
),
|
|
"start": {"dateTime": f"{xmas_split}T10:00:00", "timeZone": TZ},
|
|
"end": {"dateTime": f"{vanocni_end_school}T08:00:00", "timeZone": TZ},
|
|
"colorId": COLOR_ID,
|
|
})
|
|
|
|
return events
|
|
|
|
|
|
def date_in_vacation(dt: date, year: int) -> bool:
|
|
"""Check if a date falls within any vacation period for the given year."""
|
|
if year not in HOLIDAYS:
|
|
return False
|
|
h = HOLIDAYS[year]
|
|
|
|
# Jarni
|
|
if h["jarni"]["start"] <= dt < h["jarni"]["end_school"]:
|
|
return True
|
|
# Velikonoce
|
|
if h["velikonoce"]["start"] <= dt < h["velikonoce"]["end_school"]:
|
|
return True
|
|
# Letni
|
|
if h["letni"]["start"] <= dt <= h["letni"]["end"]:
|
|
return True
|
|
# Podzimni
|
|
if h["podzimni"]["start"] <= dt < h["podzimni"]["end_school"]:
|
|
return True
|
|
# Vanocni (spans year boundary)
|
|
if dt >= h["vanocni"]["start"]:
|
|
return True
|
|
# Also check if we're in the tail of PREVIOUS year's vanocni
|
|
prev = year - 1
|
|
if prev in HOLIDAYS:
|
|
hp = HOLIDAYS[prev]
|
|
if hp["vanocni"]["start"] <= dt < hp["vanocni"]["end_school"]:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def get_transition_events(year: int) -> list[dict]:
|
|
"""
|
|
Generate events for partial weeks at vacation boundaries.
|
|
When vacation starts mid-week, the days before it still belong to
|
|
the regular ISO week custody. Same when vacation ends mid-week.
|
|
Only creates events for OJCIEC (even ISO week) periods.
|
|
"""
|
|
if year not in HOLIDAYS:
|
|
return []
|
|
|
|
h = HOLIDAYS[year]
|
|
even = is_even_year(year)
|
|
events = []
|
|
|
|
# Each vacation: (start_date, start_time, end_date, end_time, label)
|
|
vacations = [
|
|
(h["jarni"]["start"], "09:00", h["jarni"]["end_school"], "08:00", "Jarni"),
|
|
(h["velikonoce"]["start"], "09:00", h["velikonoce"]["end_school"], "08:00", "Velikonoce"),
|
|
(h["letni"]["start"], "09:00", h["first_school_sep"], "08:00", "Letni"),
|
|
(h["podzimni"]["start"], "09:00", h["podzimni"]["end_school"], "08:00", "Podzimni"),
|
|
(h["vanocni"]["start"], "09:00", h["vanocni"]["end_school"], "08:00", "Vanocni"),
|
|
]
|
|
|
|
for vac_start, vac_stime, vac_end, vac_etime, label in vacations:
|
|
# Pre-stub: week_monday 8:00 -> vacation start
|
|
dow = vac_start.weekday()
|
|
if dow > 0:
|
|
week_monday = vac_start - timedelta(days=dow)
|
|
iso_week = week_monday.isocalendar()[1]
|
|
if iso_week % 2 == 0: # OJCIEC
|
|
events.append({
|
|
"summary": EVENT_SUMMARY,
|
|
"description": (
|
|
f"Rozsudek I./VI. | OJCIEC | ISO week {iso_week} "
|
|
f"(pred {label}) | {year} | {TAG}"
|
|
),
|
|
"start": {"dateTime": f"{week_monday}T08:00:00", "timeZone": TZ},
|
|
"end": {"dateTime": f"{vac_start}T{vac_stime}:00", "timeZone": TZ},
|
|
"colorId": COLOR_ID,
|
|
})
|
|
|
|
# Post-stub: vacation end -> next Monday 8:00
|
|
dow = vac_end.weekday()
|
|
if dow > 0:
|
|
week_monday = vac_end - timedelta(days=dow)
|
|
next_monday = week_monday + timedelta(days=7)
|
|
iso_week = week_monday.isocalendar()[1]
|
|
if iso_week % 2 == 0: # OJCIEC
|
|
events.append({
|
|
"summary": EVENT_SUMMARY,
|
|
"description": (
|
|
f"Rozsudek I./VI. | OJCIEC | ISO week {iso_week} "
|
|
f"(po {label}) | {year} | {TAG}"
|
|
),
|
|
"start": {"dateTime": f"{vac_end}T{vac_etime}:00", "timeZone": TZ},
|
|
"end": {"dateTime": f"{next_monday}T08:00:00", "timeZone": TZ},
|
|
"colorId": COLOR_ID,
|
|
})
|
|
|
|
return events
|
|
|
|
|
|
def get_school_week_events(year: int) -> list[dict]:
|
|
"""
|
|
Generate school week events based on rozsudek ISO week rule.
|
|
Skip weeks that overlap with vacation periods (handled by dohoda).
|
|
|
|
Each school week: single Mon 8:00 - Mon 8:00 event.
|
|
"""
|
|
events = []
|
|
|
|
# Start from first Monday of the year (or last Monday of prev year if week 1 starts there)
|
|
jan1 = date(year, 1, 1)
|
|
# Find first Monday on or after Jan 1
|
|
first_monday = jan1 + timedelta(days=(7 - jan1.weekday()) % 7)
|
|
if first_monday > date(year, 1, 7):
|
|
first_monday -= timedelta(days=7)
|
|
|
|
# Also handle: if previous year's vanocni extends into this year,
|
|
# start from the first Monday after vanocni ends
|
|
prev_year = year - 1
|
|
actual_start = first_monday
|
|
if prev_year in HOLIDAYS:
|
|
vanocni_end = HOLIDAYS[prev_year]["vanocni"]["end_school"]
|
|
if actual_start < vanocni_end:
|
|
# Move to first Monday on or after vanocni end
|
|
days_to_mon = (7 - vanocni_end.weekday()) % 7
|
|
actual_start = vanocni_end + timedelta(days=days_to_mon)
|
|
if actual_start.weekday() != 0:
|
|
actual_start += timedelta(days=(7 - actual_start.weekday()) % 7)
|
|
|
|
monday = actual_start
|
|
end_of_year = date(year, 12, 31)
|
|
|
|
while monday <= end_of_year:
|
|
next_monday = monday + timedelta(days=7)
|
|
|
|
# Check if this week is during a vacation
|
|
# We check the Monday itself - if it's in vacation, skip
|
|
check_year = monday.year
|
|
if date_in_vacation(monday, check_year):
|
|
monday = next_monday
|
|
continue
|
|
|
|
# Also skip if any day of the week falls entirely within a vacation
|
|
week_in_vacation = False
|
|
for d in range(7):
|
|
day = monday + timedelta(days=d)
|
|
if date_in_vacation(day, day.year):
|
|
week_in_vacation = True
|
|
break
|
|
|
|
if week_in_vacation:
|
|
# This week partially overlaps vacation.
|
|
# Vacation events from dohoda handle the vacation days.
|
|
# We still create a school week event for the non-vacation part,
|
|
# but for simplicity: if vacation starts mid-week, shorten the event.
|
|
# For now: skip entirely if ANY day is vacation (dohoda overrides).
|
|
monday = next_monday
|
|
continue
|
|
|
|
iso_week = monday.isocalendar()[1]
|
|
parent = OJCIEC if iso_week % 2 == 0 else MATKA
|
|
|
|
if parent == OJCIEC:
|
|
parity = "sudy"
|
|
events.append({
|
|
"summary": EVENT_SUMMARY,
|
|
"description": (
|
|
f"Rozsudek I./VI. | {parent} | ISO week {iso_week} ({parity}) | "
|
|
f"Skolni tyden 8:00-8:00 | {TAG}"
|
|
),
|
|
"start": {"dateTime": f"{monday}T08:00:00", "timeZone": TZ},
|
|
"end": {"dateTime": f"{next_monday}T08:00:00", "timeZone": TZ},
|
|
"colorId": COLOR_ID,
|
|
})
|
|
|
|
monday = next_monday
|
|
|
|
return events
|
|
|
|
|
|
def delete_generated_events(service, year: int, dry_run: bool = False) -> int:
|
|
"""
|
|
Delete all custody-gen tagged events for the given year.
|
|
Matches by (custody-gen) tag in description for reliable identification.
|
|
"""
|
|
import re
|
|
tag_re = re.compile(r"\(custody-gen\)")
|
|
|
|
time_min = f"{year}-01-01T00:00:00Z"
|
|
time_max = f"{year+1}-01-31T23:59:59Z" # include Jan of next year for vanocni
|
|
|
|
deleted = 0
|
|
page_token = None
|
|
|
|
while True:
|
|
result = service.events().list(
|
|
calendarId=CALENDAR_ID,
|
|
timeMin=time_min,
|
|
timeMax=time_max,
|
|
maxResults=250,
|
|
pageToken=page_token,
|
|
).execute()
|
|
|
|
for event in result.get("items", []):
|
|
desc = event.get("description", "") or ""
|
|
summary = event.get("summary", "") or ""
|
|
# Primary match: tag in description
|
|
# Backup match: our summary + Dohoda/Rozsudek in description
|
|
if tag_re.search(desc) or (summary == EVENT_SUMMARY and ("Dohoda" in desc or "Rozsudek" in desc)):
|
|
eid = event["id"]
|
|
if dry_run:
|
|
start = event.get("start", {}).get("dateTime", "?")
|
|
print(f" [DRY-RUN] Would delete: {start} | {desc[:60]}")
|
|
else:
|
|
service.events().delete(
|
|
calendarId=CALENDAR_ID, eventId=eid
|
|
).execute()
|
|
print(f" Deleted: {desc[:60]}")
|
|
deleted += 1
|
|
|
|
page_token = result.get("nextPageToken")
|
|
if not page_token:
|
|
break
|
|
|
|
return deleted
|
|
|
|
|
|
def create_events(service, events: list[dict], dry_run: bool = False) -> int:
|
|
"""Create events in Google Calendar."""
|
|
created = 0
|
|
for ev in events:
|
|
if dry_run:
|
|
desc = ev.get("description", "")
|
|
start = ev["start"].get("dateTime", ev["start"].get("date", ""))
|
|
end = ev["end"].get("dateTime", ev["end"].get("date", ""))
|
|
print(f" [DRY-RUN] {start} -> {end} | {desc}")
|
|
else:
|
|
service.events().insert(
|
|
calendarId=CALENDAR_ID, body=ev
|
|
).execute()
|
|
desc = ev.get("description", "")
|
|
print(f" Created: {desc[:80]}")
|
|
created += 1
|
|
return created
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Generate Stefi custody calendar events based on rozsudek + dohoda"
|
|
)
|
|
parser.add_argument("--year", type=int, required=True, help="Calendar year (e.g. 2026)")
|
|
parser.add_argument(
|
|
"--credentials", type=str, required=True,
|
|
help="Path to GCP service account JSON"
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run", action="store_true",
|
|
help="Print events without creating them"
|
|
)
|
|
parser.add_argument(
|
|
"--no-delete", action="store_true",
|
|
help="Skip deletion of old events before creating new ones"
|
|
)
|
|
parser.add_argument(
|
|
"--rollback", action="store_true",
|
|
help="Only delete generated events (by custody-gen tag), do not create new ones"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
year = args.year
|
|
|
|
# Authenticate
|
|
creds = Credentials.from_service_account_file(args.credentials, scopes=SCOPES)
|
|
service = build("calendar", "v3", credentials=creds)
|
|
|
|
# Rollback-only mode
|
|
if args.rollback:
|
|
print(f"=== ROLLBACK: Deleting generated custody events for {year} ===")
|
|
deleted = delete_generated_events(service, year, dry_run=args.dry_run)
|
|
print(f"Total deleted: {deleted}")
|
|
return
|
|
|
|
if year not in HOLIDAYS:
|
|
print(f"ERROR: Year {year} not configured in HOLIDAYS dict.")
|
|
print(f"Available years: {sorted(HOLIDAYS.keys())}")
|
|
sys.exit(1)
|
|
|
|
print(f"=== Stefi custody calendar for {year} ===")
|
|
print(f"Even year: {is_even_year(year)}")
|
|
print()
|
|
|
|
# Step 1: Delete old generated events
|
|
if not args.no_delete:
|
|
print(f"Deleting old generated events for {year}...")
|
|
deleted = delete_generated_events(service, year, dry_run=args.dry_run)
|
|
print(f" Total deleted: {deleted}")
|
|
print()
|
|
|
|
# Step 2: Generate vacation events (dohoda)
|
|
print("Generating vacation events (dohoda)...")
|
|
vacation_events = get_vacation_events(year)
|
|
print(f" {len(vacation_events)} vacation events")
|
|
|
|
# Step 3: Generate school week events (rozsudek)
|
|
print("Generating school week events (rozsudek)...")
|
|
school_events = get_school_week_events(year)
|
|
print(f" {len(school_events)} school week events")
|
|
|
|
# Step 4: Generate transition stubs (partial weeks at vacation boundaries)
|
|
print("Generating transition stubs...")
|
|
transition_events = get_transition_events(year)
|
|
print(f" {len(transition_events)} transition events")
|
|
|
|
all_events = vacation_events + school_events + transition_events
|
|
# Sort by start time
|
|
all_events.sort(key=lambda e: e["start"].get("dateTime", e["start"].get("date", "")))
|
|
|
|
print(f"\nTotal events to create: {len(all_events)}")
|
|
print()
|
|
|
|
# Step 5: Create events
|
|
print("Creating events...")
|
|
created = create_events(service, all_events, dry_run=args.dry_run)
|
|
print(f"\nDone! Created {created} events for {year}.")
|
|
|
|
# Summary
|
|
print("\n=== Summary ===")
|
|
ojciec_count = sum(1 for e in all_events if OJCIEC in e.get("description", ""))
|
|
matka_count = sum(1 for e in all_events if MATKA in e.get("description", ""))
|
|
print(f"OJCIEC events: {ojciec_count}")
|
|
print(f"MATKA events: {matka_count}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|