Skip to main content

Date & Time Pickers

Three locale-aware input controls in the io-oidis-usercontrols package for picking dates and times:

  • DatePicker — a single date or a from/to date range, rendered as a Zag.js headless calendar.
  • DateTimePicker — a single input whose panel combines a calendar grid and a time wheel, resolving the selection into a ZonedDateTime.
  • TimePicker — a standalone hours/minutes/seconds wheel in a Bootstrap dropdown (no Zag.js).

All three extend GuiCommons, are created with new (no constructor arguments), and bind via data-oidis-bind. Configuration is done through fluent getter/setter methods (call with an argument to set, with none to get). Every one of them defaults to the Czech locale ("cs-CZ"), which gives DD.MM.YYYY, 24-hour time, Monday-first weeks, Czech weekday/month names, and Czech panel button labels (Vymazat / Dnes / Potvrdit).

Import:

import { DatePicker } from "@io-oidis-usercontrols/Io/Oidis/UserControls/Bootstrap/UserControls/DatePicker.js";
import { DateTimePicker } from "@io-oidis-usercontrols/Io/Oidis/UserControls/Bootstrap/UserControls/DateTimePicker.js";
import { TimePicker } from "@io-oidis-usercontrols/Io/Oidis/UserControls/Bootstrap/UserControls/TimePicker.js";
Configure before the first render

All configuration methods (Locale, TimeZone, SelectionMode, MinDate, TimeFormat, Step, UseNative, …) must be called before the control first renders — in the owning panel's constructor or in the controller's beforeLoad(). They are read once when the internal calendar/wheel machine starts; setting them after the panel is visible has no effect.

Enabling Zag.js assets in a consuming application

DatePicker and DateTimePicker drive the Zag.js date-picker state machine and load it lazily at runtime. io-oidis-usercontrols ships the Zag.js bundle and its full license registration through bin/project/configs/zagjs.jsonp (a single self-contained ES module downloaded from Hub as zag-bundle-<version>.zip), so a consumer only has to import that config in its package.conf.jsonp:

imports: {
zagjs: "dependencies/io-oidis-usercontrols/bin/project/configs/zagjs.jsonp"
}

Then run oidis install. This downloads the dependency, copies the bundle to resource/libs/zag/zag-bundle.min.js, serves it (requestPatterns) and registers the licenses. ZagLoader injects the bundle into the page head on the first time a picker opens; if the file is missing at runtime it logs and throws a clear "run oidis install" error.

TimePicker needs no Zag.js assets. Setting UseNative(true) on any of the three also needs none — the native <input> fallback is plain HTML.

DatePicker​

A single date or a from/to range on one shared calendar (the range mode shows a live hover preview between the two endpoints). The panel never auto-closes — Potvrdit / clicking outside commits the selection, so a just-picked date can still be adjusted first.

Basic Usage​

// Panel
public readonly deliveryDate : DatePicker;

// Constructor
this.deliveryDate = new DatePicker();
this.deliveryDate.Locale("cs-CZ");
<!-- innerHtml() -->
<div data-oidis-bind="${this.deliveryDate}"></div>
// Controller — beforeLoad()
$instance.deliveryDate.MinDate("2026-01-01");
$instance.deliveryDate.MaxDate("2026-12-31");
$instance.deliveryDate.Value("2026-06-03"); // pre-select an ISO date
$instance.deliveryDate.OnChange(() : void => {
const picked : Date = $instance.deliveryDate.Data();
if (!ObjectValidator.IsEmptyOrNull(picked)) {
this.form.date = picked;
}
});

Value($iso) takes an ISO "YYYY-MM-DD" string in single mode; Value("") clears the selection. The getter returns the locale-formatted display string. Read the committed value from Data() (a native Date, or undefined when nothing is selected yet) rather than parsing the formatted Value() getter.

OnChange fires once for every user-driven change of the single-mode value: a day picked in the calendar, and a date typed (or erased) in the text field, committed when the field loses focus. It does not fire for a programmatic Value() call, nor for a blur that leaves the value unchanged — so it is safe to save from it directly. Erasing the text clears the selection (Data() returns undefined).

The locale is authoritative for the text field

The visible field is a plain text input whose value is formatted and parsed in the picker's configured Locale() through Intl, independently of the browser/OS locale. With Locale("cs-CZ") a single date always shows 03. 06. 2026 (DD. MM. YYYY) on every machine, and typing 3.6.2026 is parsed back using the locale's own day/month/year field order — a value typed on any browser round-trips correctly. There is no separate format-pattern option; Locale() is the single source of truth for both display and typed-input parsing. While empty, the field shows a locale-ordered placeholder mask (dd. mm. rrrr for cs-CZ, měsíc rrrr in month-granularity mode), since the text input carries no native format hint of its own.

Range Mode​

// Constructor
this.period = new DatePicker();
this.period.SelectionMode("range");
this.period.Locale("cs-CZ");
// Controller
$instance.period.SetRange("2026-06-01", "2026-06-30"); // programmatic range
const range : Date[] = $instance.period.DataRange(); // [start, end]
Range mode reads and writes differently

In "range" mode the Value() setter is a no-op — use SetRange($startIso, $endIso) to set a range programmatically, and read the result from DataRange() (a two-item [start, end] array of native Dates), not from Data(). Endpoints not yet selected come back as undefined entries. The native HTML date input cannot do ranges, so UseNative(true) combined with range mode falls back to a plain text field.

Month Granularity​

Set Granularity(DatePickerGranularity.MONTH) (before the first render) to turn DatePicker into a month picker: the panel opens directly on the month grid, and clicking a month commits day-1 of that month and closes the panel — it does not navigate into the day view. Granularity values come from the DatePickerGranularity enum (DatePickerGranularity.DAY / DatePickerGranularity.MONTH), exported from the same DatePicker module.

// Import
import { DatePicker, DatePickerGranularity } from "@io-oidis-usercontrols/Io/Oidis/UserControls/Bootstrap/UserControls/DatePicker.js";

// Constructor
this.reportMonth = new DatePicker();
this.reportMonth.Granularity(DatePickerGranularity.MONTH);
this.reportMonth.Locale("cs-CZ");
// Controller — derive YYYY-MM from the committed day-1 date
const picked : Date = $instance.reportMonth.Data();
if (!ObjectValidator.IsEmptyOrNull(picked)) {
this.form.month = `${picked.getFullYear()}-${String(picked.getMonth() + 1).padStart(2, "0")}`;
}

Because the commit is day-1 of the chosen month, Value() and Data() return that day-1 date — derive the YYYY-MM yourself from getFullYear() / getMonth() + 1. The field is rendered read-only in month mode (pick-only, no keyboard typing) and its display shows the localized "month year", e.g. červenec 2026 for cs-CZ.

Granularity is a DatePicker-only feature

Granularity applies only to DatePicker — DateTimePicker and TimePicker have no granularity option. It is implemented in the shared calendar engine but gated behind DatePickerGranularity.MONTH, so the default DatePickerGranularity.DAY behavior is unchanged.

API Reference​

MethodPurpose
Value($iso?)Get the locale-formatted display string; set from an ISO "YYYY-MM-DD" (single mode only, "" clears; no-op in range mode).
Data()Selected date as a native Date (single mode), or undefined when nothing is selected.
DataRange()Selected range as a [start, end] array of native Dates (range mode); undefined entries for endpoints not yet picked.
SelectionMode($mode?)Get/set "single" (default) or "range".
Granularity($value?)Get/set a DatePickerGranularity enum value — DatePickerGranularity.DAY (default) or DatePickerGranularity.MONTH. In MONTH mode the panel is a month picker committing day-1 of the chosen month; the field is read-only and shows the localized "month year".
SetRange($startIso, $endIso)Set the range programmatically (range mode only).
FocusDate($iso)Navigate the calendar to the month containing an ISO date without selecting it.
MinDate($iso?) / MaxDate($iso?)Get/set the earliest/latest selectable date.
Locale($bcp47?)Get/set the BCP-47 locale (default "cs-CZ") driving weekday/month names, first day of week, display format, and button labels.
TimeZone($iana?)Get/set the IANA time zone used by the underlying date machine (default "UTC").
Enabled($bool?) / Readonly($bool?)Get/set enabled and readonly state.
OnChange($cb)Register a callback fired on a genuine user-driven change (never from a programmatic Value call).
OnOpen($cb)Register a callback fired each time the calendar panel opens.
UseNative($bool?)Get/set the native <input type="date"> fallback.

DateTimePicker​

A single input whose one panel stacks a calendar grid on top of a time wheel. Data() resolves the picked wall-clock date+time into an @internationalized/date ZonedDateTime against the configured TimeZone(). Like DatePicker, the panel never auto-closes — Potvrdit / clicking outside commits.

Basic Usage​

// Panel
public readonly arrival : DateTimePicker;

// Constructor
this.arrival = new DateTimePicker();
this.arrival.Locale("cs-CZ");
this.arrival.TimeZone("Europe/Prague");
<!-- innerHtml() -->
<div data-oidis-bind="${this.arrival}"></div>
// Controller — beforeLoad()
$instance.arrival.Step(15); // 15-minute slots
$instance.arrival.TimeFormat("HH:mm");
$instance.arrival.Value("2026-06-03T19:35"); // "YYYY-MM-DDTHH:mm[:ss]"
$instance.arrival.OnChange(() : void => {
const zoned : any = $instance.arrival.Data();
if (!ObjectValidator.IsEmptyOrNull(zoned)) {
this.form.arrival = zoned.toDate(); // JS Date from the ZonedDateTime
}
});

Set TimeZone("Europe/Prague") alongside Locale("cs-CZ") for Czech data, because Data() resolves against that IANA zone.

The locale is authoritative for the text field

The visible text field is formatted and parsed in the picker's configured Locale() through Intl, independently of the browser/OS locale (the same behavior DatePicker has for its date display). With Locale("cs-CZ") the field always shows 03. 06. 2026 19:35 (DD. MM. YYYY, 24-hour) on every machine, and typing 3.6.2026 19:35 is parsed back using the locale's own day/month/year field order and its 12h/24h choice — a value typed on any browser round-trips correctly. There is no separate format-pattern option; Locale() is the single source of truth. This is the whole point of the control over a native <input type="datetime-local">, whose display format the browser dictates and cannot be forced. While empty, the field shows a locale-ordered placeholder mask (dd. mm. rrrr hh:mm for cs-CZ), since the text input carries no native format hint of its own.

Reading and clearing DateTimePicker

Data() returns an @internationalized/date ZonedDateTime, not a native Date — call .toDate() on it to get a JS Date. It returns undefined until a date is selected. Also, Value("") is a no-op (an empty string has nothing to parse) — call Clear() to reset the field and its displayed value.

API Reference​

MethodPurpose
Value($s?)Get the locale-formatted display string; set from "YYYY-MM-DDTHH:mm[:ss]". Value("") is a no-op — use Clear().
Clear()Clear the selection and the displayed value (the way to reset the field).
Data()Selected date+time as a ZonedDateTime resolved against TimeZone(), or undefined. Call .toDate() for a JS Date.
MinDate($iso?) / MaxDate($iso?)Get/set the earliest/latest selectable date.
MinTime($hhmm?) / MaxTime($hhmm?)Get/set the earliest/latest selectable time ("HH:mm"); wheel selections are clamped.
Step($minutes?)Get/set the minutes-wheel granularity (default 1).
TimeFormat($fmt?)Get/set which units are shown: "HH", "mm", "ss", "HH:mm", "mm:ss", "HH:mm:ss".
Locale($bcp47?)Get/set the BCP-47 locale (default "cs-CZ") driving calendar labels, the text-field format, and the 12h/24h choice.
TimeZone($iana?)Get/set the IANA time zone (default "UTC") the selection is resolved against in Data().
Enabled($bool?) / Readonly($bool?)Get/set enabled and readonly state.
OnChange($cb)Register a callback fired on a genuine user-driven change to either the calendar or the wheel.
UseNative($bool?)Get/set the native <input type="datetime-local"> fallback.

TimePicker​

A standalone time picker with a scrollable, native-picker-style wheel column per active unit (hours / minutes / seconds). It opens through plain Bootstrap dropdown markup and needs no Zag.js. The field is editable by typing directly — a typed value is committed on blur or Enter and snaps back to the canonical locale format.

Basic Usage​

// Panel
public readonly openingTime : TimePicker;

// Constructor
this.openingTime = new TimePicker();
this.openingTime.Locale("cs-CZ");
this.openingTime.Format("HH:mm");
<!-- innerHtml() -->
<div data-oidis-bind="${this.openingTime}"></div>
// Controller — beforeLoad()
$instance.openingTime.Step(5);
$instance.openingTime.Value("08:30"); // 24-hour canonical in
$instance.openingTime.OnChange(() : void => {
const time : {hours : number; minutes : number; seconds : number} =
$instance.openingTime.Data();
this.form.openMinutes = time.hours * 60 + time.minutes;
});

Value($s) takes a 24-hour canonical "HH:mm" or "HH:mm:ss" string and returns a locale-formatted string (12h or 24h per Locale()). Data() always returns a 24-hour {hours, minutes, seconds} object, regardless of the display format.

API Reference​

MethodPurpose
Value($s?)Get the locale-formatted display string; set from 24-hour canonical "HH:mm" / "HH:mm:ss".
Data()Selected time as a 24-hour {hours, minutes, seconds} object.
Format($fmt?)Get/set which units are shown: "HH", "mm", "ss", "HH:mm", "mm:ss", "HH:mm:ss".
Step($minutes?)Get/set the minutes-wheel granularity (default 1).
MinTime($hhmm?) / MaxTime($hhmm?)Get/set the earliest/latest selectable time; wheel selections are clamped.
Locale($bcp47?)Get/set the BCP-47 locale (default "cs-CZ") driving the 12h/24h display.
TimeZone($iana?)Get/set the associated IANA time zone (stored so a composing DateTimePicker can resolve a zoned timestamp; the picker itself is zone-agnostic).
Enabled($bool?) / Readonly($bool?)Get/set enabled and readonly state.
OnChange($cb)Register a callback fired on a genuine user-driven change.
UseNative($bool?)Get/set the native <input type="time"> fallback.

Czech Configuration​

"cs-CZ" is the default Locale() for all three controls, so a Czech-market app usually needs no locale call at all. It yields DD.MM.YYYY, 24-hour time, Monday-first weeks, Czech weekday/month names, and Czech panel buttons (Vymazat / Dnes / Potvrdit). For DateTimePicker also set TimeZone("Europe/Prague"), since its Data() resolves the wall-clock selection into a ZonedDateTime against that IANA zone:

picker.Locale("cs-CZ");
picker.TimeZone("Europe/Prague");

Reading Committed Values​

Always read the committed value from the typed Data() accessor, not from the formatted Value() getter:

  • DatePicker.Data() → native Date (single mode); DataRange() → [start, end] (range mode).
  • DateTimePicker.Data() → ZonedDateTime (call .toDate() for a native Date).
  • TimePicker.Data() → {hours, minutes, seconds}.

An empty selection surfaces as undefined (or undefined entries in a DataRange() array). These controls expose no Placeholder, Required, or validate API — validate the value yourself against Data().

Native Fallback​

Calling UseNative(true) swaps the custom UI for the browser's native control:

ControlNative elementNotes
DatePicker<input type="date">Range selection is not supported natively (falls back to a plain text field).
DateTimePicker<input type="datetime-local">The forced-locale text-field display is not available — the browser controls the format.
TimePicker<input type="time">Always shows hours+minutes (seconds when Format includes "ss"); per-unit-only formats are not supported.

Native mode needs no Zag.js assets.

Architecture / Internals​

The three public controls are thin Bootstrap wrappers over a set of shared internal building blocks — ZagCalendarController (the Zag.js calendar state machine and panel rendering, used by DatePicker and DateTimePicker), TimeWheelController (the hours/minutes/seconds wheel state and UI, used by TimePicker and DateTimePicker), DateTimePickerExtras (the time-wheel slice embedded into DateTimePicker's calendar panel), and ZagLoader (the lazy Zag.js bundle loader). These are internal — not part of the public API — and should never be imported or driven directly. Use the three controls above.

Like DropDownList, each public control exposes a protected triggerCss and an overridable innerHtml(), so a solution can subclass it for project-specific trigger-button styling.


See also: User Controls | Autocomplete | Data Binding | Events and Validation