Guide

반복 캘린더 일정: RRULE 완전 정리

주간 스탠드업은 데이터베이스에는 일정 하나, 화면에는 52개로 나타납니다. 모든 캘린더 시스템은 이 간극을 반복 규칙 — RFC 5545의 RRULE 문자열 — 으로 처리하며, 모든 연동은 결국 같은 질문에 부딪힙니다: 규칙 문법이 무엇을 의미하는지, 누가 시리즈를 인스턴스로 확장하는지, 발생 하나가 이동하면 어떻게 되는지. 이 가이드는 Google, Microsoft Graph, CalDAV의 세부 사항을 나란히 비교하며 세 가지 질문에 모두 답합니다.

Written by Hazik Director of Product Management

VerifiedCLI 3.1.16 · last tested June 6, 2026

이 가이드에서 사용하는 명령어 레퍼런스: nylas calendar events list nylas calendar events show.

RRULE이란 무엇인가요?

RRULE은 RFC 5545(iCalendar 사양)에 정의된 반복 규칙 문법으로, 반복되는 스케줄을 문자열 하나로 기술합니다. 규칙 하나가 저장된 수백 개의 행을 대체합니다: 일정은 한 번만 존재하고, 소프트웨어가 발생을 계산해 냅니다. 3대 주요 캘린더 시스템은 모두 RRULE을 직접 저장하거나 경계에서 RRULE과 상호 변환합니다.

이 문법에는 필수 파트인 FREQ 하나와 여러 수정자가 있습니다. 실제로 사용하게 될 6가지는 아래와 같습니다 — 그리고 그 무엇보다 중요한 RFC의 제약이 하나 있습니다: 사양은 UNTILCOUNT 파트가 "동일한 'recur' 안에 함께 나타나서는 안 된다(MUST NOT)"고 명시합니다. 규칙이 끝나는 방식은 정확히 3가지뿐입니다: 영원히 계속, N회 발생 후 종료, 특정 날짜에 종료.

파트의미예시
FREQ기본 주기 (필수)FREQ=WEEKLY
INTERVALN번째 주기마다 (기본값 1)INTERVAL=2 — 격주
BYDAY요일 선택자; 서수 지원BYDAY=MO,WE,FR 또는 BYDAY=-1FR (마지막 금요일)
BYMONTHDAY월중 날짜 선택자BYMONTHDAY=15
COUNTN회 발생 후 종료COUNT=12
UNTILUTC 타임스탬프에 종료UNTIL=20261231T235959Z
# Every Monday and Wednesday for 12 occurrences
RRULE:FREQ=WEEKLY;BYDAY=MO,WE;COUNT=12

# Biweekly Friday, forever
RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=FR

# Last Friday of each month through 2026
RRULE:FREQ=MONTHLY;BYDAY=-1FR;UNTIL=20261231T235959Z

시리즈를 인스턴스로 확장하는 주체는 누구인가요?

규칙 하나를 날짜가 지정된 발생으로 바꾸는 확장 작업은 API마다 다른 위치에서 일어나며, 이 위치가 작성해야 할 반복 처리 코드의 양을 결정합니다. Google은 요청 시 서버 측에서 확장하고, Graph는 구조화된 객체를 저장하며 instances 뷰를 제공하고, CalDAV는 원시 규칙을 그대로 전달하여 계산을 클라이언트에 맡깁니다. 3가지 모델을 간단히 정리하면:

  • Google Calendar API events.listsingleEvents=true를 전달하면 응답에 요청한 시간 범위 내의 개별 인스턴스가 포함되며, 각 인스턴스에는 recurringEventId가 있습니다. Google의 반복 일정 가이드에서 마스터와 인스턴스 모델을 다룹니다.
  • Microsoft Graph — 일정은 원시 RRULE 문자열 대신 pattern(주기)과 range(범위)로 구성된 patternedRecurrence 객체를 저장합니다. 인스턴스 확장은 시리즈 마스터의 instances 뷰에서 제공되며, 시작 및 종료 매개변수로 범위를 지정합니다.
  • CalDAV — 서버는 RRULE이 포함된 VEVENT를 그대로 반환하고, 클라이언트가 시간대 계산까지 포함하여 직접 확장합니다. 도움 없이 이를 올바르게 구현하는 것이 가장 어려운 길입니다. RFC 5545의 반복 관련 섹션이 수십 페이지에 달하는 데는 이유가 있습니다.

실용적인 규칙은 이렇습니다: 연동에 "이번 주에 무슨 일정이 있는지"만 필요하다면, 확장을 대신해 주는 API(또는 도구)를 선택하세요. 올바른 클라이언트 측 확장기를 작성하는 것은 함수 하나가 아니라 프로젝트입니다. 이 3가지 API가 반복 외의 영역(인증, 가용 시간, 웹훅)에서 어떻게 다른지는 캘린더 API 비교를 참고하세요. 이 페이지는 반복 문제에만 집중합니다.

발생 하나가 이동하거나 취소되면 어떻게 되나요?

수정된 단일 발생은 예외(exception)가 됩니다: 나머지 시리즈는 마스터를 따르는 동안 특정 날짜 하나에 대해서만 규칙을 재정의하는 독립 레코드입니다. 시스템마다 인코딩 방식이 다릅니다 — Google은 originalStartTime 필드가 있는 별도의 이벤트를 생성하고, Graph는 exception 발생 유형으로 모델링하며, iCalendar는 이동된 인스턴스와 일치하는 RECURRENCE-ID를 가진 두 번째 VEVENT를 추가합니다.

예외는 단순한 동기화 코드가 깨지는 지점입니다. 대부분의 버그는 두 가지 실패 패턴에서 나옵니다: 예외를 완전히 새로운 일정으로 취급하는 경우(규칙 확장에서 한 번, 재정의에서 한 번 — 회의가 두 번 나타남), 그리고 예외 위에 시리즈 편집을 덮어쓰는 경우(사용자의 일회성 회의실 변경이 조용히 되돌아감). 동기화 로직은 둘 중 어느 경로를 적용하기 전에 예외 마커부터 확인해야 합니다 — 중복 회의 지원 티켓보다 훨씬 저렴한 2분기 검사입니다.

CLI에서 반복 일정을 어떻게 읽나요?

nylas calendar events list 명령은 요청한 범위 내의 확장된 인스턴스를 반환하므로, 주간 시리즈가 별도의 RRULE 파싱 없이 날짜가 지정된 발생으로 나타납니다. 반복 시리즈의 각 인스턴스에는 시리즈 마스터로 연결되는 master_event_id가 있으며, 인스턴스 자체의 ID에는 발생 타임스탬프가 포함되어 있습니다 — 둘 다 JSON 출력에서 확인할 수 있습니다.

# Expanded instances for the next 30 days
nylas calendar events list --days 30 --json | jq '.[] | {
  id: .id,
  title: .title,
  master: .master_event_id,
  start: .when.start_time
}'

# Group instances by series to spot every recurring meeting
nylas calendar events list --days 30 --json | jq '
  [.[] | select(.master_event_id != null)]
  | group_by(.master_event_id)
  | map({series: .[0].title, occurrences: length})'

같은 2개의 명령이 Google, Microsoft, iCloud 계정에서 동일하게 작동하므로, 읽기 경로에서는 앞 섹션의 API별 확장 차이가 사라집니다. 한 가지 솔직한 제한 사항: CLI의 calendar events create 명령으로 일정을 생성할 때는 반복 플래그를 지원하지 않습니다 — 시리즈 생성은 공급자의 UI나 API에서 하고, CLI는 읽기, 필터링, 스크립팅을 담당합니다.

반복 일정이 서머타임 전후에 밀리는 이유는 무엇인가요?

매주 9:00 회의는 고정된 UTC 오프셋이 아니라 명명된 시간대 기준의 9:00입니다 — 따라서 1년에 두 번, UTC 기준 시각이 1시간씩 이동합니다. 원시 UTC 기준으로 확장된 규칙은 서머타임(DST) 전환 때마다 어긋나며, 올바른 확장은 각 발생을 시간대 데이터베이스(UTC-5가 아니라 America/Toronto)를 통해 해석합니다. 클라이언트 측 CalDAV 확장이 어려운 이유의 대부분이 여기에 있으며, 일정 시작 시각은 시간대 참조를 갖는 반면 UNTIL 타임스탬프는 UTC여야 한다는 RFC 5545의 요구 사항에도 이 내용이 담겨 있습니다.

3월이나 11월에 나타나는 동기화 버그를 조사할 때는 무엇보다 먼저 확장기가 어떤 시계를 사용했는지 확인하세요. 위의 인스턴스 JSON에는 기간형 일정에 start_timezone이 포함되므로, 예상과 다른 시간대를 가진 인스턴스를 찾는 한 줄짜리 jq 필터로 어긋난 시리즈를 빠르게 찾을 수 있습니다.

다음 단계