Перейти к содержимому

date —— примитивы дат

Вычисления с датами — основная алгоритмическая часть date-picker и общее ядро интервального выбора в calendar. У нативного Date есть несколько классических ловушек (двузначный год, переполнение при new Date(2023, 1, 30)), которые этот модуль обрабатывает единообразно.

month всегда начинается с 0, как у нативного Date (0 = январь, 11 = декабрь). Это самый частый источник ошибок при использовании модуля — «март», полученный из UI, нужно передавать как 2.

Сравнивается только часть даты: isSameDay / compareDate не смотрят на часы, минуты, секунды, что соответствует семантике «выбрать конкретный день».

import {
getDaysInMonth,
getFirstDayOfMonth,
isSameDay,
isSameMonth,
compareDate,
isDateDisabled,
formatDate,
parseDate,
} from '@lh-ui/share/core/shared/date'
Функция Сигнатура Назначение
getDaysInMonth (year: number, month: number) => number количество дней в месяце (month с 0)
getFirstDayOfMonth (year: number, month: number) => number день недели первого дня месяца (0 = воскресенье)
isSameDay (a: Date, b: Date) => boolean один и тот же ли день
isSameMonth (a: Date, b: Date) => boolean один ли тот же год и месяц
compareDate (a: Date, b: Date) => number сравнение части даты, возвращает разность в миллисекундах
isDateDisabled (date, minDate, maxDate, disabledDate) => boolean отключена ли дата
formatDate (date: Date, format: string) => string форматирование даты
parseDate (str: string, format: string) => Date | null разбор строки даты

getDaysInMonth и getFirstDayOfMonth внутренне нормализуют year ∈ [0, 99] (+1900). Почему: new Date(99, 0, 1) в JS воспринимается как 1999-01-01 (особое поведение конструктора Date для годов 0–99), и без нормализации день недели первого дня месяца вычисляется неверно. Обычные четырёхзначные годы не затрагиваются.

disabledDate?.(date) возвращает истину → true
дата раньше minDate (minDate не null) → true
дата позже maxDate (maxDate не null) → true
иначе → false

При null для minDate / maxDate / disabledDate соответствующее условие пропускается — «только верхняя граница» и «только пользовательская оценка» работают корректно. Порядок выбран намеренно: пользовательская оценка имеет приоритет, поэтому семантика disabledDate — «независимо от интервала, эти даты недоступны для выбора» (праздничные дни, даты с нулевым остатком).

Поддерживаются токены фиксированной ширины: yyyy / MM / dd / HH / mm / ss.

  • Любой порядок: 'MM/dd/yyyy', 'yyyy-MM-dd HH:mm' — любой вариант допустим.
  • Многократное вхождение заменяется целиком: обе dd в 'yyyy-MM-dd' будут заменены одним и тем же значением.
  • Год дополняется нулями до 4 знаков: formatDate(new Date(23, 0, 1), 'yyyy') → '0023'.
  • Функции симметричны: parseDate(formatDate(d, fmt), fmt) должно вернуть тот же день (тот же месяц, тот же год; часы, минуты, секунды — в зависимости от токенов).
formatDate(new Date(2026, 9, 12), 'yyyy-MM-dd') // '2026-10-12'
formatDate(new Date(2026, 9, 12, 14, 5), 'yyyy-MM-dd HH:mm') // '2026-10-12 14:05'
parseDate('2026-10-12', 'yyyy-MM-dd') // Date(2026, 9, 12)
parseDate('неверный', 'yyyy-MM-dd') // null
  1. Строка не соответствует формату: parseInt даёт NaN.
  2. Некорректное значение: 2023-02-30 будет нативным Date молча сдвинуто на 2 марта — функция после конструирования делает обратную проверку, и если обнаруживает сдвиг, признаёт значение недопустимым и возвращает null. Это ключ проверки поля ввода в date-picker: когда пользователь вводит несуществующую дату, компонент способен это распознать.

Значения по умолчанию для отсутствующих токенов: год = текущий, месяц = 0, день = 1, часы / минуты / секунды = 0. Поэтому parseDate('10-12', 'MM-dd') даёт «12 октября текущего года 00:00:00».

// Год и месяц текущего представления
const year = 2026
const month = 9 // с 0 = октябрь
const days = getDaysInMonth(year, month) // 31
const firstDay = getFirstDayOfMonth(year, month) // 4 = четверг → впереди 4 пустых ячейки
const min = new Date(2026, 0, 1)
const max = new Date(2026, 11, 31)
isDateDisabled(new Date(2026, 5, 1), min, max, null) // false
isDateDisabled(new Date(2025, 5, 1), min, max, null) // true (раньше min)
isDateDisabled(new Date(2026, 5, 1), null, null, (d) => // true (пользовательская оценка)
d.getDay() === 0) // воскресенье недоступно для выбора
// внутри компонента date-picker
function isDisabled(date: Date) {
return isDateDisabled(date, props.minDate, props.maxDate, props.disabledDate)
}
function onInput(value: string) {
const parsed = parseDate(value, props.format)
if (parsed === null) {
errorMessage.value = 'Неверный формат даты'
return
}
modelValue.value = parsed
}
  • month начинается с 0: когда UI-слой показывает «Октябрь», нужно передать 9. Состояние панели date-picker внутренне использует отсчёт с 0 и добавляет +1 только при рендере.
  • Год >= 10000 не поддерживается: это выходит за выразительную способность токенов фиксированной ширины, formatDate выдаст непредсказуемый результат. На практике это не ограничение (также input[type=date] поддерживает только до 9999 года).
  • compareDate возвращает разность в миллисекундах: это разность Date.UTC(...), положительное число означает, что a позже. Сравнивается только часть даты, без времени суток.
  • disabledDate у isDateDisabled вызывается при каждом рендере каждой ячейки: 42 ячейки календаря × каждое изменение представления — не делайте здесь тяжёлых вычислений, кэширование лежит на UI-слое.
  • Часовые пояса: всё основано на Date в локальном часовом поясе, преобразования в UTC не выполняются. Для кросс-поясных приложений унификацию выполняет UI-слой до передачи значений.
@lh-ui/share/core/date → getDaysInMonth( )
返回值
(填好参数后点运行)

Copyright © 2024-2026 FengMo WenXue