date-fns Adapter Guide
The date-fns adapter brings the power of date-fns's extensive utility library to useTemporal while maintaining excellent tree-shaking and performance.
Overview
- Bundle Size: ~2.1KB (adapter only, date-fns tree-shakes)
- Performance: Excellent (80-90% of native)
- Timezone Support: No (use date-fns-tz separately)
- Best For: Applications needing rich date utilities and formatting
Installation
bash
npm install @allystudio/usetemporal @allystudio/usetemporal-adapter-date-fns date-fnsBasic Usage
Simple Setup
typescript
import { createTemporal } from '@allystudio/usetemporal'
import { createDateFnsAdapter } from '@allystudio/usetemporal-adapter-date-fns'
const temporal = createTemporal({
adapter: createDateFnsAdapter()
})With Configuration
typescript
import { createTemporal } from '@allystudio/usetemporal'
import { createDateFnsAdapter } from '@allystudio/usetemporal-adapter-date-fns'
import { enUS, es, fr } from 'date-fns/locale'
const temporal = createTemporal({
adapter: createDateFnsAdapter({
locale: enUS, // Default locale
weekStartsOn: 1, // Monday
firstWeekContainsDate: 4 // ISO week numbering
})
})Features
Rich Formatting
Leverage date-fns formatting alongside useTemporal:
typescript
import { format, formatDistance, formatRelative } from 'date-fns'
const month = temporal.period( new Date(), 'month')
// Various formatting options
format(month.start, 'MMMM yyyy') // "July 2025"
format(month.start, 'MMM do, yyyy') // "Jul 1st, 2025"
format(month.start, 'yyyy-MM-dd') // "2025-07-01"
// Relative formatting
formatDistance(month.start, month.end) // "1 month"
formatRelative(month.start, new Date()) // "last Monday at 12:00 AM"Locale Support
Full internationalization support:
typescript
import { format } from 'date-fns'
import { es, fr, de, ja } from 'date-fns/locale'
const period = temporal.period( new Date(), 'month')
// Format in different locales
format(period.start, 'MMMM yyyy', { locale: es }) // "julio 2025"
format(period.start, 'MMMM yyyy', { locale: fr }) // "juillet 2025"
format(period.start, 'MMMM yyyy', { locale: de }) // "Juli 2025"
format(period.start, 'MMMM yyyy', { locale: ja }) // "7月 2025年"Advanced Date Operations
Combine useTemporal patterns with date-fns utilities:
typescript
import {
addBusinessDays,
isWeekend,
isValid,
parseISO,
startOfQuarter,
endOfQuarter
} from 'date-fns'
// Business day calculations
const today = temporal.period( new Date(), 'day')
const nextBusinessDay = addBusinessDays(today.start, 1)
// Validate dates
const userInput = '2025-07-25'
if (isValid(parseISO(userInput))) {
const period = temporal.period( parseISO(userInput), 'day')
}
// Quarter operations
const quarter = {
start: startOfQuarter(new Date()),
end: endOfQuarter(new Date())
}Unique Capabilities
1. Extensive Parse Functions
typescript
import { parse, parseISO, parseJSON } from 'date-fns'
// Parse various formats
const date1 = parse('25/07/2025', 'dd/MM/yyyy', new Date())
const date2 = parseISO('2025-07-25T10:30:00Z')
const date3 = parseJSON('2025-07-25T10:30:00.000Z')
// Create periods from parsed dates
const period1 = temporal.period( date1, 'day')
const period2 = temporal.period( date2, 'hour')2. Date Validation
typescript
import { isValid, isDate, isPast, isFuture, isToday } from 'date-fns'
function validateAndCreatePeriod(temporal, input, unit) {
const date = parseISO(input)
if (!isValid(date)) {
throw new Error('Invalid date')
}
if (isPast(date)) {
console.warn('Date is in the past')
}
return temporal.period( date, unit)
}3. Complex Comparisons
typescript
import {
isBefore, isAfter, isEqual,
isWithinInterval, areIntervalsOverlapping,
compareAsc, compareDesc
} from 'date-fns'
const period1 = temporal.period( '2025-07-01', 'week')
const period2 = temporal.period( '2025-07-15', 'week')
// Check relationships
isBefore(period1.start, period2.start) // true
isWithinInterval(new Date(), {
start: period1.start,
end: period2.end
}) // depends on current date
// Sort periods
const periods = [period2, period1]
periods.sort((a, b) => compareAsc(a.start, b.start))Common Patterns
Calendar with Formatting
typescript
import { format, getDay } from 'date-fns'
import { enUS } from 'date-fns/locale'
function createFormattedCalendar(temporal, date, locale = enUS) {
const month = temporal.period( date, 'month')
const weeks = divide(temporal.adapter, month, 'week')
return {
title: format(month.start, 'MMMM yyyy', { locale }),
weeks: weeks.map(week => ({
weekNumber: format(week.start, 'w', { locale }),
days: divide(temporal.adapter, week, 'day').map(day => ({
date: day.date,
dayNumber: format(day.start, 'd', { locale }),
dayName: format(day.start, 'EEEE', { locale }),
isWeekend: [0, 6].includes(getDay(day.start))
}))
}))
}
}Working Days Calculator
typescript
import {
addBusinessDays,
differenceInBusinessDays,
isWeekend,
isWithinInterval
} from 'date-fns'
function getWorkingDaysInPeriod(temporal, period) {
const days = divide(temporal.adapter, period, 'day')
return days.filter(day => !isWeekend(day.start))
}
function getNextWorkingDay(temporal, date) {
const nextDay = addBusinessDays(date, 1)
return temporal.period( nextDay, 'day')
}
// Calculate project timeline
const project = temporal.period( '2025-07-01', 'month')
const workingDays = getWorkingDaysInPeriod(temporal, project)
console.log(`Project has ${workingDays.length} working days`)Date Range Formatting
typescript
import { format, isSameMonth, isSameYear } from 'date-fns'
function formatDateRange(start, end, locale) {
if (isSameMonth(start, end)) {
// Same month: "July 1-15, 2025"
return `${format(start, 'MMMM d', { locale })}-${format(end, 'd, yyyy', { locale })}`
} else if (isSameYear(start, end)) {
// Same year: "July 1 - August 15, 2025"
return `${format(start, 'MMMM d', { locale })} - ${format(end, 'MMMM d, yyyy', { locale })}`
} else {
// Different years: "December 25, 2024 - January 5, 2025"
return `${format(start, 'MMMM d, yyyy', { locale })} - ${format(end, 'MMMM d, yyyy', { locale })}`
}
}
const period = temporal.period( new Date(), 'quarter')
console.log(formatDateRange(period.start, period.end, enUS))Performance Considerations
Tree-Shaking
Import only what you need:
typescript
// ✅ Good: Specific imports
import { format, parseISO } from 'date-fns'
// ❌ Bad: Importing everything
import * as dateFns from 'date-fns'Locale Loading
Load locales dynamically:
typescript
// Static import (adds to bundle)
import { es } from 'date-fns/locale'
// Dynamic import (loaded when needed)
async function loadLocale(code) {
const locale = await import(`date-fns/locale/${code}`)
return locale.default
}
// Usage
const locale = await loadLocale('es')
format(date, 'MMMM', { locale })Migration from date-fns
If you're already using date-fns, migration is straightforward:
typescript
// Before: Pure date-fns
import { startOfMonth, endOfMonth, eachDayOfInterval } from 'date-fns'
const start = startOfMonth(new Date())
const end = endOfMonth(new Date())
const days = eachDayOfInterval({ start, end })
// After: useTemporal with date-fns
const month = temporal.period( new Date(), 'month')
const days = divide(temporal.adapter, month, 'day')
// You can still use date-fns utilities!
const formatted = days.map(day => format(day.start, 'yyyy-MM-dd'))Best Practices
1. Combine Strengths
Use useTemporal for structure, date-fns for utilities:
typescript
// useTemporal for time hierarchy
const year = temporal.period( new Date(), 'year')
const quarters = divide(temporal.adapter, year, 'quarter')
// date-fns for formatting and utilities
const quarterReports = quarters.map(q => ({
period: q,
label: format(q.start, 'Qo yyyy'),
workingDays: differenceInBusinessDays(q.end, q.start)
}))2. Consistent Locale Handling
typescript
// Create a locale-aware temporal instance
function createLocaleTemporal(localeCode) {
const locale = await import(`date-fns/locale/${localeCode}`)
return {
temporal: createTemporal({
adapter: createDateFnsAdapter({
locale: locale.default
})
}),
locale: locale.default
}
}
// Use consistently throughout app
const { temporal, locale } = await createLocaleTemporal('es')3. Type Safety
typescript
import type { Locale } from 'date-fns'
interface DateConfig {
temporal: Temporal
locale: Locale
format: string
}
function formatPeriod(
period: Period,
config: DateConfig
): string {
return format(period.start, config.format, {
locale: config.locale
})
}Limitations
No Built-in Timezone Support
For timezone support, combine with date-fns-tz:
typescript
import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz'
// Convert to specific timezone
const utcDate = zonedTimeToUtc('2025-07-25 10:00:00', 'America/New_York')
const period = temporal.period( utcDate, 'hour')Quarter Handling
date-fns has limited quarter support:
typescript
// date-fns quarters are calendar quarters (Q1 = Jan-Mar)
// For fiscal quarters, implement custom logic
function getFiscalQuarter(date, fiscalYearStart = 4) {
const month = date.getMonth()
const adjustedMonth = (month - fiscalYearStart + 12) % 12
return Math.floor(adjustedMonth / 3) + 1
}Next Steps
- Explore date-fns documentation
- Learn about format strings
- See locale guide
- Consider Luxon adapter for timezones
- Check performance guide