개요

자바스크립트에서 날짜·시간을 다루는 일은 사소해 보이지만 버그가 가장 잦은 영역 중 하나임 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 사용

실전 패턴

  1. 현재 날짜와 시간 가져오기
const now = new Date()
  1. 특정 날짜·시간 설정
import { setMinutes, setHours } from 'date-fns'
const specific = setHours(setMinutes(new Date(), 30), 17) // 17:30
  1. 날짜 포맷팅
import { format } from 'date-fns'
const ymd = format(new Date(), 'yyyy-MM-dd')
  1. 기간 더하기·빼기
import { addDays, subMonths } from 'date-fns'
const inAWeek = addDays(new Date(), 7)
const aMonthAgo = subMonths(new Date(), 1)
  1. 특정 요일까지 남은 일수
import { nextDay, differenceInDays } from 'date-fns'
const nextMon = nextDay(new Date(), 1) // 0 일요일, 1 월요일
const d = differenceInDays(nextMon, new Date())
  1. 두 날짜 차이 계산
import { differenceInDays } from 'date-fns'
const diff = differenceInDays(new Date(2023, 11, 31), new Date(2023, 0, 1))
  1. 동일한 날짜 비교
import { isSameDay } from 'date-fns'
const same = isSameDay(new Date(2023, 0, 1), new Date('2023-01-01T05:00:00Z'))
  1. 윤년 확인
import { isLeapYear } from 'date-fns'
const isLeap = isLeapYear(new Date(2024, 0, 1))
  1. 날짜 유효성 검사
import { isValid } from 'date-fns'
const ok = isValid(new Date('2023-02-30')) // false
  1. 로컬과 특정 타임존 간 변환
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')
  1. 문자열 파싱
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())
  1. 상대 시간 표현
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 })
  1. 과거·미래 판별
import { isPast, isFuture } from 'date-fns'
const past = isPast(new Date('2023-01-01'))
const future = isFuture(new Date(Date.now() + 60_000))
  1. 특정 기간의 날짜 배열 생성

루프 대신 eachDayOfInterval 사용 권장

import { startOfMonth, endOfMonth, eachDayOfInterval } from 'date-fns'
const days = eachDayOfInterval({ start: startOfMonth(new Date(2023, 0)), end: endOfMonth(new Date(2023, 0)) })
  1. 연도의 첫날·마지막 날
import { startOfYear, endOfYear } from 'date-fns'
const yStart = startOfYear(new Date())
const yEnd = endOfYear(new Date())
  1. 간격을 사람이 읽는 문자열로 표현
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)
  1. 타임존을 고려한 계산과 표시

계산은 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')
  1. 특정 날짜의 시작·끝
import { startOfDay, endOfDay } from 'date-fns'
const s = startOfDay(new Date())
const e = endOfDay(new Date())
  1. 국제화 포맷팅
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 })
  1. 날짜 데이터 유효성 검증 심화
import { parseISO, isValid } from 'date-fns'
const input = '2023-01-01'
const parsed = parseISO(input)
const safe = isValid(parsed)
  1. 기간 내 특정 요일 모두 구하기

주 단위 앵커를 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일 → 월요일
}
  1. 오늘로부터 N일 후
import { addDays } from 'date-fns'
const afterN = (n) => addDays(new Date(), n)
  1. 분기 계산
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는 작은 단위 함수들의 조합으로 날짜·시간 문제를 안정적으로 해결하게 해줌 포맷팅, 파싱, 구간 계산, 타임존과 국제화까지 대부분의 요구를 커버하며 불변성과 모듈성 덕에 유지보수에 유리함 위 패턴들을 팀 기준 유틸로 정리해두면 제품 전반의 시간 처리 일관성과 품질을 빠르게 끌어올릴 수 있음

참고자료