> test EXDATE
DTSTART:20230101T130000Z
RRULE:FREQ=MONTHLY
EXDATE:20230801T130000Z
> convert
"start": "2023-01-01T13:00:00",
"timeZone": "Etc/UTC",
"recurrenceRule": {
  "frequency": "monthly"
},
"recurrenceOverrides": {
  "2023-08-01T13:00:00": {
    "excluded": true
  }
}

> test RDATE
DTSTART:20230101T130000Z
RRULE:FREQ=MONTHLY
RDATE:20230805T170000Z
> convert
"start": "2023-01-01T13:00:00",
"timeZone": "Etc/UTC",
"recurrenceRule": {
  "frequency": "monthly"
},
"recurrenceOverrides": {
  "2023-08-05T17:00:00": {}
}

> test RDATE with value type PERIOD
DTSTART:20230101T130000Z
DURATION:PT1H
RRULE:FREQ=MONTHLY
RDATE;VALUE=PERIOD:20231223T150000Z/PT2H
> convert
"start": "2023-01-01T13:00:00",
"duration": "PT1H",
"timeZone": "Etc/UTC",
"recurrenceRule": {
  "frequency": "monthly"
},
"recurrenceOverrides": {
  "2023-12-23T15:00:00": {
    "duration": "PT2H"
  }
},
"iCalendar": {
  "convertedProperties": {
    "recurrenceOverrides/2023-12-23T15:00:00": {
      "name": "rdate",
      "parameters": {
        "value": "PERIOD"
      }
    }
  },
  "name": "vevent"
}

> test RDATE with multiple PERIOD values
DTSTART:20230101T130000Z
DURATION:PT1H
RDATE;VALUE=PERIOD:20231223T150000Z/PT2H,20231224T150000Z/PT3H
> convert
"start": "2023-01-01T13:00:00",
"duration": "PT1H",
"timeZone": "Etc/UTC",
"recurrenceOverrides": {
  "2023-12-23T15:00:00": {
    "duration": "PT2H"
  },
  "2023-12-24T15:00:00": {
    "duration": "PT3H"
  }
},
"iCalendar": {
  "convertedProperties": {
    "recurrenceOverrides/2023-12-23T15:00:00": {
      "name": "rdate",
      "parameters": {
        "value": "PERIOD"
      }
    },
    "recurrenceOverrides/2023-12-24T15:00:00": {
      "name": "rdate",
      "parameters": {
        "value": "PERIOD"
      }
    }
  },
  "name": "vevent"
}
> convert
DTSTART:20230101T130000Z
DURATION:PT1H
RDATE;VALUE=PERIOD:20231223T150000Z/PT2H
RDATE;VALUE=PERIOD:20231224T150000Z/PT3H

> test RDATE with value type PERIOD and explicit end
DTSTART:20230101T130000Z
DURATION:PT1H
RDATE;VALUE=PERIOD:20231223T150000Z/20231223T170000Z
> convert
"start": "2023-01-01T13:00:00",
"duration": "PT1H",
"timeZone": "Etc/UTC",
"recurrenceOverrides": {
  "2023-12-23T15:00:00": {
    "duration": "PT2H"
  }
},
"iCalendar": {
  "convertedProperties": {
    "recurrenceOverrides/2023-12-23T15:00:00": {
      "name": "rdate",
      "parameters": {
        "value": "PERIOD"
      }
    }
  },
  "name": "vevent"
}
> convert
DTSTART:20230101T130000Z
DURATION:PT1H
RDATE;VALUE=PERIOD:20231223T150000Z/PT2H

> test RRULE
DTSTART;TZID=Europe/Berlin:20240101T010000
RRULE:FREQ=YEARLY
 ;INTERVAL=2
 ;BYMONTH=1
 ;BYDAY=SU
 ;BYHOUR=8,9
 ;BYMINUTE=30
 ;UNTIL=20240930T120000Z
> convert
"recurrenceRule": {
  "frequency": "yearly",
  "interval": 2,
  "byMonth": [
    "1"
  ],
  "byDay": [
    {
      "day": "su"
    }
  ],
  "byHour": [
    8,
    9
  ],
  "byMinute": [
    30
  ],
  "until": "2024-09-30T14:00:00"
},
"start": "2024-01-01T01:00:00",
"timeZone": "Europe/Berlin"

> test VEVENT recurrence overrides
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:F4257E1D-5461-4EF6-840F-9DFC653EB559
RECURRENCE-ID;TZID=Europe/Berlin:20240202T140000
DTSTART;TZID=Europe/Berlin:20240202T160000
END:VEVENT
BEGIN:VEVENT
UID:F4257E1D-5461-4EF6-840F-9DFC653EB559
RRULE:FREQ=DAILY
DTSTART;TZID=Europe/Berlin:20240101T140000
END:VEVENT
END:VCALENDAR
> convert
{
  "@type": "Group",
  "entries": [
    {
      "@type": "Event",
      "recurrenceOverrides": {
        "2024-02-02T14:00:00": {
          "start": "2024-02-02T16:00:00"
        }
      },
      "recurrenceRule": {
        "frequency": "daily"
      },
      "start": "2024-01-01T14:00:00",
      "timeZone": "Europe/Berlin",
      "uid": "F4257E1D-5461-4EF6-840F-9DFC653EB559"
    }
  ]
}

> test VEVENT recurrence instances
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:F4257E1D-5461-4EF6-840F-9DFC653EB559
RECURRENCE-ID;TZID=Europe/Berlin:20240202T140000
DTSTART;TZID=Europe/Berlin:20240202T160000
END:VEVENT
BEGIN:VEVENT
UID:F4257E1D-5461-4EF6-840F-9DFC653EB559
RECURRENCE-ID;TZID=Europe/Berlin:20240103T140000
DTSTART;TZID=Europe/Berlin:20240103T170000
END:VEVENT
END:VCALENDAR
> convert
{
  "@type": "Group",
  "entries": [
    {
      "@type": "Event",
      "recurrenceId": "2024-02-02T14:00:00",
      "recurrenceIdTimeZone": "Europe/Berlin",
      "start": "2024-02-02T16:00:00",
      "timeZone": "Europe/Berlin",
      "uid": "F4257E1D-5461-4EF6-840F-9DFC653EB559"
    },
    {
      "@type": "Event",
      "recurrenceId": "2024-01-03T14:00:00",
      "recurrenceIdTimeZone": "Europe/Berlin",
      "start": "2024-01-03T17:00:00",
      "timeZone": "Europe/Berlin",
      "uid": "F4257E1D-5461-4EF6-840F-9DFC653EB559"
    }
  ]
}

> test VEVENT recurrence override with forbidden patches
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:9C3F2A8E-0B7D-4E51-A6C2-3D5E8F1B2A47
RECURRENCE-ID;TZID=Europe/Berlin:20240202T140000
DTSTART;TZID=Europe/Berlin:20240202T160000
ORGANIZER:mailto:organizer@example.com
CLASS:CONFIDENTIAL
RRULE:FREQ=WEEKLY
END:VEVENT
BEGIN:VEVENT
UID:9C3F2A8E-0B7D-4E51-A6C2-3D5E8F1B2A47
RRULE:FREQ=DAILY
DTSTART;TZID=Europe/Berlin:20240101T140000
ORGANIZER:mailto:organizer@example.com
CLASS:PUBLIC
END:VEVENT
END:VCALENDAR
> convert
{
  "@type": "Group",
  "entries": [
    {
      "@type": "Event",
      "organizerCalendarAddress": "mailto:organizer@example.com",
      "participants": {
        "251d3e9f-d83f-534c-8c45-c2896c75670c": {
          "@type": "Participant",
          "calendarAddress": "mailto:organizer@example.com",
          "roles": {
            "owner": true
          }
        }
      },
      "privacy": "secret",
      "recurrenceOverrides": {
        "2024-02-02T14:00:00": {
          "participants": {
            "251d3e9f-d83f-534c-8c45-c2896c75670c": {
              "@type": "Participant",
              "calendarAddress": "mailto:organizer@example.com",
              "roles": {
                "owner": true
              }
            }
          },
          "start": "2024-02-02T16:00:00"
        }
      },
      "recurrenceRule": {
        "frequency": "daily"
      },
      "start": "2024-01-01T14:00:00",
      "timeZone": "Europe/Berlin",
      "uid": "9C3F2A8E-0B7D-4E51-A6C2-3D5E8F1B2A47"
    }
  ]
}
> convert
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:9C3F2A8E-0B7D-4E51-A6C2-3D5E8F1B2A47
RECURRENCE-ID;TZID=Europe/Berlin:20240202T140000
DTSTART;TZID=Europe/Berlin:20240202T160000
ORGANIZER:mailto:organizer@example.com
CLASS:CONFIDENTIAL
END:VEVENT
BEGIN:VEVENT
UID:9C3F2A8E-0B7D-4E51-A6C2-3D5E8F1B2A47
RRULE:FREQ=DAILY
DTSTART;TZID=Europe/Berlin:20240101T140000
ORGANIZER:mailto:organizer@example.com
CLASS:CONFIDENTIAL
END:VEVENT
END:VCALENDAR

> test VEVENT recurrence instances with different CLASS
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:5E1D7C42-8A3B-4F6E-9B20-C4D1A7E3F865
RECURRENCE-ID;TZID=Europe/Berlin:20240202T140000
DTSTART;TZID=Europe/Berlin:20240202T160000
CLASS:PRIVATE
END:VEVENT
BEGIN:VEVENT
UID:5E1D7C42-8A3B-4F6E-9B20-C4D1A7E3F865
RECURRENCE-ID;TZID=Europe/Berlin:20240103T140000
DTSTART;TZID=Europe/Berlin:20240103T170000
END:VEVENT
END:VCALENDAR
> convert
{
  "@type": "Group",
  "entries": [
    {
      "@type": "Event",
      "privacy": "private",
      "recurrenceId": "2024-02-02T14:00:00",
      "recurrenceIdTimeZone": "Europe/Berlin",
      "start": "2024-02-02T16:00:00",
      "timeZone": "Europe/Berlin",
      "uid": "5E1D7C42-8A3B-4F6E-9B20-C4D1A7E3F865"
    },
    {
      "@type": "Event",
      "privacy": "private",
      "recurrenceId": "2024-01-03T14:00:00",
      "recurrenceIdTimeZone": "Europe/Berlin",
      "start": "2024-01-03T17:00:00",
      "timeZone": "Europe/Berlin",
      "uid": "5E1D7C42-8A3B-4F6E-9B20-C4D1A7E3F865"
    }
  ]
}
> convert
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:5E1D7C42-8A3B-4F6E-9B20-C4D1A7E3F865
RECURRENCE-ID;TZID=Europe/Berlin:20240202T140000
DTSTART;TZID=Europe/Berlin:20240202T160000
CLASS:PRIVATE
END:VEVENT
BEGIN:VEVENT
UID:5E1D7C42-8A3B-4F6E-9B20-C4D1A7E3F865
RECURRENCE-ID;TZID=Europe/Berlin:20240103T140000
DTSTART;TZID=Europe/Berlin:20240103T170000
CLASS:PRIVATE
END:VEVENT
END:VCALENDAR

> test JSCalendar - privacy and forbidden patches in recurrence overrides
{
  "@type": "Event",
  "uid": "A7B2C9D4-1E6F-4A8B-B3C5-D2E7F9A1B6C8",
  "title": "Board meeting",
  "start": "2024-01-01T14:00:00",
  "timeZone": "Europe/Berlin",
  "privacy": "secret",
  "recurrenceRule": {
    "frequency": "daily"
  },
  "recurrenceOverrides": {
    "2024-01-05T14:00:00": {
      "title": "Board meeting (moved)",
      "privacy": "public",
      "uid": "other",
      "recurrenceRule/frequency": "weekly"
    }
  }
}
> convert
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:A7B2C9D4-1E6F-4A8B-B3C5-D2E7F9A1B6C8
RECURRENCE-ID;TZID=Europe/Berlin:20240105T140000
SUMMARY:Board meeting (moved)
CLASS:CONFIDENTIAL
END:VEVENT
BEGIN:VEVENT
UID:A7B2C9D4-1E6F-4A8B-B3C5-D2E7F9A1B6C8
SUMMARY:Board meeting
DTSTART;TZID=Europe/Berlin:20240101T140000
CLASS:CONFIDENTIAL
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
> convert
{
  "@type": "Event",
  "uid": "A7B2C9D4-1E6F-4A8B-B3C5-D2E7F9A1B6C8",
  "title": "Board meeting",
  "start": "2024-01-01T14:00:00",
  "timeZone": "Europe/Berlin",
  "privacy": "secret",
  "recurrenceRule": {
    "frequency": "daily"
  },
  "recurrenceOverrides": {
    "2024-01-05T14:00:00": {
      "title": "Board meeting (moved)"
    }
  }
}

> test VEVENT recurrence override raises vendor privacy
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:B41E6D2A-7C9F-4A35-8E1B-2D6F9A4C7E50
RRULE:FREQ=DAILY
DTSTART;TZID=Europe/Berlin:20240101T140000
JSPROP;JSPTR=privacy:"example.com:hidden"
END:VEVENT
BEGIN:VEVENT
UID:B41E6D2A-7C9F-4A35-8E1B-2D6F9A4C7E50
RECURRENCE-ID;TZID=Europe/Berlin:20240202T140000
DTSTART;TZID=Europe/Berlin:20240202T160000
CLASS:CONFIDENTIAL
END:VEVENT
END:VCALENDAR
> convert
{
  "@type": "Group",
  "entries": [
    {
      "@type": "Event",
      "privacy": "secret",
      "recurrenceOverrides": {
        "2024-02-02T14:00:00": {
          "start": "2024-02-02T16:00:00"
        }
      },
      "recurrenceRule": {
        "frequency": "daily"
      },
      "start": "2024-01-01T14:00:00",
      "timeZone": "Europe/Berlin",
      "uid": "B41E6D2A-7C9F-4A35-8E1B-2D6F9A4C7E50"
    }
  ]
}
> convert
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:B41E6D2A-7C9F-4A35-8E1B-2D6F9A4C7E50
RECURRENCE-ID;TZID=Europe/Berlin:20240202T140000
DTSTART;TZID=Europe/Berlin:20240202T160000
CLASS:CONFIDENTIAL
END:VEVENT
BEGIN:VEVENT
UID:B41E6D2A-7C9F-4A35-8E1B-2D6F9A4C7E50
RRULE:FREQ=DAILY
DTSTART;TZID=Europe/Berlin:20240101T140000
CLASS:CONFIDENTIAL
END:VEVENT
END:VCALENDAR

> test JSCalendar - organizer is not copied into sparse recurrence overrides
{
  "@type": "Event",
  "uid": "3F8E1B6A-2C4D-4B7E-9A1F-6D2C8E5B7A93",
  "title": "Planning",
  "start": "2024-01-01T14:00:00",
  "timeZone": "Europe/Berlin",
  "organizerCalendarAddress": "mailto:alice@example.com",
  "participants": {
    "alice": {
      "@type": "Participant",
      "name": "Alice",
      "calendarAddress": "mailto:alice@example.com",
      "roles": {
        "owner": true
      }
    },
    "bob": {
      "@type": "Participant",
      "calendarAddress": "mailto:bob@example.com"
    }
  },
  "recurrenceRule": {
    "frequency": "daily"
  },
  "recurrenceOverrides": {
    "2024-01-05T14:00:00": {
      "title": "Planning (moved)"
    }
  }
}
> convert
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:3F8E1B6A-2C4D-4B7E-9A1F-6D2C8E5B7A93
RECURRENCE-ID;TZID=Europe/Berlin:20240105T140000
SUMMARY:Planning (moved)
END:VEVENT
BEGIN:VEVENT
UID:3F8E1B6A-2C4D-4B7E-9A1F-6D2C8E5B7A93
SUMMARY:Planning
DTSTART;TZID=Europe/Berlin:20240101T140000
ORGANIZER;CN=Alice;JSID=alice:mailto:alice@example.com
ATTENDEE;JSID=bob:mailto:bob@example.com
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR

> test JSCalendar - recurrence override with only a forbidden privacy patch
{
  "@type": "Event",
  "uid": "E2C7A9B4-6F1D-4E83-B5A2-9C4D7F1E3B68",
  "title": "Review",
  "start": "2024-01-01T14:00:00",
  "timeZone": "Europe/Berlin",
  "recurrenceRule": {
    "frequency": "daily"
  },
  "recurrenceOverrides": {
    "2024-01-05T14:00:00": {
      "privacy": "secret"
    }
  }
}
> convert
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:E2C7A9B4-6F1D-4E83-B5A2-9C4D7F1E3B68
SUMMARY:Review
DTSTART;TZID=Europe/Berlin:20240101T140000
CLASS:CONFIDENTIAL
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
> convert
{
  "@type": "Event",
  "uid": "E2C7A9B4-6F1D-4E83-B5A2-9C4D7F1E3B68",
  "title": "Review",
  "start": "2024-01-01T14:00:00",
  "timeZone": "Europe/Berlin",
  "privacy": "secret",
  "recurrenceRule": {
    "frequency": "daily"
  }
}

> test JSCalendar - RRULE with RSCALE and SKIP
{
  "@type": "Event",
  "uid": "4bd6ba4a-1c40-4dc0-a4f8-c1c4d3a97a2e",
  "start": "2026-07-16T10:00:00",
  "timeZone": "Etc/UTC",
  "duration": "PT1H",
  "recurrenceRule": {
    "frequency": "monthly",
    "rscale": "gregorian",
    "skip": "omit"
  }
}
> convert
BEGIN:VEVENT
DTSTART:20260716T100000Z
DURATION:PT1H
UID:4bd6ba4a-1c40-4dc0-a4f8-c1c4d3a97a2e
RRULE:FREQ=MONTHLY;RSCALE=GREGORIAN;SKIP=OMIT
END:VEVENT
END:VCALENDAR

> test RRULE with SKIP and no RSCALE
DTSTART:20230101T130000Z
RRULE:FREQ=MONTHLY;SKIP=OMIT
> convert
"start": "2023-01-01T13:00:00",
"timeZone": "Etc/UTC",
"recurrenceRule": {
  "frequency": "monthly",
  "skip": "omit"
}
