개요
자바스크립트에서 날짜·시간을 다루는 일은 사소해 보이지만 버그가 가장 잦은 영역 중 하나임 day.js, moment.js, luxon 등 대안 존재하나 date-fns는 모듈화, 작은 크기, 불변성 중심 설계로 프런트엔드와 백엔드 모두에 적합함 아래는 date-fns와 date-fns-tz를 활용해 바로 써먹을 수 있는 23가지 패턴 정리
핵심 개념
- Date 객체는 타임존이 아닌 UTC 타임스탬프를 보유하는 값이라는 점이 핵심
- date-fns 함수는 대부분 불변 동작, 인자로 받은 Date를 변경하지 않음
- 포맷팅은 format, 상대 표기는 formatDistance 계열, 구간 계산은 differenceIn*, intervalToDuration 등 사용
- 파싱은 parseISO 또는 parse로 명시적 포맷 지정 권장
- 타임존 변환은 date-fns-tz의 utcToZonedTime, zonedTimeToUtc, formatInTimeZone 사용
실전 패턴
- 현재 날짜와 시간 가져오기
const now = new Date()- 특정 날짜·시간 설정
import { setMinutes, setHours } from 'date-fns'
const specific = setHours(setMinutes(new Date(), 30), 17) // 17:30
- 날짜 포맷팅
import { format } from 'date-fns'
const ymd = format(new Date(), 'yyyy-MM-dd')- 기간 더하기·빼기
import { addDays, subMonths } from 'date-fns'
const inAWeek = addDays(new Date(), 7)
const aMonthAgo = subMonths(new Date(), 1)- 특정 요일까지 남은 일수
import { nextDay, differenceInDays } from 'date-fns'
const nextMon = nextDay(new Date(), 1) // 0 일요일, 1 월요일
const d = differenceInDays(nextMon, new Date())- 두 날짜 차이 계산
import { differenceInDays } from 'date-fns'
const diff = differenceInDays(new Date(2023, 11, 31), new Date(2023, 0, 1))- 동일한 날짜 비교
import { isSameDay } from 'date-fns'
const same = isSameDay(new Date(2023, 0, 1), new Date('2023-01-01T05:00:00Z'))- 윤년 확인
import { isLeapYear } from 'date-fns'
const isLeap = isLeapYear(new Date(2024, 0, 1))- 날짜 유효성 검사
import { isValid } from 'date-fns'
const ok = isValid(new Date('2023-02-30')) // false
- 로컬과 특정 타임존 간 변환
import { utcToZonedTime, zonedTimeToUtc, formatInTimeZone } from 'date-fns-tz'
const tz = 'America/New_York'
const utc = zonedTimeToUtc(new Date(), tz)
const inNY = utcToZonedTime(new Date(), tz)
const shown = formatInTimeZone(new Date(), tz, 'yyyy-MM-dd HH:mm:ssXXX')- 문자열 파싱
import { parseISO, parse } from 'date-fns'
const a = parseISO('2023-01-01')
const b = parse('01/02/2023 17:30', 'MM/dd/yyyy HH:mm', new Date())- 상대 시간 표현
import { formatDistanceToNow } from 'date-fns'
import { ko } from 'date-fns/locale'
const rel = formatDistanceToNow(new Date(Date.now() - 3 * 24 * 60 * 60 * 1000), { addSuffix: true, locale: ko })- 과거·미래 판별
import { isPast, isFuture } from 'date-fns'
const past = isPast(new Date('2023-01-01'))
const future = isFuture(new Date(Date.now() + 60_000))- 특정 기간의 날짜 배열 생성
루프 대신 eachDayOfInterval 사용 권장
import { startOfMonth, endOfMonth, eachDayOfInterval } from 'date-fns'
const days = eachDayOfInterval({ start: startOfMonth(new Date(2023, 0)), end: endOfMonth(new Date(2023, 0)) })- 연도의 첫날·마지막 날
import { startOfYear, endOfYear } from 'date-fns'
const yStart = startOfYear(new Date())
const yEnd = endOfYear(new Date())- 간격을 사람이 읽는 문자열로 표현
import { intervalToDuration, formatDuration } from 'date-fns'
const dur = intervalToDuration({ start: new Date(2023, 0, 1), end: new Date(2023, 1, 1) })
const txt = formatDuration(dur)- 타임존을 고려한 계산과 표시
계산은 UTC 기준, 표시만 타임존 적용 권장
import { addHours } from 'date-fns'
import { formatInTimeZone } from 'date-fns-tz'
const shippedAtUtc = addHours(new Date(), 5)
const view = formatInTimeZone(shippedAtUtc, 'Asia/Seoul', 'yyyy-MM-dd HH:mm')- 특정 날짜의 시작·끝
import { startOfDay, endOfDay } from 'date-fns'
const s = startOfDay(new Date())
const e = endOfDay(new Date())- 국제화 포맷팅
import { format } from 'date-fns'
import { ko, es } from 'date-fns/locale'
const d = new Date(2023, 0, 1)
const koFmt = format(d, 'PPP', { locale: ko })
const esFmt = format(d, 'PPP', { locale: es })- 날짜 데이터 유효성 검증 심화
import { parseISO, isValid } from 'date-fns'
const input = '2023-01-01'
const parsed = parseISO(input)
const safe = isValid(parsed)- 기간 내 특정 요일 모두 구하기
주 단위 앵커를 eachWeekOfInterval로 만들고 원하는 요일로 오프셋, 주 시작 요일 옵션 주의
import { eachWeekOfInterval, startOfYear, endOfYear, addDays } from 'date-fns'
const mondaysOfYear = (date) => {
const weeks = eachWeekOfInterval({ start: startOfYear(date), end: endOfYear(date) }, { weekStartsOn: 0 })
return weeks.map(w => addDays(w, 1)) // 일요일 시작 기준 +1일 → 월요일
}- 오늘로부터 N일 후
import { addDays } from 'date-fns'
const afterN = (n) => addDays(new Date(), n)- 분기 계산
import { getQuarter } from 'date-fns'
const q = getQuarter(new Date(2023, 5))주의사항
- 타임존 라벨은 반드시 IANA 이름 사용 예시 ‘Asia/Seoul’, ‘America/New_York’ 등, ‘Local’ 같은 임의 문자열 사용 불가
- date-fns-tz에서 format 함수가 아닌 formatInTimeZone 사용 권장, 일반 포맷은 date-fns의 format 사용
- 포맷 토큰 주의 yyyy와 YYYY는 다름, 주차 기반 연도는 YYYY가 아닌 경우가 많음, date-fns는 소문자 yyyy 권장
- formatDistanceToNow 결과 언어는 locale 옵션 없으면 영어, 한국어 표시는 locale: ko 필요
- 반복 계산 시 불변성 유지되어 안전하지만 Date 객체 생성 비용 존재, 대량 처리 시 배치 처리 또는 캐싱 고려
- DST 전환 구간은 날짜 계산이 직관과 다를 수 있음, 타임존 표시만 바꾸고 계산은 UTC 기준으로 수행 권장
- 트리 셰이킹을 위해 필요한 함수만 개별 import 권장
마무리
date-fns는 작은 단위 함수들의 조합으로 날짜·시간 문제를 안정적으로 해결하게 해줌 포맷팅, 파싱, 구간 계산, 타임존과 국제화까지 대부분의 요구를 커버하며 불변성과 모듈성 덕에 유지보수에 유리함 위 패턴들을 팀 기준 유틸로 정리해두면 제품 전반의 시간 처리 일관성과 품질을 빠르게 끌어올릴 수 있음