Date math
addDays, addMonths, addYears,
startOfMonth, endOfMonth, getDaysInMonth
@litopis/dom and @litopis/core
Public APIs for @litopis/core and @litopis/dom.
Litopis represents a calendar date without a time or time zone. Months and days are
one-based; supported years are 1 through 9999.
interface DateValue {
readonly day: number;
readonly month: number;
readonly year: number;
}
DateValue is the default public value. Use
valueAs: "date" when the application works with native
Date instances.
A valid value has year: 1…9999, month: 1…12 and a day valid
for that month; for example { year: 2026, month: 7, day: 29 }.
interface DateRange {
readonly start: DateValue | null;
readonly end: DateValue | null;
}
The calendar keeps date-only values internally. The controller can expose the same selection in the form that fits the consuming code.
const picker = createDatePicker(root, {
format: "dd.mm.yyyy",
valueAs: "date",
});
picker.getValue(); // Date | null
picker.getInputValue(); // "29.02.2024"
picker.getISOValue(); // "2024-02-29"
getInputValue() is the exact value shown in the field, including the
range separator. getISOValue() is always an ISO value for forms and
storage. With valueAs: "date", selected, range,
setDate() and setRange() also accept local
Date instances. Litopis reads their local year, month and day and returns
local-noon Date instances so a calendar day is not shifted at a
daylight-saving transition.
selection and valueAs determine the return type together.
Pass literals directly, or preserve them with satisfies when options are
stored first.
import {
createDatePicker,
type DatePickerOptions,
type DatePickerRange,
type DateValue,
} from "@litopis/dom";
const dateValuePicker = createDatePicker(root);
const dateValue: DateValue | null = dateValuePicker.getValue();
const nativeDatePicker = createDatePicker(root, { valueAs: "date" });
const nativeDate: Date | null = nativeDatePicker.getValue();
const rangeOptions = {
selection: "range",
valueAs: "date",
} satisfies DatePickerOptions<"date", "range">;
const rangePicker = createDatePicker(root, rangeOptions);
const range: DatePickerRange<"date"> = rangePicker.getValue();
| Options | getValue() |
|---|---|
| Default | DateValue | null |
valueAs: "date" |
Date | null |
selection: "range" |
{ start: DateValue | null; end: DateValue | null } |
selection: "range", valueAs: "date" |
{ start: Date | null; end: Date | null } |
Month and year selections represent complete periods in values sent to an application or a form. Field text remains at the selected precision.
| Granularity | Single value / range start | Range end | Field text |
|---|---|---|---|
"day" |
The selected day | The selected day | Configured date format |
"month" |
First day of the month | Last day of the month; February 2024 ends on 2024-02-29 |
YYYY-MM or MM.YYYY |
"year" |
January 1 | December 31 | YYYY |
For example, selecting February through March 2024 yields
2024-02-01/2024-03-31 from getISOValue(), while a field with
format: "dd.mm.yyyy" displays 02.2024 – 03.2024.
Mounts a date field and calendar into an existing element.
function createDatePicker(
root: HTMLElement,
options?: DatePickerOptions,
): DatePickerController;
Literal unions below are the complete set of accepted variants. Text options accept
user-facing text; locale accepts a supported BCP 47 tag such as "uk-UA".
| Option | Type | Default | Description |
|---|---|---|---|
mode |
"inline" | "popover" |
"inline" |
"inline" stays in document flow; "popover" opens from
the field.
|
selection |
"single" | "range" |
"single" |
"single" chooses one period; "range" chooses start
then end.
|
granularity |
"day" | "month" | "year" |
"day" |
Chooses days, months or years. Period output uses real calendar boundaries; field text remains at the selected precision. |
range |
{ start: DateValue | Date | null; end: DateValue | Date | null }
|
{ start: null, end: null } |
Initial range for selection: "range"; a partial range may have only
start. Endpoints are ordered chronologically.
|
layout |
"single" | "split" |
"split" for range |
"single" uses one start – end field;
"split" uses From and To fields. It applies only to ranges.
|
panels |
1 | 2 | "auto" |
"auto" for range |
1 shows one month; 2 shows adjacent months;
"auto" uses two when the container is at least 640px wide.
|
closeOnSelect |
boolean |
true |
true closes a popover after selection; false leaves it
open. Inline calendars stay open.
|
clearButton |
boolean |
false |
true adds a Clear footer action; false omits it.
|
clearLabel |
string |
"Clear" |
Any visible text for Clear; for example "Reset dates". |
firstDayOfWeek |
0 | 1 | 2 | 3 | 4 | 5 | 6 |
Locale-derived |
Overrides locale: 0 Sunday through 6 Saturday; for
example 1 starts weeks on Monday.
|
format |
"yyyy-mm-dd" | "dd.mm.yyyy" | "mm/dd/yyyy" |
"yyyy-mm-dd" |
Visible mask and parsing: "yyyy-mm-dd" → 2026-07-29;
"dd.mm.yyyy" → 29.07.2026; "mm/dd/yyyy" → 07/29/2026.
|
label |
string | { start: string; end: string }; for example
"Travel date"
|
"Date"; "Start" / "End" for split |
Visible label and accessible name. A range object labels split fields; for
example { start: "Departure", end: "Return" }.
|
locale |
Supported BCP 47 tag; for example "uk-UA" |
Browser locale |
Omit it for the browser language. "uk-UA" localizes labels; invalid
tags fall back to "en-US".
|
min |
DateValue |
— |
Inclusive earliest selectable date; for example
{ year: 2026, month: 7, day: 1 }.
|
max |
DateValue |
— |
Inclusive latest selectable date; for example
{ year: 2026, month: 7, day: 31 }.
|
onValueChange |
(value: DateValue | Date | null) => void |
— |
Called after a single value changes. Its value follows valueAs; use
it to synchronize application state.
|
valueAs |
"date-value" | "date" |
"date-value" |
Chooses the representation returned by selection APIs and change callbacks: a
DateValue object or a local native Date.
|
selected |
DateValue | Date | null |
null |
Initial single value. Use null for none; use range for
range mode.
|
onRangeChange |
(range) => void |
— |
Called after either endpoint changes. The endpoint type follows
valueAs; the completed end is expanded to the final day of a
selected month or year.
|
name |
string | { start: string; end: string }; for example
"period"
|
— |
Native FormData name. A split range derived from
"stay" submits stay[start] and stay[end];
pass an object only when the endpoints need different names. A one-field range
serializes full ISO boundaries, for example 2024-02-01/2024-03-31.
|
outsideDays |
boolean |
true |
true shows subdued adjacent-month dates; false leaves
their cells empty.
|
season |
boolean |
false |
true adds a localized season beside the caption;
false omits it.
|
todayButton |
boolean |
false |
true adds a Today footer action; false omits it.
|
size |
"compact" | "comfortable" |
"compact" |
"compact" uses 40px controls; "comfortable" uses at
least 44px controls.
|
today |
DateValue |
Local current date |
Overrides the local current date; for example
{ year: 2026, month: 7, day: 29 } for deterministic tests.
|
todayLabel |
string |
"Today" |
Any visible text for Today; for example "Jump to today". |
| Method | Result | Description |
|---|---|---|
open() |
void |
Opens a popover calendar. |
close() |
void |
Closes a popover calendar and restores its closed state. |
toggle() |
void |
Toggles popover visibility. |
getValue() |
DateValue | Date | null, or { start, end } for range
|
Returns the selected value. Its shape follows selection; its
endpoint type follows valueAs.
|
getInputValue() |
string |
Returns the formatted field text; a range uses start – end. |
getISOValue() |
string |
Returns ISO YYYY-MM-DD, a complete range as
start/end with complete period boundaries, or an empty string.
|
setDate(value) |
void |
Sets or clears selection and updates the input. |
setRange(range) |
void |
Sets or clears range endpoints and synchronizes the fields and native form values. |
setVisibleMonth(value) |
void |
Changes the month currently displayed. |
setOptions(options) |
void |
Replaces options without remounting; selection is preserved unless supplied. |
goToToday() |
void |
Focuses and reveals today within configured bounds. |
destroy() |
void |
Removes listeners, generated DOM and Litopis state attributes. |
Mounts only the masked text field. It is useful when the application provides its own calendar surface or needs typed date entry without a picker.
const field = createDateField(root, {
format: "dd.mm.yyyy",
label: "Invoice date",
min: { day: 1, month: 1, year: 2026 },
});
field.getDate();
field.getISOValue();
field.isValid();
field.setDate({ day: 16, month: 7, year: 2026 });
field.destroy();
| Option | Accepted value | Default | Effect |
|---|---|---|---|
format |
"yyyy-mm-dd" | "dd.mm.yyyy" | "mm/dd/yyyy" |
"yyyy-mm-dd" |
Input mask and parser; for example "dd.mm.yyyy" accepts 29.07.2026.
|
label |
Visible text; for example "Invoice date" |
"Date" |
Visible label and accessible name. |
min |
DateValue |
— | Inclusive lower bound; dates before it are invalid. |
max |
DateValue |
— | Inclusive upper bound; dates after it are invalid. |
value |
DateValue | null |
null |
Initial value; null starts with an empty field. |
@litopis/core has no DOM dependency and can be used for custom renderers,
server-side validation and deterministic calendar calculations.
addDays, addMonths, addYears,
startOfMonth, endOfMonth, getDaysInMonth
compareDates, clampDate, isSameDate,
isValidDateValue, isDateDisabled
formatDate, toIsoDate, toLocalDate,
getToday
createCalendarState, createCalendarGrid,
moveFocus, focusDate, selectDate,
selectFocusedDate