date — date primitives
Date math is the algorithmic body of date-picker and the shared kernel of calendar range selection. The native Date has a few classic traps (two-digit years, new Date(2023, 1, 30) rolling over); this module handles them all in one place.
Conventions
Section titled “Conventions”month is always 0-based, consistent with the native Date (0 = January, 11 = December). This is the most common source of misuse in this module — “March” coming from the UI must be passed in as 2.
Only the date part is compared: isSameDay / compareDate ignore hours, minutes, and seconds, matching the semantics of “a specific day is selected”.
Import
Section titled “Import”import { getDaysInMonth, getFirstDayOfMonth, isSameDay, isSameMonth, compareDate, isDateDisabled, formatDate, parseDate,} from '@lh-ui/share/core/shared/date'Function table
Section titled “Function table”| Function | Signature | Purpose |
|---|---|---|
getDaysInMonth |
(year: number, month: number) => number |
Number of days in a month (month is 0-based) |
getFirstDayOfMonth |
(year: number, month: number) => number |
The weekday of the month’s first day (0 = Sunday) |
isSameDay |
(a: Date, b: Date) => boolean |
Whether two dates are the same day |
isSameMonth |
(a: Date, b: Date) => boolean |
Whether two dates are in the same year and month |
compareDate |
(a: Date, b: Date) => number |
Compares the date parts, returns the difference in milliseconds |
isDateDisabled |
(date, minDate, maxDate, disabledDate) => boolean |
Whether a date is disabled |
formatDate |
(date: Date, format: string) => string |
Formats a date |
parseDate |
(str: string, format: string) => Date | null |
Parses a date string |
Two-digit year normalization
Section titled “Two-digit year normalization”getDaysInMonth and getFirstDayOfMonth normalize year ∈ [0, 99] internally (+1900). Why: new Date(99, 0, 1) is treated as 1999-01-01 in JS (the Date constructor’s special behavior for years 0-99); without normalization the month’s starting weekday would be wrong. Normal four-digit years are unaffected.
Evaluation order of isDateDisabled
Section titled “Evaluation order of isDateDisabled”disabledDate?.(date) returns truthy → truedate earlier than minDate (minDate non-null) → truedate later than maxDate (maxDate non-null) → trueotherwise → falseWhen minDate / maxDate / disabledDate is null, that condition is skipped — “only a lower bound” and “only a custom test” both work. The order is deliberate: the custom test wins first, so disabledDate means “these dates are unselectable regardless of the range” (holidays, dates with zero stock).
Tokens of formatDate / parseDate
Section titled “Tokens of formatDate / parseDate”Supported fixed-width tokens: yyyy / MM / dd / HH / mm / ss.
- Any order:
'MM/dd/yyyy'and'yyyy-MM-dd HH:mm'both work. - Every occurrence is replaced: both
ddin'yyyy-MM-dd'are replaced with the same value. - The year is zero-padded to 4 digits:
formatDate(new Date(23, 0, 1), 'yyyy')→'0023'. - The two functions are symmetric:
parseDate(formatDate(d, fmt), fmt)should restore the same day (same month, year, and day; hours, minutes and seconds depend on the tokens present).
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('invalid', 'yyyy-MM-dd') // nullThe two cases where parseDate returns null
Section titled “The two cases where parseDate returns null”- The string does not match the format:
parseIntyieldsNaN. - The value is not legal:
2023-02-30would be silently rolled to March 2 by the nativeDate— the function constructs it and then reads it back to verify, and since it rolled, the value is illegal andnullis returned. This is key for date-picker input validation: when the user types a date that does not exist, the component can recognize it.
Defaults for missing tokens: year = the current year, month = 0, day = 1, hours / minutes / seconds = 0. So parseDate('10-12', 'MM-dd') gives you “this year’s October 12, 00:00:00”.
Usage examples
Section titled “Usage examples”Rendering a calendar panel
Section titled “Rendering a calendar panel”// the year and month of the current viewconst year = 2026const month = 9 // 0-based = October
const days = getDaysInMonth(year, month) // 31const firstDay = getFirstDayOfMonth(year, month) // 4 = Thursday → prepend 4 blank cellsRange disabling
Section titled “Range disabling”const min = new Date(2026, 0, 1)const max = new Date(2026, 11, 31)
isDateDisabled(new Date(2026, 5, 1), min, max, null) // falseisDateDisabled(new Date(2025, 5, 1), min, max, null) // true (earlier than min)isDateDisabled(new Date(2026, 5, 1), null, null, (d) => // true (custom test) d.getDay() === 0) // Sundays are unselectableWiring into a component
Section titled “Wiring into a component”// inside the date-picker componentfunction 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 = 'Invalid date format' return } modelValue.value = parsed}Caveats
Section titled “Caveats”- month is 0-based: when the UI layer displays “October”, pass
9. date-picker’s panel state is 0-based internally and only+1at render time. year >= 10000is not supported: it exceeds the expressive power of the fixed-width tokens, andformatDateproduces unexpected results. This is not a practical limitation (input[type=date]also only supports up to 9999).compareDatereturns a millisecond difference: the difference ofDate.UTC(...), positive meaningais later. Only the date part is compared, not the time of day.isDateDisabled’sdisabledDateis called for every cell on every render: 42 calendar cells × every view change — do not do heavy computation here; caching is the UI layer’s responsibility.- Time zones: everything is based on the local time zone’s
Date, with no UTC conversion. Cross-timezone applications should normalize before passing values in, in the UI layer.
@lh-ui/share/core/date → getDaysInMonth( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue