Skills web-utilities-date-fns
date-fns patterns for TypeScript - formatting, parsing, manipulation, comparison, timezone handling, and internationalization
git clone https://github.com/agents-inc/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/agents-inc/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/src/skills/web-utilities-date-fns" ~/.claude/skills/agents-inc-skills-web-utilities-date-fns-7c7a9a && rm -rf "$T"
src/skills/web-utilities-date-fns/SKILL.mddate-fns Date Utility Patterns
Quick Guide: Use date-fns for modular, tree-shakeable date operations. Import only what you need. Use
for ISO strings,parseISOwith Unicode tokens for display, and pure functions that return new Date objects. For timezones, useformatwith@date-fns/tz(v4+) orTZDatewithdate-fns-tz(v3.x). Never mutate dates.formatInTimeZone
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use
for ISO 8601 strings - NEVER use parseISO()
which has browser inconsistencies)new Date(string)
(You MUST import only needed functions - NEVER use
which defeats tree-shaking)import * as dateFns
(You MUST use pure functions that return new dates - NEVER mutate dates with
or similar)setDate()
(You MUST use named constants for format strings and durations - NO magic strings like 'yyyy-MM-dd' scattered in code)
</critical_requirements>
Auto-detection: date-fns, format, parseISO, addDays, subMonths, differenceInDays, formatDistance, isAfter, isBefore, eachDayOfInterval, date-fns-tz, @date-fns/tz, @date-fns/utc, TZDate, TZDateMini, UTCDate, UTCDateMini, tz(), transpose, tzName, tzScan, withTimeZone, locale
When to use:
- Formatting dates for display with locale support
- Parsing date strings into Date objects
- Date arithmetic (add/subtract days, months, years)
- Comparing dates and checking intervals
- Calculating differences between dates
- Generating date ranges for calendars
- Relative time formatting ("2 hours ago")
When NOT to use:
- Simple date display - use
(zero bundle cost)Intl.DateTimeFormat - Very simple operations - native
may sufficeDate - Complex recurring dates - use
alongsiderrule.js - Future projects with Temporal API browser support
Detailed Resources:
- examples/core.md - Formatting, parsing, arithmetic, boundaries, preset ranges
- examples/timezone.md - v4 TZDate, v3 date-fns-tz, DST handling, UTC operations
- examples/i18n.md - Locale setup, locale-aware formatting, week start days
- examples/relative.md - Relative time, duration formatting, smart date display
- examples/comparison.md - Comparisons, intervals, overlap detection, validation
- reference.md - Decision frameworks, migration guides, anti-patterns, quick reference
<philosophy>
Philosophy
date-fns is a modular, functional date utility library. Each function is independent, enabling tree-shaking to include only what you use. All functions are pure - they return new Date objects rather than mutating inputs. This makes date operations predictable and testable.
Key principle: Import what you need, let bundlers remove the rest. A simple
format import adds ~2KB, not the entire 80KB library.
// Tree-shakeable - only includes format and parseISO import { format, parseISO } from "date-fns"; // Format constant at module level const DATE_DISPLAY_FORMAT = "MMMM d, yyyy"; const date = parseISO("2026-01-15"); const display = format(date, DATE_DISPLAY_FORMAT); // "January 15, 2026"
v3+ is 100% TypeScript with built-in type definitions. No
@types/date-fns needed.
</philosophy>
<patterns>
Core Patterns
Pattern 1: Format Tokens (Unicode TR35)
date-fns uses Unicode Technical Standard #35 format tokens. These differ from Moment.js (
yyyy not YYYY, dd not DD, EEEE not dddd).
import { format } from "date-fns"; // Define format constants at module level const ISO_DATE_FORMAT = "yyyy-MM-dd"; const DISPLAY_DATE_FORMAT = "MMMM d, yyyy"; const DISPLAY_DATETIME_FORMAT = "MMMM d, yyyy 'at' h:mm a"; const date = new Date(2026, 0, 15, 14, 30, 0); format(date, ISO_DATE_FORMAT); // "2026-01-15" format(date, DISPLAY_DATE_FORMAT); // "January 15, 2026" format(date, DISPLAY_DATETIME_FORMAT); // "January 15, 2026 at 2:30 PM" format(date, "EEEE"); // "Thursday" format(date, "h:mm a"); // "2:30 PM"
See reference.md for the full format token table.
Why good: named constants make format strings reusable and discoverable, Unicode tokens are standard across date libraries
Pattern 2: Locale-Aware Format Shortcuts
Use
P, PP, PPP, PPPP for locale-aware date formatting without specifying exact format.
import { format } from "date-fns"; import { enUS, de, ja, fr } from "date-fns/locale"; const date = new Date(2026, 0, 15); // ✅ Good Example - Locale-aware formatting // Short date format(date, "P", { locale: enUS }); // "01/15/2026" format(date, "P", { locale: de }); // "15.01.2026" format(date, "P", { locale: ja }); // "2026/01/15" // Medium date format(date, "PP", { locale: enUS }); // "Jan 15, 2026" format(date, "PP", { locale: de }); // "15. Jan. 2026" // Long date format(date, "PPP", { locale: enUS }); // "January 15th, 2026" format(date, "PPP", { locale: fr }); // "15 janvier 2026" // Full date format(date, "PPPP", { locale: enUS }); // "Thursday, January 15th, 2026" format(date, "PPPP", { locale: ja }); // "2026年1月15日木曜日" // Time shortcuts format(date, "p", { locale: enUS }); // "12:00 AM" format(date, "pp", { locale: enUS }); // "12:00:00 AM" // Combined date and time format(date, "PPpp", { locale: enUS }); // "Jan 15, 2026, 12:00:00 AM"
Why good:
P/PP/PPP adapt to locale conventions automatically, users see dates in familiar format for their region
// ❌ Bad Example - Hardcoded format ignores locale format(date, "MM/dd/yyyy"); // "01/15/2026" - wrong for most of world!
Why bad: hardcoded MM/dd/yyyy format is US-specific, confusing for European and Asian users who expect different order
Pattern 3: Safe Parsing with parseISO
Use
parseISO for ISO 8601 strings. Use parse for custom formats.
import { parseISO, parse, isValid } from "date-fns"; // ✅ Good - parseISO for ISO strings const date = parseISO("2026-01-15T14:30:00Z"); // ✅ Good - parse for custom formats (3rd arg is reference date) const CUSTOM_DATE_FORMAT = "dd/MM/yyyy"; const customDate = parse("15/01/2026", CUSTOM_DATE_FORMAT, new Date()); // Always validate parsed dates if (!isValid(date)) { /* handle invalid */ }
Why good: parseISO handles all ISO 8601 variants, isValid catches invalid dates
// ❌ Bad - Using Date constructor const date1 = new Date("2026-01-15"); // Browser-inconsistent! const date2 = new Date("01/15/2026"); // May fail in non-US browsers
Why bad:
new Date(string) parsing varies by browser and locale
See examples/core.md for strict round-trip parsing that catches invalid dates like Feb 30.
Pattern 4: Date Arithmetic
Use pure functions for date manipulation. All return new Date objects.
import { addDays, addMonths, addYears, subDays, subMonths, subYears, addHours, addMinutes, } from "date-fns"; // Duration constants const WEEK_IN_DAYS = 7; const BILLING_CYCLE_MONTHS = 1; const TRIAL_PERIOD_DAYS = 14; const date = new Date(2026, 0, 15); // ✅ Good Example - Pure functions return new dates const nextWeek = addDays(date, WEEK_IN_DAYS); // Jan 22, 2026 const nextMonth = addMonths(date, BILLING_CYCLE_MONTHS); // Feb 15, 2026 const nextYear = addYears(date, 1); // Jan 15, 2027 const lastWeek = subDays(date, WEEK_IN_DAYS); // Jan 8, 2026 const trialEnd = addDays(date, TRIAL_PERIOD_DAYS); // Jan 29, 2026 // Chain operations const twoWeeksFromNextMonth = addDays(addMonths(date, 1), WEEK_IN_DAYS * 2); // Original date is unchanged console.log(date); // Still Jan 15, 2026
Why good: pure functions are predictable and testable, original date unchanged, constants document business logic
// ❌ Bad Example - Mutating dates const date = new Date(2026, 0, 15); date.setDate(date.getDate() + 7); // Mutates original! // Original date is now changed - causes bugs in shared references
Why bad: mutation creates side effects, especially problematic when date is passed as prop or stored in state
Pattern 5: Date Boundaries
Use boundary functions for consistent start/end of periods.
import { startOfDay, endOfDay, startOfWeek, endOfWeek, startOfMonth, endOfMonth, startOfQuarter, endOfQuarter, startOfYear, endOfYear, } from "date-fns"; const date = new Date(2026, 0, 15, 14, 30, 0); // ✅ Good Example - Boundary functions for consistent ranges const dayStart = startOfDay(date); // Jan 15, 2026 00:00:00 const dayEnd = endOfDay(date); // Jan 15, 2026 23:59:59.999 // Week boundaries (weekStartsOn: 1 = Monday) const weekStart = startOfWeek(date, { weekStartsOn: 1 }); // Jan 13, 2026 const weekEnd = endOfWeek(date, { weekStartsOn: 1 }); // Jan 19, 2026 const monthStart = startOfMonth(date); // Jan 1, 2026 const monthEnd = endOfMonth(date); // Jan 31, 2026 23:59:59.999
Why good: boundary functions handle edge cases (month lengths, leap years), consistent for database queries and filtering
See examples/core.md for range generation utilities (getMonthRange, preset ranges).
Pattern 6: Date Comparisons
Use comparison functions instead of manual timestamp comparisons.
import { isAfter, isBefore, isEqual, isSameDay, isSameMonth, isSameYear, isWithinInterval, isToday, isPast, isFuture, isWeekend, } from "date-fns"; const date1 = new Date(2026, 0, 15); const date2 = new Date(2026, 0, 20); // ✅ Good Example - Semantic comparison functions const isLater = isAfter(date2, date1); // true const isEarlier = isBefore(date1, date2); // true const areSame = isEqual(date1, date1); // true // Same period checks (ignore time) const sameDay = isSameDay(date1, date1); // true const sameMonth = isSameMonth(date1, date2); // true (both January) const sameYear = isSameYear(date1, date2); // true (both 2026) // Range check const isInRange = isWithinInterval(new Date(2026, 0, 17), { start: date1, end: date2, }); // true // Convenience checks const todayCheck = isToday(new Date()); // true const pastCheck = isPast(date1); // depends on current date const futureCheck = isFuture(date2); // depends on current date const weekendCheck = isWeekend(date1); // false (Thursday)
Why good: semantic function names make code readable, handles edge cases like time zone differences
// ❌ Bad Example - Manual timestamp comparison const isLater = date2.getTime() > date1.getTime(); const sameDay = date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
Why bad: verbose, error-prone, doesn't handle edge cases, hard to read intent
</patterns><integration>
Version Notes
- v4+ (current, 4.1.0): Uses
for timezone handling with@date-fns/tz
class andTZDate
helpertz() - v3.x: Uses
package withdate-fns-tz
,formatInTimeZone
,toZonedTimefromZonedTime - v4 is ESM-first; constants must be imported from
date-fns/constants - v4 returns Invalid Date/NaN instead of throwing errors for invalid inputs
- All versions are 100% TypeScript with built-in types
v4 Bundle Size:
- Core date-fns function: ~2KB each (tree-shakeable)
: 1.2 KB |TZDate
: 916 B (use Mini for internal calculations)TZDateMini
: 504 B |UTCDate
: 239 B (use Mini for internal calculations)UTCDateMini
v4 Timezone Functions (
):@date-fns/tz
: Creates timezone context for thetz()
optionin
: Returns human-readable timezone name (short, long, generic formats)tzName()
: Detects DST transitions within a date rangetzScan()
: Returns UTC offset in minutestzOffset()
: TZDate method for timezone conversion.withTimeZone()
v4 Core Functions (from
):date-fns
: Converts dates between timezones (replacestranspose()
/toZonedTime
)fromZonedTime
<red_flags>
RED FLAGS
for parsing - Browser-inconsistent, usenew Date(string)
orparseISOparse- Mutating dates -
causes side effects, use pure functions likedate.setDate()addDays
from date-fns - Defeats tree-shaking, import individual functionsimport *- Magic format strings -
scattered in code, use named constantsformat(date, 'yyyy-MM-dd') - Missing
check - Parsed dates can be invalid, always validate after parsingisValid - Using Moment.js tokens -
/YYYY
(Moment) vsDD
/yyyy
(date-fns)dd - Hardcoded locale formats -
is US-only, useMM/dd/yyyy
/P
/PP
with localePPP
with inverted start/end - Throws error if start > endisWithinInterval- Month boundary surprises - Jan 31 + 1 month = Feb 28, not March 3
See reference.md for the complete red flags and gotchas list.
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST use
for ISO 8601 strings - NEVER use parseISO()
which has browser inconsistencies)new Date(string)
(You MUST import only needed functions - NEVER use
which defeats tree-shaking)import * as dateFns
(You MUST use pure functions that return new dates - NEVER mutate dates with
or similar)setDate()
(You MUST use named constants for format strings and durations - NO magic strings like 'yyyy-MM-dd' scattered in code)
Failure to follow these rules will cause browser inconsistencies, bundle bloat, and mutation bugs.
</critical_reminders>