reopt designreopt design
DocsExploreToolsPricingBuilder
시작하기
시작하기
Playground
핵심 개념
핵심 개념
Calendar
Calendar 개요
이벤트 편집
반복 일정
타임존
예약 · 가용성
AI 스트리밍
원격 소스
앱 조합
구축·운영
스킬
릴리즈 노트
레퍼런스
Hook 레퍼런스
타입 레퍼런스
Oopt-calendar
reopt designreopt design

AI 시대를 위한 디자인 시스템

  • 문서
  • 가격
  • 릴리즈 노트
  • GitHub
  • 서비스 약관
  • 개인정보처리방침

© 2026 reopt-ai. All rights reserved.

Calendar
  1. 문서
  2. /
  3. Calendar
  4. /
  5. 타임존

타임존

TimeZoneAdapter, wall-clock↔instant 변환, DST 경계에서의 렌더/반복 정확성

reopt design · 업데이트 2026년 7월 2일

시작하기핵심 개념개요이벤트 편집반복 일정타임존예약 · 가용성AI 스트리밍원격 소스앱 조합스킬릴리즈 노트Hook 레퍼런스타입 레퍼런스

1. 시간 인코딩 3종

opt-calendar은 시간을 세 가지로 인코딩합니다. 이 분리 덕분에 native Intl만으로 모든 표시 작업을 처리하며, 레포에 별도 날짜 라이브러리가 없습니다.

  • Timed 이벤트 — 오프셋 포함 절대 ISO datetime(ISODateTime), 예 "2026-07-01T09:00:00-04:00". 특정 순간을 가리킵니다.
  • All-day 이벤트 — 타임존 없는 floating ISO date(ISODate), 예 "2026-07-13". 뷰어/spec 타임존에서 해석하며 end는 배타적입니다.
  • 가용성(availability) — wall-clock HH:mm(WallClockTime) + 스케줄의 타임존. WeeklyAvailabilityRule이 요일별 창을 정의하고 스케줄 tz에서 해석됩니다.
tsx
// 1) Timed — 오프셋 포함 절대 instant
spec.events.sync = {
  id: "sync",
  title: "주간 싱크",
  start: "2026-07-01T09:00:00-04:00",
  end: "2026-07-01T10:00:00-04:00",
};

// 2) All-day — 타임존 없는 floating date, end 배타적
spec.events.offsite = {
  id: "offsite",
  title: "오프사이트",
  start: "2026-07-13",
  end: "2026-07-15", // 7/14까지 (end 배타적)
  allDay: true,
};

// 3) Availability — wall-clock HH:mm + 스케줄 tz
spec.availability.default = {
  id: "default",
  name: "업무 시간",
  timeZone: "America/New_York",
  rules: [
    { day: 1, start: "09:00", end: "17:00" }, // 월 09:00–17:00 (스케줄 tz)
  ],
};

2. TimeZoneAdapter 계약

타임존 해석은 TimeZoneAdapter seam 뒤에 있습니다. 기본값 intlTimeZoneAdapter는 native Intl.DateTimeFormat 기반이라 무거운 의존성이 없습니다. 계약은 그리드가 필요로 하는 세 가지입니다.

  • format(iso, tz?, opts?, locale?) — 타임존에서 ISO 값의 지역화된 표시 문자열
  • getParts(instant, tz) → ZonedParts — 절대 instant의 wall-clock 파트(year / month / day / hour / minute / weekday)
  • wallClockToInstant(date, time, tz) → Date — 특정 타임존의 wall-clock date+time을 절대 instant로

DST-grade 정확성이 필요하면 같은 seam으로 Temporal polyfill 기반 어댑터를 주입할 수 있습니다(hard dependency 아님). expandEvents(spec, range, engine?, adapter?)의 4번째 인자, 그리고 event-time 헬퍼들의 마지막 인자로 넘깁니다.

tsx
import { intlTimeZoneAdapter } from "@reopt-ai/opt-calendar";

// wall-clock → 절대 instant (해당 날짜의 tz 오프셋을 반영)
const instant = intlTimeZoneAdapter.wallClockToInstant(
  "2026-07-01",
  "09:00",
  "America/New_York",
);
instant.toISOString(); // "2026-07-01T13:00:00.000Z" (EDT, UTC-4)

// 절대 instant → 특정 tz의 wall-clock 파트
const parts = intlTimeZoneAdapter.getParts(instant, "America/New_York");
parts.hour; // 9
parts.minute; // 0
parts.weekday; // 3 (수요일)

3. Calendar timeZone prop 정렬

<Calendar timeZone="..." />가 넘기는 표시 타임존은 세 가지를 한 기준으로 정렬합니다.

  • 뷰 위치 계산 — timed 이벤트가 day/week 그리드의 어느 줄에 그려질지(getEventDayMinutes가 tz에서 wall-clock 해석)
  • 반복 전개 — occurrence를 master tz의 wall-clock으로 materialize
  • 드래그 수식 — 이동·리사이즈를 wall-clock 분으로 계산해 다시 절대 instant로(computeEventMove / computeEventResize)

같은 절대 instant라도 표시 타임존이 다르면 다른 줄에 그려집니다. event-time 헬퍼는 tz를 주면 어댑터로 wall-clock을 해석하고, 안 주면 문자열에서 textual하게 읽는 same-tz fast path로 폴백합니다.

tsx
import {
  getEventDayMinutes,
  computeEventMove,
} from "@reopt-ai/opt-calendar";

const event = {
  id: "sync",
  title: "싱크",
  start: "2026-07-01T20:00:00Z",
  end: "2026-07-01T21:00:00Z",
};

// 표시 tz에서 그리드 위치(분) 해석 — 같은 instant, 다른 tz → 다른 날/줄
getEventDayMinutes(event, "America/New_York");
// → { dayKey: "2026-07-01", startMin: 960, endMin: 1020 } (16:00)
getEventDayMinutes(event, "Asia/Seoul");
// → { dayKey: "2026-07-02", startMin: 300, endMin: 360 } (05:00 다음날)

// 드래그: +30분 이동을 wall-clock으로 계산 후 절대 instant로 환산
computeEventMove(event, 30, "America/New_York");
// → { start: "2026-07-01T20:30:00.000Z", end: "2026-07-01T21:30:00.000Z" }

4. DST 정확성

반복 occurrence는 master 타임존의 절대 instant로 materialize됩니다. expandEvents는 master의 wall-clock HH:mm를 뽑아, 각 occurrence 날짜마다 wallClockToInstant로 그 날짜의 오프셋을 다시 계산합니다. 그래서 "매일 09:00" 시리즈는 DST 경계를 지나도 9시를 유지합니다 — 바뀌는 것은 wall-clock이 아니라 저장되는 오프셋입니다.

tsx
import { expandEvents } from "@reopt-ai/opt-calendar";

// master: America/New_York, 매일 09:00
spec.timeZone = "America/New_York";
spec.events.standup = {
  id: "standup",
  title: "데일리 스탠드업",
  start: "2026-03-07T09:00:00-05:00", // EST (UTC-5)
  end: "2026-03-07T09:15:00-05:00",
  rrule: "FREQ=DAILY",
};

// DST 봄 전환(미국 2026-03-08)을 가로질러 전개
const instances = expandEvents(spec, {
  start: new Date("2026-03-07"),
  end: new Date("2026-03-10"),
});

// wall-clock 09:00은 유지되고, 절대 오프셋만 -05:00 → -04:00으로 이동
instances[0].start; // "2026-03-07T14:00:00.000Z" (09:00 EST)
instances[1].start; // "2026-03-08T13:00:00.000Z" (09:00 EDT — 오프셋만 이동)
instances[2].start; // "2026-03-09T13:00:00.000Z" (09:00 EDT)

canonical occurrenceStart 키는 wall-clock을 textual하게 보존하므로 EXDATE·override 매칭에 안정적으로 쓰이고, materialize된 start/end만 날짜별 오프셋을 반영합니다. 기본 intlTimeZoneAdapter로도 위 결과가 정확하며, 더 엄격한 DST 처리가 필요하면 Temporal 어댑터를 주입하세요.

5. 다음 단계

반복 일정

RRULE, occurrence, this/following/all 편집

Hook 레퍼런스

useCalendar*, navigation, remote, suggestion

Previous반복 일정RRULE 전개, occurrence 모델, this/following/all 인스턴스 단위 편집 시퀀스Calendar
반복 일정 페이지로 이동
Next예약 · 가용성EventType/AvailabilitySchedule/BookingSlot과 confirm→materialize 라이프사이클Calendar