# /// script # dependencies = ["icalendar==7.3.0", "recurring-ical-events==3.8.2"] # /// """Expected VEVENT instances for tests/corpus.rs, from recurring-ical-events. uv run oracle.py > oracle.tsv # compact, checked in uv run oracle.py --full > full.tsv # every start, for review Conventions shared with corpus.rs: starts in [1970, 2040) as unix seconds, floating times and dates in UTC, X-WR-TIMEZONE ignored. """ import datetime as dt import os import sys from concurrent.futures import ProcessPoolExecutor UTC = dt.timezone.utc LO, HI = dt.datetime(1970, 1, 1, tzinfo=UTC), dt.datetime(2040, 1, 1, tzinfo=UTC) def fnv(data: bytes) -> str: h = 0xCBF29CE484222325 for b in data: h = ((h ^ b) * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF return f"{h:016x}" def expand(path): # One process per file: icalendar caches TZIDs globally, so a VTIMEZONE # from one fixture would leak into the next. import icalendar import recurring_ical_events raw = open(path, "rb").read() try: txt = raw.decode("utf-8") except UnicodeDecodeError: txt = raw.decode("latin-1") starts = [] try: for cal in icalendar.Calendar.from_ical(txt, multiple=True): cal.pop("X-WR-TIMEZONE", None) for ev in recurring_ical_events.of(cal, components=["VEVENT"]).between(LO, HI): s = ev.get("DTSTART") if s is None: continue s = s.dt if not isinstance(s, dt.datetime): s = dt.datetime(s.year, s.month, s.day) if s.tzinfo is None: s = s.replace(tzinfo=UTC) if LO <= s < HI: starts.append(int(s.timestamp())) status = "ok" except Exception as e: status = "error:" + type(e).__name__ starts.sort() return fnv(raw), status, starts def main(): d, full = sys.argv[1], "--full" in sys.argv names = sorted(n for n in os.listdir(d) if n.endswith(".ics")) with ProcessPoolExecutor(max_tasks_per_child=1) as pool: for name, (file_hash, status, starts) in zip( names, pool.map(expand, [os.path.join(d, n) for n in names]) ): text = ",".join(map(str, starts)) last = text if full else fnv(text.encode()) print(f"{name}\t{file_hash}\t{status}\t{len(starts)}\t{last}", flush=True) if __name__ == "__main__": main()