date-fns provides modular date utilities for JavaScript — each function is a pure import with no global state or prototype modifications. format(date, "yyyy-MM-dd HH:mm") formats using Unicode token patterns. parseISO("2024-01-15T12:00:00Z") parses ISO 8601 strings. parse("01/15/2024", "MM/dd/yyyy", new Date()) parses custom formats. differenceInDays, differenceInHours, differenceInMinutes compute differences. addDays, addMonths, subWeeks, startOfMonth, endOfMonth, startOfWeek perform arithmetic. isBefore, isAfter, isEqual, isWithinInterval compare dates. formatDistance produces relative strings like “3 days ago”. date-fns-tz adds timezone-aware formatInTimeZone and zonedTimeToUtc. eachDayOfInterval generates date arrays for calendars. locale objects from date-fns/locale enable localized output. Claude Code generates date-fns formatting, parsing, arithmetic, timezone handling, and calendar range utilities for TypeScript applications.
CLAUDE.md for date-fns
## date-fns Stack
- Version: date-fns >= 3.3, date-fns-tz >= 3.1
- Format: format(date, "yyyy-MM-dd") — Unicode tokens, not moment.js tokens
- Parse ISO: parseISO("2024-01-15T12:00:00Z") — safe ISO 8601 parser
- Parse custom: parse("Jan 15", "MMM d", new Date()) — reference date required
- Diff: differenceInDays(end, start) — positive if end is after start
- Arithmetic: addDays(date, 7) / subMonths(date, 1) / startOfWeek(date, { weekStartsOn: 1 })
- Relative: formatDistance(date, new Date(), { addSuffix: true }) — "3 days ago"
- TZ: formatInTimeZone(date, "America/New_York", "yyyy-MM-dd HH:mm zzz")
- Locale: format(date, "PPPP", { locale: fr }) — import { fr } from "date-fns/locale"
Formatting and Parsing
// lib/date-utils.ts — format and parse utilities
import {
format,
formatISO,
parseISO,
parse,
isValid,
formatDistance,
formatDistanceStrict,
formatRelative,
} from "date-fns"
// Common format patterns
export const DATE_FORMATS = {
short: "MMM d, yyyy", // Jan 15, 2024
long: "MMMM d, yyyy", // January 15, 2024
full: "EEEE, MMMM d, yyyy", // Monday, January 15, 2024
iso: "yyyy-MM-dd", // 2024-01-15
datetime: "MMM d, yyyy h:mm a", // Jan 15, 2024 2:30 PM
time: "h:mm a", // 2:30 PM
time24: "HH:mm", // 14:30
monthYear: "MMMM yyyy", // January 2024
dayOfWeek: "EEEE", // Monday
}
export function formatDate(
date: Date | string | number,
pattern: keyof typeof DATE_FORMATS | string = "short"
): string {
const d = typeof date === "string" ? parseISO(date) : new Date(date)
if (!isValid(d)) return "Invalid date"
const fmt = DATE_FORMATS[pattern as keyof typeof DATE_FORMATS] ?? pattern
return format(d, fmt)
}
// Parse user-entered dates in multiple formats
const PARSE_FORMATS = [
"yyyy-MM-dd",
"MM/dd/yyyy",
"M/d/yyyy",
"MMM d, yyyy",
"MMMM d, yyyy",
"dd-MM-yyyy",
]
export function parseUserDate(input: string): Date | null {
const trimmed = input.trim()
// Try ISO first (most common from APIs)
const iso = parseISO(trimmed)
if (isValid(iso)) return iso
// Try common formats
const ref = new Date()
for (const fmt of PARSE_FORMATS) {
const parsed = parse(trimmed, fmt, ref)
if (isValid(parsed)) return parsed
}
return null
}
// Relative time formatting
export function timeAgo(date: Date | string): string {
const d = typeof date === "string" ? parseISO(date) : date
return formatDistance(d, new Date(), { addSuffix: true }) // "3 days ago"
}
export function strictTimeAgo(date: Date | string): string {
const d = typeof date === "string" ? parseISO(date) : date
return formatDistanceStrict(d, new Date(), { addSuffix: true }) // "3 days ago" (no "about")
}
// ISO for API payloads
export function toISOString(date: Date): string {
return formatISO(date) // "2024-01-15T12:00:00.000Z"
}
Date Arithmetic
// lib/date-arithmetic.ts — add/subtract/start/end
import {
addDays, addWeeks, addMonths, addYears,
subDays, subWeeks, subMonths,
startOfDay, endOfDay,
startOfWeek, endOfWeek,
startOfMonth, endOfMonth,
startOfQuarter, endOfQuarter,
startOfYear, endOfYear,
differenceInDays, differenceInHours, differenceInMinutes,
differenceInCalendarDays,
isBefore, isAfter, isEqual, isWithinInterval,
clamp, max, min,
} from "date-fns"
// Business date ranges for reports
export function getDateRange(preset: "today" | "week" | "month" | "quarter" | "year") {
const now = new Date()
switch (preset) {
case "today":
return { start: startOfDay(now), end: endOfDay(now) }
case "week":
return {
start: startOfWeek(now, { weekStartsOn: 1 }), // Monday start
end: endOfWeek(now, { weekStartsOn: 1 }),
}
case "month":
return { start: startOfMonth(now), end: endOfMonth(now) }
case "quarter":
return { start: startOfQuarter(now), end: endOfQuarter(now) }
case "year":
return { start: startOfYear(now), end: endOfYear(now) }
}
}
// Compare with previous period
export function getPreviousPeriod(start: Date, end: Date): { start: Date; end: Date } {
const durationDays = differenceInDays(end, start) + 1
return {
start: subDays(start, durationDays),
end: subDays(end, durationDays),
}
}
// SLA and deadline tracking
export function getDeadlineInfo(createdAt: Date, slaHours: number) {
const deadline = addHours(createdAt, slaHours)
const now = new Date()
const minutesRemaining = differenceInMinutes(deadline, now)
const isOverdue = isBefore(deadline, now)
const isUrgent = minutesRemaining < 60 && !isOverdue
return {
deadline,
isOverdue,
isUrgent,
minutesRemaining: Math.max(0, minutesRemaining),
hoursRemaining: Math.max(0, differenceInHours(deadline, now)),
}
}
import { addHours } from "date-fns"
// Upcoming events within n days
export function filterUpcoming(events: { date: Date }[], withinDays: number) {
const now = new Date()
const cutoff = addDays(now, withinDays)
return events.filter(e =>
isWithinInterval(e.date, { start: now, end: cutoff })
)
}
// Duration formatting
export function formatDuration(startDate: Date, endDate: Date): string {
const days = differenceInCalendarDays(endDate, startDate)
if (days === 0) {
const hours = differenceInHours(endDate, startDate)
if (hours === 0) return `${differenceInMinutes(endDate, startDate)}m`
return `${hours}h`
}
if (days < 7) return `${days}d`
if (days < 30) return `${Math.floor(days / 7)}w`
if (days < 365) return `${Math.floor(days / 30)}mo`
return `${Math.floor(days / 365)}y`
}
Timezone-Aware Formatting
// lib/timezone-utils.ts — date-fns-tz
import { formatInTimeZone, toZonedTime, fromZonedTime } from "date-fns-tz"
import { format } from "date-fns"
// Format a UTC date for a specific user timezone
export function formatForTimezone(
utcDate: Date | string,
timezone: string,
pattern = "MMM d, yyyy h:mm a zzz"
): string {
const d = typeof utcDate === "string" ? new Date(utcDate) : utcDate
return formatInTimeZone(d, timezone, pattern)
}
// Convert local datetime input to UTC for storage
export function localToUtc(localDateString: string, timezone: string): Date {
return fromZonedTime(localDateString, timezone)
}
// Common timezone display names
export const TIMEZONE_OPTIONS = [
{ value: "America/New_York", label: "Eastern (ET)" },
{ value: "America/Chicago", label: "Central (CT)" },
{ value: "America/Denver", label: "Mountain (MT)" },
{ value: "America/Los_Angeles", label: "Pacific (PT)" },
{ value: "Europe/London", label: "London (GMT/BST)" },
{ value: "Europe/Paris", label: "Central European (CET)" },
{ value: "Asia/Tokyo", label: "Japan (JST)" },
{ value: "Asia/Singapore", label: "Singapore (SGT)" },
{ value: "Australia/Sydney", label: "Sydney (AEST)" },
] as const
// Format order timestamps for customer's timezone
export function formatOrderDate(
order: { createdAt: string },
customerTimezone: string
): string {
return formatInTimeZone(
new Date(order.createdAt),
customerTimezone,
"MMMM d, yyyy 'at' h:mm a zzz"
)
}
Calendar Range Generation
// lib/calendar-utils.ts — eachDayOfInterval, calendar grids
import {
eachDayOfInterval,
eachWeekOfInterval,
startOfMonth, endOfMonth,
startOfWeek, endOfWeek,
isSameDay, isSameMonth,
isToday, isWeekend,
getDay,
} from "date-fns"
interface CalendarDay {
date: Date
isCurrentMonth: boolean
isToday: boolean
isWeekend: boolean
dayOfWeek: number
}
// Generate full calendar grid (including padding days from adjacent months)
export function buildCalendarGrid(month: Date): CalendarDay[][] {
const monthStart = startOfMonth(month)
const monthEnd = endOfMonth(month)
const calendarStart = startOfWeek(monthStart, { weekStartsOn: 1 })
const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 1 })
const allDays = eachDayOfInterval({ start: calendarStart, end: calendarEnd })
const days: CalendarDay[] = allDays.map(date => ({
date,
isCurrentMonth: isSameMonth(date, month),
isToday: isToday(date),
isWeekend: isWeekend(date),
dayOfWeek: getDay(date),
}))
// Split into weeks
const weeks: CalendarDay[][] = []
for (let i = 0; i < days.length; i += 7) {
weeks.push(days.slice(i, i + 7))
}
return weeks
}
// Date range for analytics reports
export function getDaysInRange(start: Date, end: Date): Date[] {
return eachDayOfInterval({ start, end })
}
// Check if a date has events (for calendar highlighting)
export function datesWithEvents(
eventDates: Date[],
calendarDates: Date[]
): Set<string> {
const eventSet = new Set(eventDates.map(d => format(d, "yyyy-MM-dd")))
return new Set(
calendarDates
.filter(d => eventSet.has(format(d, "yyyy-MM-dd")))
.map(d => format(d, "yyyy-MM-dd"))
)
}
import { format } from "date-fns"
For the Luxon alternative when timezone-aware operations are the primary concern — Luxon has a more ergonomic immutable DateTime API with chainable methods and first-class timezone and locale support built-in, without needing a separate date-fns-tz package, see the timezone handling guide. For the Day.js alternative when a tiny bundle size is critical — Day.js is a Moment.js-compatible API at 2KB gzipped with an identical plugin architecture, useful in consumer-facing bundles where date-fns tree-shaking still weighs too much, see the lightweight utilities guide. The Claude Skills 360 bundle includes date-fns skill sets covering formatting, arithmetic, timezones, and calendar generation. Start with the free tier to try date utility generation.