Библиотека date-fns изначально проектировалась как набор независимых чистых функций. В сочетании с TypeScript это позволяет строить строго типизированную систему работы с датами без мутаций и неявных преобразований.
Большинство функций библиотеки принимают аргумент типа:
Date | number
Тип number интерпретируется как Unix timestamp в
миллисекундах.
Пример сигнатуры:
addDays(date: Date | number, amount: number): Date
Использование:
import { addDays } from 'date-fns'
const now: Date = new Date()
const result = addDays(now, 5)
TypeScript автоматически выводит возвращаемый тип:
const result: Date
DateArgВо внутренних типах библиотеки часто используется универсальный тип:
type DateArg<DateType extends Date> = DateType | number | string
Он применяется для поддержки расширенных сценариев и совместимости с пользовательскими типами дат.
Например:
function process(date: DateArg<Date>) {
return new Date(date)
}
Подобная конструкция особенно полезна при создании собственных обёрток над date-fns.
Одна из самых распространённых проблем — передача null
или undefined.
Небезопасный вариант:
import { format } from 'date-fns'
const userDate: Date | undefined = undefined
format(userDate, 'yyyy-MM-dd')
TypeScript выдаст ошибку:
Argument of type 'undefined' is not assignable
Корректная проверка:
if (userDate) {
const formatted = format(userDate, 'yyyy-MM-dd')
}
Альтернативный вариант:
const formatted = userDate
? format(userDate, 'yyyy-MM-dd')
: 'Дата отсутствует'
Date-fns особенно эффективна в сочетании с собственными типизированными утилитами.
Пример:
import { isBefore, isAfter } from 'date-fns'
type DateRange = {
start: Date
end: Date
}
function isDateInsideRange(
date: Date,
range: DateRange
): boolean {
return (
isAfter(date, range.start) &&
isBefore(date, range.end)
)
}
Использование:
const range: DateRange = {
start: new Date(2025, 0, 1),
end: new Date(2025, 11, 31)
}
const result = isDateInsideRange(
new Date(),
range
)
Readonly<Date>Объекты Date являются изменяемыми. Несмотря на то что
date-fns не мутирует входящие значения, обычный JavaScript позволяет
изменить дату напрямую.
Пример мутации:
const date = new Date()
date.setHours(0)
Для ограничения подобных операций можно применять
Readonly<Date>:
function printDate(date: Readonly<Date>) {
console.log(date.toISOString())
}
Теперь TypeScript запретит вызовы mutating-методов внутри функции.
Типизированные коллекции позволяют избежать смешивания разных форматов.
Корректный вариант:
const dates: Date[] = [
new Date(),
new Date(2025, 0, 1)
]
Сортировка:
import { compareAsc } from 'date-fns'
dates.sort(compareAsc)
TypeScript гарантирует, что в массив не попадут строки или произвольные значения.
Пример фильтрации будущих дат:
import { isFuture } from 'date-fns'
const dates: Date[] = [
new Date(),
new Date(2030, 0, 1),
new Date(2020, 0, 1)
]
const futureDates = dates.filter(isFuture)
Тип результата:
Date[]
Некоторые проекты используют расширения Date.
Пример:
class UTCDate extends Date {
timezone = 'UTC'
}
Date-fns поддерживает generic-типизацию:
function cloneDate<T extends Date>(date: T): T {
return new date.constructor(date.getTime()) as T
}
Использование:
const utcDate = new UTCDate()
const cloned = cloneDate(utcDate)
Тип cloned будет определён как UTCDate.
Одной из ключевых проблем JavaScript остаётся небезопасный парсинг дат.
Плохой вариант:
new Date('01-02-2025')
Поведение зависит от среды выполнения.
Вместо этого используется parse:
import { parse } from 'date-fns'
const parsed = parse(
'2025-01-15',
'yyyy-MM-dd',
new Date()
)
Тип результата:
Date
Даже объект Date может содержать невалидное
значение.
Пример:
const invalid = new Date('wrong')
Для проверки применяется isValid.
import { isValid } from 'date-fns'
if (isValid(invalid)) {
console.log('Корректная дата')
}
TypeScript поддерживает narrowing через пользовательские guards.
Пример:
import { isValid } from 'date-fns'
function isRealDate(value: unknown): value is Date {
return value instanceof Date && isValid(value)
}
Использование:
const value: unknown = new Date()
if (isRealDate(value)) {
value.getFullYear()
}
После проверки тип автоматически сужается до Date.
Часто API возвращает дату в нескольких форматах.
Пример:
type InputDate = string | number | Date
Типобезопасное преобразование:
function normalizeDate(input: InputDate): Date {
if (input instanceof Date) {
return input
}
return new Date(input)
}
Date-fns хорошо сочетается со строгими доменными моделями.
Пример структуры периода:
type Period = {
readonly start: Date
readonly end: Date
}
Вычисление длительности:
import { differenceInDays } from 'date-fns'
function getPeriodLength(period: Period): number {
return differenceInDays(
period.end,
period.start
)
}
Все функции date-fns возвращают новый объект даты.
Пример:
import { addMonths } from 'date-fns'
const original = new Date(2025, 0, 1)
const updated = addMonths(original, 1)
Исходная дата не изменяется:
console.log(original)
console.log(updated)
Это особенно важно в Redux, Zustand, MobX и других state-oriented архитектурах.
Функция format принимает строку шаблона:
format(date, 'yyyy-MM-dd')
Однако TypeScript не валидирует шаблон автоматически.
Для повышения безопасности можно использовать string literal types:
type DateFormat =
| 'yyyy-MM-dd'
| 'dd.MM.yyyy'
| 'HH:mm:ss'
Пример:
function safeFormat(
date: Date,
pattern: DateFormat
) {
return format(date, pattern)
}
Теперь использование неверного шаблона вызовет ошибку компиляции.
В крупных проектах часто применяется enum-подход.
enum DateFormats {
ISO = 'yyyy-MM-dd',
FULL = 'dd.MM.yyyy',
TIME = 'HH:mm:ss'
}
Использование:
format(new Date(), DateFormats.ISO)
При передаче данных по сети объекты Date преобразуются в
строки.
Тип DTO:
type UserDto = {
createdAt: string
}
Внутренний тип приложения:
type User = {
createdAt: Date
}
Преобразование:
function mapUser(dto: UserDto): User {
return {
createdAt: new Date(dto.createdAt)
}
}
Date-fns содержит отдельную функцию для ISO-формата:
import { parseISO } from 'date-fns'
const date = parseISO(
'2025-01-10T12:00:00.000Z'
)
Такой подход безопаснее обычного конструктора Date.
При работе с API особенно важно явно разделять типы.
Пример:
type ApiResponse = {
updatedAt: string
}
Преобразование:
async function loadData(): Promise<Date> {
const response: ApiResponse = await fetchData()
return parseISO(response.updatedAt)
}
Date-fns предоставляет типобезопасные функции работы с диапазонами.
Пример:
import { isWithinInterval } from 'date-fns'
const result = isWithinInterval(
new Date(),
{
start: new Date(2025, 0, 1),
end: new Date(2025, 11, 31)
}
)
Структура interval:
{
start: Date
end: Date
}
Date-fns хорошо подходит для построения функциональных пайплайнов.
Пример:
import {
addDays,
startOfMonth,
format
} from 'date-fns'
const result = format(
addDays(
startOfMonth(new Date()),
10
),
'yyyy-MM-dd'
)
Все промежуточные значения сохраняют строгую типизацию.
Date-fns принимает timestamp в миллисекундах.
Ошибка:
addDays(1710000000, 1)
Здесь переданы секунды, а не миллисекунды.
Безопасный вариант:
const timestampMs = 1710000000 * 1000
addDays(timestampMs, 1)
Для исключения путаницы между секундами и миллисекундами можно применять branded types.
Пример:
type Milliseconds = number & {
__brand: 'milliseconds'
}
type Seconds = number & {
__brand: 'seconds'
}
Конвертация:
function toMilliseconds(
seconds: Seconds
): Milliseconds {
return (seconds * 1000) as Milliseconds
}
Date-fns удобно использовать в доменной логике.
Пример проверки возраста:
import { differenceInYears } from 'date-fns'
function isAdult(
birthDate: Date
): boolean {
return (
differenceInYears(
new Date(),
birthDate
) >= 18
)
}
strictNullChecksПри включённом strictNullChecks TypeScript предотвращает
большинство ошибок, связанных с отсутствующими датами.
Пример:
type Event = {
startDate?: Date
}
Без проверки:
event.startDate.getTime()
Ошибка компиляции:
Object is possibly 'undefined'
Корректный вариант:
if (event.startDate) {
event.startDate.getTime()
}
В больших системах полезно выделять отдельные типы.
Пример:
type CalendarEvent = {
id: string
title: string
start: Date
end: Date
}
Проверка пересечения:
import {
areIntervalsOverlapping
} from 'date-fns'
function intersects(
a: CalendarEvent,
b: CalendarEvent
) {
return areIntervalsOverlapping(
{
start: a.start,
end: a.end
},
{
start: b.start,
end: b.end
}
)
}
Часто date-fns используется вместе с runtime-валидацией.
Пример:
import { z } from 'zod'
const schema = z.object({
createdAt: z.coerce.date()
})
После валидации:
type Result = z.infer<typeof schema>
Тип:
{
createdAt: Date
}
Prisma возвращает поля типа Date.
Пример:
const user = await prisma.user.findFirst()
if (user) {
format(user.createdAt, 'yyyy-MM-dd')
}
TypeScript автоматически определяет тип поля как
Date.
Date-fns широко используется в React-приложениях.
Пример:
type Props = {
createdAt: Date
}
export function PostDate({
createdAt
}: Props) {
return (
<span>
{format(createdAt, 'dd.MM.yyyy')}
</span>
)
}
Типизация props предотвращает передачу строк вместо объектов
Date.
Пример:
function useFormattedDate(
date: Date,
pattern: string
): string {
return format(date, pattern)
}
Использование:
const value = useFormattedDate(
new Date(),
'yyyy-MM-dd'
)
Date-fns работает с локальной временной зоной среды выполнения. Для строгой типизации timezone-логики часто вводятся дополнительные типы.
Пример:
type UTCISOString = string & {
__brand: 'utc'
}
Такой подход помогает отделять UTC-значения от локальных дат.
Пример конфигурации форматирования:
type FormatConfig = {
locale: Locale
pattern: string
}
Использование:
import { ru } from 'date-fns/locale'
const config: FormatConfig = {
locale: ru,
pattern: 'dd MMMM yyyy'
}
unknownПри обработке внешних данных предпочтительно использовать
unknown.
Пример:
function parseUnknownDate(
value: unknown
): Date | null {
if (typeof value === 'string') {
const parsed = new Date(value)
return isValid(parsed)
? parsed
: null
}
return null
}
Такой подход исключает неконтролируемые преобразования типов.