RFC 5545 표준에 따라 iCalendar 파일의 구조, 속성, 반복 규칙 문법 오류를 검증합니다.
원제 · Calendar ICS Validator
---
name: calendar-ics-validator
description: Validates .ics iCalendar files against RFC 5545 basics: BEGIN/END nesting, required VCALENDAR and VEVENT properties (VERSION, PRODID, UID, DTSTAMP, DTSTART), date and time formats, DTEND vs DURATION, TZID references, RRULE syntax, and line folding. Use when a calendar invite will not import, events show at the wrong time, or recurring events misbehave. Includes a stdlib Python checker.
---
# Calendar ICS Validator
You help people find and fix problems in iCalendar (`.ics`) files exported from apps, generated by code, or attached to invites. You check structure, required properties, date-time values, time zones, and recurrence rules, then explain each problem in plain words with the exact fix. You follow RFC 5545 and note where popular clients (Google Calendar, Outlook, Apple Calendar) are stricter or looser.
## Files in this skill
- `scripts/check_ics.py` - parses an `.ics` file (with line unfolding) and reports errors and warnings with line numbers (Python 3 standard library only)
- `references/ics-fields.md` - components, required and common properties, and value formats
- `references/recurrence-rules.md` - RRULE parts, valid values, EXDATE/RDATE, and common recurrence bugs
- `templates/fix-checklist.md` - the checklist and report shape to return
- `examples/sample.ics` - a small valid calendar with a time zone, a one-off event, and a weekly recurring event
## Workflow
### 1. Get the file
Ask for the `.ics` content or path, the app that produced it, the app that fails to import it, and the symptom (import error, wrong time, missing occurrences, duplicates).
### 2. Run the checker
```
python3 scripts/check_ics.py path/to/calendar.ics
python3 scripts/check_ics.py --json path/to/calendar.ics
python3 scripts/check_ics.py --self-test
```
Exit code 0 means no errors (warnings may remain), 1 means errors were found.
### 3. Interpret
- **Errors** break imports or violate RFC 5545 (missing UID, DTEND before DTSTART, unbalanced BEGIN/END, invalid RRULE).
- **Warnings** often cause wrong behavior in some clients (floating times, TZID without VTIMEZONE, lines over 75 octets, LF-only line endings).
Use `references/ics-fields.md` and `references/recurrence-rules.md` to explain each finding.
### 4. Diagnose the symptom
- Wrong time: check for floating times (no `Z`, no `TZID`), mismatched TZID names, or UTC `Z` added to a local time.
- Missing or extra occurrences: check `RRULE` `UNTIL` vs `DTSTART` type, `COUNT`, `BYDAY`, and `EXDATE` format matching `DTSTART`.
- Duplicates on re-import: check that `UID` is stable and `SEQUENCE` increases on updates.
### 5. Report
Fill `templates/fix-checklist.md`: summary, findings table (severity, line, property, problem, fix), corrected snippet, and re-test steps.
## Rules
- Quote the exact offending line and give a corrected line.
- Do not silently change event times; if a time zone must change, say so and ask.
- Keep `UID` values unchanged when fixing, unless they are missing or duplicated.
- Never include private attendee emails in examples you write back; replace them with placeholders.
FILE:references/ics-fields.md
# iCalendar Fields Reference (RFC 5545 essentials)
## Structure
```
BEGIN:VCALENDAR
VERSION:2.0 (required)
PRODID:-//Org//App//EN (required)
CALSCALE:GREGORIAN (optional, default)
METHOD:PUBLISH (optional; REQUEST/REPLY/CANCEL for invites)
BEGIN:VTIMEZONE ... END:VTIMEZONE (one per TZID used)
BEGIN:VEVENT ... END:VEVENT
BEGIN:VTODO ... END:VTODO
END:VCALENDAR
```
Every `BEGIN:X` needs a matching `END:X` in the right order. `VALARM` lives inside `VEVENT` or `VTODO`.
## VEVENT properties
| Property | Required | Notes |
|---|---|---|
| UID | yes | Globally unique and stable across updates (e.g. `uuid@yourdomain`). |
| DTSTAMP | yes | When this object was created/sent, in UTC: `20261015T070000Z`. |
| DTSTART | yes* | Required unless METHOD is set and the client allows it; always include it. |
| DTEND | no | Must be later than DTSTART. Exclusive end. |
| DURATION | no | e.g. `PT1H30M`, `P1D`. Not allowed together with DTEND. |
| SUMMARY | no | Title shown in calendars. |
| DESCRIPTION, LOCATION | no | Escape `,` `;` `\` and newlines as `\,` `\;` `\\` `\n`. |
| RRULE | no | Recurrence rule; see recurrence-rules.md. |
| EXDATE / RDATE | no | Excluded / extra dates; same value type and TZID as DTSTART. |
| SEQUENCE | no | Integer; increment on each significant update. |
| STATUS | no | TENTATIVE, CONFIRMED, CANCELLED. |
| ORGANIZER / ATTENDEE | no | `mailto:` URIs with parameters like `CN=` and `PARTSTAT=`. |
## Date and time values
| Form | Example | Meaning |
|---|---|---|
| DATE | `DTSTART;VALUE=DATE:20261020` | All-day; DTEND is the next day (exclusive). |
| UTC DATE-TIME | `DTSTART:20261020T090000Z` | Absolute time in UTC. |
| Local with TZID | `DTSTART;TZID=Europe/Istanbul:20261020T120000` | Wall time in that zone; needs a matching VTIMEZONE for strict clients. |
| Floating | `DTSTART:20261020T120000` | No zone; shows the same wall time everywhere. Usually a bug for meetings. |
Format: `YYYYMMDD` or `YYYYMMDDTHHMMSS` with optional `Z`. Hours 00-23, minutes and seconds 00-59 (60 allowed for leap seconds).
## Line rules
- Lines end with CRLF (`\r\n`).
- Lines longer than 75 octets must be folded: insert CRLF followed by one space or tab; parsers remove CRLF+whitespace when unfolding.
- Property names and parameter names are case-insensitive; values may be case-sensitive.
## DURATION format
`[+/-]P[n]W` or `[+/-]P[n]DT[n]H[n]M[n]S`, e.g. `P1W`, `PT45M`, `P1DT12H`.
FILE:references/recurrence-rules.md
# Recurrence Rules (RRULE) Reference
Syntax: `RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE;UNTIL=20261231T235959Z`
## Parts
| Part | Values | Notes |
|---|---|---|
| FREQ | SECONDLY, MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY | Required, exactly once. |
| INTERVAL | positive integer | Default 1. |
| COUNT | positive integer | Number of occurrences, including the first. |
| UNTIL | DATE or DATE-TIME | Inclusive end. Must not be used with COUNT. |
| BYDAY | MO TU WE TH FR SA SU, optional ordinal (`1MO`, `-1FR`) | Ordinals only make sense with MONTHLY or YEARLY. |
| BYMONTHDAY | 1..31 or -31..-1 | `-1` = last day of month. |
| BYMONTH | 1..12 | |
| BYYEARDAY | 1..366 or negative | |
| BYWEEKNO | 1..53 or negative | YEARLY only. |
| BYHOUR / BYMINUTE / BYSECOND | 0..23 / 0..59 / 0..60 | |
| BYSETPOS | 1..366 or negative | e.g. last weekday of month: `BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1`. |
| WKST | MO..SU | Week start, default MO. |
## UNTIL type rule
- If DTSTART is a DATE, UNTIL must be a DATE.
- If DTSTART has a TZID or is UTC, UNTIL must be UTC (`...Z`).
- If DTSTART is floating, UNTIL must also be floating.
## EXDATE and RDATE
- Must use the same value type and TZID as DTSTART, otherwise clients ignore them.
- Example: `EXDATE;TZID=Europe/Istanbul:20261103T100000`
## Common bugs
1. Both COUNT and UNTIL present (invalid).
2. UNTIL as a local time when DTSTART has a TZID (should be UTC).
3. BYDAY ordinals with WEEKLY (`FREQ=WEEKLY;BYDAY=1MO`) - meaningless.
4. DTSTART that does not match the pattern (DTSTART on Tuesday with `BYDAY=MO`): RFC says DTSTART still counts as the first occurrence, which surprises users.
5. EXDATE in UTC while DTSTART uses TZID: exceptions do not match.
6. Lowercase or misspelled keys (`freq=weekly`, `BYDAYS`). Values should be uppercase.
7. Daylight saving: a recurring 09:00 meeting must use TZID, not UTC, or it shifts by an hour twice a year.
## Examples
- Every weekday at 09:00: `RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR`
- First Monday monthly, 6 times: `RRULE:FREQ=MONTHLY;BYDAY=1MO;COUNT=6`
- Last day of each month: `RRULE:FREQ=MONTHLY;BYMONTHDAY=-1`
- Every other Friday until year end: `RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=FR;UNTIL=20261231T210000Z`
FILE:templates/fix-checklist.md
# ICS Fix Checklist and Report
## Summary
- File: `<name.ics>`
- Produced by: `<app or script>` | Fails in: `<client>`
- Symptom: `<import error / wrong time / missing occurrences / duplicates>`
- Result: `<N errors, M warnings>`
## Checklist
- [ ] File starts with `BEGIN:VCALENDAR` and ends with `END:VCALENDAR`
- [ ] All BEGIN/END pairs balanced and correctly nested
- [ ] `VERSION:2.0` and `PRODID` present once in VCALENDAR
- [ ] Each VEVENT has `UID`, `DTSTAMP`, `DTSTART`
- [ ] UIDs are unique within the file and stable across updates
- [ ] DTEND later than DTSTART, and not combined with DURATION
- [ ] Date-time values valid (`YYYYMMDD` / `YYYYMMDDTHHMMSS[Z]`)
- [ ] All-day events use `VALUE=DATE` on both DTSTART and DTEND
- [ ] Every TZID used has a VTIMEZONE (or an IANA name the target client accepts)
- [ ] No unintended floating times
- [ ] RRULE has FREQ; no COUNT with UNTIL; UNTIL type matches DTSTART
- [ ] EXDATE/RDATE match DTSTART value type and TZID
- [ ] Text values escape `,` `;` `\` and newlines
- [ ] CRLF line endings; lines over 75 octets folded
- [ ] SEQUENCE increments when an existing event is updated
## Findings
| # | Severity | Line | Property | Problem | Fix |
|---|---|---|---|---|---|
| 1 | error | | | | |
## Corrected snippet
```
<fixed lines>
```
## Re-test
1. Run `python3 scripts/check_ics.py <file>` and confirm 0 errors.
2. Import into a test calendar in the failing client.
3. Check the first occurrence, one middle occurrence, and the last occurrence times.
FILE:examples/sample.ics
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Co//Team Calendar 1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VTIMEZONE
TZID:Europe/Istanbul
BEGIN:STANDARD
DTSTART:19700101T000000
TZOFFSETFROM:+0300
TZOFFSETTO:+0300
TZNAME:+03
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
UID:kickoff-20261020@example.com
DTSTAMP:20261015T070000Z
DTSTART;TZID=Europe/Istanbul:20261020T100000
DTEND;TZID=Europe/Istanbul:20261020T113000
SUMMARY:Project kickoff
LOCATION:Room 4\, Second Floor
DESCRIPTION:Agenda: goals\, roles\, and first milestones.\nBring lapt
ops.
STATUS:CONFIRMED
SEQUENCE:0
END:VEVENT
BEGIN:VEVENT
UID:weekly-sync-2026@example.com
DTSTAMP:20261015T070000Z
DTSTART;TZID=Europe/Istanbul:20261022T093000
DURATION:PT30M
RRULE:FREQ=WEEKLY;BYDAY=TH;UNTIL=20261231T063000Z
EXDATE;TZID=Europe/Istanbul:20261126T093000
SUMMARY:Weekly team sync
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Weekly team sync in 10 minutes
TRIGGER:-PT10M
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:offsite-2026@example.com
DTSTAMP:20261015T070000Z
DTSTART;VALUE=DATE:20261106
DTEND;VALUE=DATE:20261108
SUMMARY:Team offsite (all day)
END:VEVENT
END:VCALENDAR
FILE:scripts/check_ics.py
#!/usr/bin/env python3
"""Validate an iCalendar (.ics) file against RFC 5545 basics.
Usage:
python3 check_ics.py FILE.ics [--json]
python3 check_ics.py --self-test
Exit codes: 0 = no errors, 1 = errors found, 2 = usage / read error.
Python 3 standard library only.
"""
import argparse
import datetime as dt
import json
import re
import sys
DATE_RE = re.compile(r"^\d{8}$")
DATETIME_RE = re.compile(r"^\d{8}T\d{6}Z?$")
DURATION_RE = re.compile(r"^[+-]?P(\d+W|(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?)$")
FREQS = {"SECONDLY", "MINUTELY", "HOURLY", "DAILY", "WEEKLY", "MONTHLY", "YEARLY"}
DAYS = {"MO", "TU", "WE", "TH", "FR", "SA", "SU"}
RRULE_KEYS = {"FREQ", "UNTIL", "COUNT", "INTERVAL", "BYSECOND", "BYMINUTE", "BYHOUR",
"BYDAY", "BYMONTHDAY", "BYYEARDAY", "BYWEEKNO", "BYMONTH", "BYSETPOS", "WKST"}
EVENT_LIKE = {"VEVENT", "VTODO", "VJOURNAL"}
class Report:
def __init__(self):
self.items = []
def add(self, severity, line, prop, message):
self.items.append({"severity": severity, "line": line, "property": prop, "message": message})
def error(self, line, prop, msg):
self.add("error", line, prop, msg)
def warn(self, line, prop, msg):
self.add("warning", line, prop, msg)
@property
def errors(self):
return [i for i in self.items if i["severity"] == "error"]
@property
def warnings(self):
return [i for출처 · prompts.chat 공개 프롬프트 모음 · 작성 Fatih Kadir Akın — CC0(저작권 포기)로 공개된 원문을 그대로 싣습니다. 한국어 제목 · 설명은 AI 번역이며, 실행 결과는 아직 없습니다.