레퍼런스

근무일 달력 헬퍼

내보내는 달력 API예요: `createWorkingCalendar`, `CALENDAR_DAYS`, `WorkingCalendar`

WorkingCalendar는 날짜 계산이 무엇을 하루로 셀지 정하는 객체예요. 기본값인 CALENDAR_DAYS는 7일을 모두 세요. 그래서 이 달력을 거친 계산은 평범한 달력 계산과 같아요. 근무일 달력을 켜면 차트가 어떻게 달라지는지는 근무일 달력에서 다뤄요.

import {
  CALENDAR_DAYS,
  createWorkingCalendar,
  type WorkingCalendar,
  type WorkingCalendarOptions,
} from '@jaeungkim/gantt-chart';

이 메서드들이 주고받는 Dayjs 값은 패키지의 런타임 의존성인 dayjs에서 와요. @jaeungkim/gantt-chart는 dayjs를 다시 내보내지 않아요. dayjs에서 직접 가져와 UTC 모드로 넘기세요.

createWorkingCalendar

src/core/calendar.ts
/** 주말과 휴일을 건너뛰는 달력이에요. */
export function createWorkingCalendar(
  options: WorkingCalendarOptions = {}
): WorkingCalendar
매개변수타입필수의미
optionsWorkingCalendarOptions아니요기본값은 {}예요. 휴일이 없고 월요일부터 금요일까지가 근무일이에요

이 함수는 차트가 비근무일을 음영 처리할 때 쓰는 정의를 그대로 받아요. 차트의 holidays prop은 객체와 날짜 범위도 받아서 이 함수가 받는 YYYY-MM-DD 문자열로 펼쳐요.

반환된 달력은 언제나 skipsNonWorkingDays: true예요. 모든 날이 근무일로 남는 설정이어도 마찬가지예요.

WorkingCalendarOptions

src/core/calendar.ts
export interface WorkingCalendarOptions {
  /** 근무 요일을 UTC 요일 번호로. 0은 일요일이에요 (기본값 월~금) */
  workingWeekdays?: number[];
  /** 비근무일을 UTC `YYYY-MM-DD` 문자열로 */
  holidays?: string[];
}
옵션타입기본값의미
workingWeekdaysnumber[][1, 2, 3, 4, 5] (월요일부터 금요일까지)UTC 요일 번호예요. 0은 일요일이고 배열에 없는 날은 비근무일이에요
holidaysstring[][]UTC YYYY-MM-DD 문자열이에요. date.format('YYYY-MM-DD') 결과와 문자열이 정확히 같을 때만 걸려요

CALENDAR_DAYS

src/core/calendar.ts
/** 기본 달력이에요. 모든 날을 세니 이 달력을 거친 날짜 계산은 평범한 달력 계산이에요. */
export const CALENDAR_DAYS: WorkingCalendar

CALENDAR_DAYS는 공유되는 인스턴스 하나예요. workingCalendar가 꺼져 있는 동안 차트가 쓰는 달력이 이거예요. 직접 만든 코드가 달력 인자를 받고 호출부가 평범한 달력 날짜를 원할 때 넘기세요.

WorkingCalendar

src/core/calendar.ts
/** 비근무일을 건너뛸 수 있는 날짜 계산이에요. */
export interface WorkingCalendar {
  /** 기본 달력에서는 false예요 (모든 날을 세요) */
  readonly skipsNonWorkingDays: boolean;
  isWorkingDay(date: Dayjs): boolean;
  /** 비근무일을 건너뛰며 `days`만큼 앞으로(또는 뒤로) 옮겨요 */
  addDays(date: Dayjs, days: number): Dayjs;
  /** `from`에서 `to`까지의 일수예요. `addDays`가 움직이는 방식대로 세고, 부호가 있어요 */
  daysBetween(from: Dayjs, to: Dayjs): number;
  /** 근무일이면 그 날짜 그대로, 아니면 다음 근무일이에요 (시각은 유지) */
  snapForward(date: Dayjs): Dayjs;
}

코어에서 하루의 단위는 이 객체 하나가 정해요. 근무일 달력을 켜면 이 객체만 바뀌고 나머지는 그대로예요. skipsNonWorkingDays를 보면 하루씩 확인하지 않고 평범한 달력 계산 경로를 쓸 수 있어요.

멤버

멤버시그니처CALENDAR_DAYS근무일 달력
skipsNonWorkingDaysreadonly booleanfalsetrue
isWorkingDay(date: Dayjs) => boolean항상 true요일과 휴일 검사를 통과하면 true
addDays(date: Dayjs, days: number) => Dayjsdate.add(days, 'day')달력 하루씩 옮기면서 근무일에서만 남은 일수를 줄여요. 시각은 유지돼요
daysBetween(from: Dayjs, to: Dayjs) => numberto.startOf('day').diff(from.startOf('day'), 'day')하루씩 걸으며 지나간 근무일 수만 세어요. 출발한 날은 세지 않아요
snapForward(date: Dayjs) => Dayjsdate를 그대로 반환해요date 당일이나 그 뒤의 첫 근무일이고 시각은 유지돼요

daysBetween은 날짜 단위로 비교해요. 양끝에 startOf('day')를 부르니 같은 날짜의 두 시각은 0만큼 떨어져 있어요.

근무일에서 출발하면 addDays와 daysBetween은 정확히 서로의 역이라 daysBetween(from, addDays(from, n))이 n이에요. 비근무일에서 뒤로 움직이면 왕복하는 동안 한 걸음을 잃어요. daysBetween이 출발한 날을 세지 않기 때문이에요. 이 항등식에는 걷기 한계 안의 구간과 근무일이 하나 이상인 달력도 필요해요. 두 한계는 아래 제약에 있어요.

계산 결과표

2025년 6월 기준이에요. 2일은 월요일, 6일은 금요일, 7일과 8일은 주말, 9일은 그다음 월요일이에요. workweek은 createWorkingCalendar()이고 withHoliday는 createWorkingCalendar({ holidays: ['2025-06-09'] })예요. 날짜는 짧게 문자열로 적었어요. 각각 dayjs.utc(...)를 뜻해요.

호출결과
workweek.addDays('2025-06-06', 1)2025-06-09
workweek.addDays('2025-06-02', 10)2025-06-16
workweek.addDays('2025-06-09', -1)2025-06-06
workweek.addDays('2025-06-06T14:30', 1)2025-06-09T14:30
workweek.daysBetween('2025-06-02', '2025-06-09')5
workweek.daysBetween('2025-06-06', '2025-06-08')0 (금요일에서 일요일)
workweek.daysBetween('2025-06-07', '2025-06-09')1 (토요일에서 출발)
workweek.daysBetween('2025-06-09', '2025-06-02')-5
workweek.snapForward('2025-06-07T09:00')2025-06-09T09:00
withHoliday.addDays('2025-06-06', 1)2025-06-10
withHoliday.daysBetween('2025-06-06', '2025-06-13')4
CALENDAR_DAYS.daysBetween('2025-06-02', '2025-06-09')7

예제

import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { createWorkingCalendar } from '@jaeungkim/gantt-chart';

dayjs.extend(utc);

const calendar = createWorkingCalendar({ holidays: ['2025-06-09'] });
calendar.addDays(dayjs.utc('2025-06-06'), 1).toISOString(); // 2025-06-10T00:00:00.000Z

패키지에서 달력 인자를 받는 것은 하나도 없어요. 차트는 workingCalendar, workingWeekdays, holidays prop으로 자기 달력을 직접 만들어요. 그래서 직접 만든 달력은 호스트 앱의 날짜 계산에 쓰세요. 근무일 달력을 참고하세요.

제약

src/core/calendar.ts는 Dayjs 타입만 가져오고 dayjs 자체는 가져오지 않아요. 요일은 date.day()로 읽고 휴일은 date.format('YYYY-MM-DD') 결과와 비교해요. 그래서 둘 다 넘긴 Dayjs 인스턴스의 모드를 그대로 따라요.

차트 안에서는 항상 UTC예요. 코어가 달력에 건네는 날짜는 모두 src/core/dates.ts가 dayjs.utc로 만들어요. 로컬 모드 Dayjs는 로컬 요일과 로컬 YYYY-MM-DD를 읽어요. 그러니 위 예제처럼 UTC 모드 인스턴스를 넘기세요.

근무일이 하나도 없는 달력도 결과를 반환해요. addDays는 요청한 하루마다 367걸음의 예산을 받고 예산이 떨어지면 평범한 달력 날짜로 물러나요. snapForward는 366번 시도한 뒤 입력을 그대로 반환해요. workingWeekdays를 빈 배열로 주면 오류가 아니라 틀린 숫자가 나와요.

daysBetween이 하루씩 걷는 범위는 18,263일(약 50년)까지예요. 그보다 긴 구간에서는 평범한 달력 일수 차이를 반환해요. 그래서 그만큼 긴 구간에서는 근무일 달력의 단위가 조용히 바뀌어요.

근무일이 하나도 없는 역방향 구간에서 daysBetween은 -0이 아니라 언제나 0을 반환해요.

달력에는 시간 개념이 없어요. 같은 날짜에서 09:00부터 17:00까지인 작업은 daysBetween이 0이에요.

건너뛰기 한계(366)와 걷기 한계(18,263)는 src/core/calendar.ts의 내부 상수예요. @jaeungkim/gantt-chart에서 내보내지 않고 설정할 수도 없어요. CALENDAR_DAYS와 createWorkingCalendar가 모두 거쳐 가는 내부 팩토리 build도 내보내지 않아요.

WorkingCalendar는 그냥 인터페이스예요. 호스트 앱이 createWorkingCalendar 대신 직접 구현해도 돼요.

달력은 날짜만 바꿔요. 막대 도형과 타임라인 눈금은 달력 인자를 받지 않아요. 그래서 막대는 걸쳐 있는 주말과 휴일을 그대로 덮어요. 음영은 별개인 showNonWorkingDays prop이고 타임라인에서 설명해요.

workingCalendar prop이 고르는 달력은 막대 드래그에서 읽어요. 거기서 snapForward가 끌던 끝을 비근무일 밖으로 옮겨요. 근무일 달력을 참고하세요.