Skip to content

Display, format, sort, and filter time values correctly by using the time cell type. Edit times via the cell editor.

Overview

The time cell type lets you treat cell values as times: format how they are displayed and validate input. Handsontable supports two configurations: the object-style configuration using the native Intl.DateTimeFormat API (recommended), and a string-style configuration using Moment.js (deprecated).

Time cell type demo

In the following demo, the Start, Break start, and End columns use the time cell type with different formats: short style, custom format with hours, minutes, and seconds, and format with day period. Use the locale selector to see how each format varies by locale.

JavaScript
import { useRef, useState, useEffect } from 'react';
import { HotTable } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
23 collapsed lines
const localeOptions = [
{ value: 'ar-AR', label: 'Arabic (Global)' },
{ value: 'cs-CZ', label: 'Czech (Czechia)' },
{ value: 'de-CH', label: 'German (Switzerland)' },
{ value: 'de-DE', label: 'German (Germany)' },
{ value: 'en-US', label: 'English (United States)' },
{ value: 'es-MX', label: 'Spanish (Mexico)' },
{ value: 'fa-IR', label: 'Persian (Iran)' },
{ value: 'fr-FR', label: 'French (France)' },
{ value: 'hr-HR', label: 'Croatian (Croatia)' },
{ value: 'it-IT', label: 'Italian (Italy)' },
{ value: 'ja-JP', label: 'Japanese (Japan)' },
{ value: 'ko-KR', label: 'Korean (Korea)' },
{ value: 'lv-LV', label: 'Latvian (Latvia)' },
{ value: 'nb-NO', label: 'Norwegian Bokmal (Norway)' },
{ value: 'nl-NL', label: 'Dutch (Netherlands)' },
{ value: 'pl-PL', label: 'Polish (Poland)' },
{ value: 'pt-BR', label: 'Portuguese (Brazil)' },
{ value: 'ru-RU', label: 'Russian (Russia)' },
{ value: 'sr-SP', label: 'Serbian Latin (Serbia)' },
{ value: 'zh-CN', label: 'Chinese (Simplified, China)' },
{ value: 'zh-TW', label: 'Chinese (Traditional, Taiwan)' },
];
const ExampleComponent = () => {
const hotRef = useRef(null);
const dropdownRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
const [locale, setLocale] = useState('en-US');
useEffect(() => {
const handleOutsideClick = (e) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
setIsOpen(false);
}
};
const handleEscape = (e) => {
if (e.key === 'Escape') {
setIsOpen(false);
}
};
document.addEventListener('click', handleOutsideClick);
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('click', handleOutsideClick);
document.removeEventListener('keydown', handleEscape);
};
}, []);
const handleSelect = (value) => {
setLocale(value);
setIsOpen(false);
hotRef.current?.hotInstance?.updateSettings({ locale: value });
};
const selectedLabel = localeOptions.find((o) => o.value === locale)?.label;
7 collapsed lines
const data = [
{ shift: 'Morning', start: '09:00', breakStart: '12:00', end: '17:00' },
{ shift: 'Afternoon', start: '13:30', breakStart: '16:00', end: '21:00' },
{ shift: 'Night', start: '22:00', breakStart: '01:00', end: '06:00' },
{ shift: 'Split', start: '08:00', breakStart: '12:30', end: '20:00' },
{ shift: 'Short day', start: '10:00', breakStart: '13:00', end: '15:00' },
];
return (
<>
<div className="example-controls-container">
<div className="controls">
<div className="theme-dropdown" ref={dropdownRef}>
<button
className="theme-dropdown-trigger"
type="button"
aria-haspopup="listbox"
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
>
<span>{selectedLabel}</span>
<svg className="theme-dropdown-chevron" aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 9l6 6l6 -6"/></svg>
</button>
{isOpen && (
<ul className="theme-dropdown-menu" role="listbox">
{localeOptions.map((opt) => (
<li
key={opt.value}
role="option"
aria-selected={locale === opt.value}
onClick={() => handleSelect(opt.value)}
>
{opt.label}
</li>
))}
</ul>
)}
</div>
</div>
</div>
<HotTable
ref={hotRef}
data={data}
colHeaders={['Shift', 'Start', 'Break start', 'End']}
locale={locale}
31 collapsed lines
columns={[
{
type: 'text',
data: 'shift',
},
{
type: 'intl-time',
data: 'start',
timeFormat: {
timeStyle: 'short',
},
},
{
type: 'intl-time',
data: 'breakStart',
timeFormat: {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
},
},
{
type: 'intl-time',
data: 'end',
timeFormat: {
hour: 'numeric',
hourCycle: 'h12',
dayPeriod: 'short',
},
},
]}
columnSorting={true}
filters={true}
dropdownMenu={true}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;
TypeScript
import { useRef, useState, useEffect } from 'react';
import Handsontable from 'handsontable/base';
import { HotTable, HotTableRef } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
23 collapsed lines
const localeOptions: { value: string; label: string }[] = [
{ value: 'ar-AR', label: 'Arabic (Global)' },
{ value: 'cs-CZ', label: 'Czech (Czechia)' },
{ value: 'de-CH', label: 'German (Switzerland)' },
{ value: 'de-DE', label: 'German (Germany)' },
{ value: 'en-US', label: 'English (United States)' },
{ value: 'es-MX', label: 'Spanish (Mexico)' },
{ value: 'fa-IR', label: 'Persian (Iran)' },
{ value: 'fr-FR', label: 'French (France)' },
{ value: 'hr-HR', label: 'Croatian (Croatia)' },
{ value: 'it-IT', label: 'Italian (Italy)' },
{ value: 'ja-JP', label: 'Japanese (Japan)' },
{ value: 'ko-KR', label: 'Korean (Korea)' },
{ value: 'lv-LV', label: 'Latvian (Latvia)' },
{ value: 'nb-NO', label: 'Norwegian Bokmal (Norway)' },
{ value: 'nl-NL', label: 'Dutch (Netherlands)' },
{ value: 'pl-PL', label: 'Polish (Poland)' },
{ value: 'pt-BR', label: 'Portuguese (Brazil)' },
{ value: 'ru-RU', label: 'Russian (Russia)' },
{ value: 'sr-SP', label: 'Serbian Latin (Serbia)' },
{ value: 'zh-CN', label: 'Chinese (Simplified, China)' },
{ value: 'zh-TW', label: 'Chinese (Traditional, Taiwan)' },
];
const ExampleComponent = () => {
const hotRef = useRef<HotTableRef>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [locale, setLocale] = useState('en-US');
useEffect(() => {
const handleOutsideClick = (e: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setIsOpen(false);
}
};
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
setIsOpen(false);
}
};
document.addEventListener('click', handleOutsideClick);
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('click', handleOutsideClick);
document.removeEventListener('keydown', handleEscape);
};
}, []);
const handleSelect = (value: string) => {
setLocale(value);
setIsOpen(false);
hotRef.current?.hotInstance?.updateSettings({ locale: value } as Handsontable.GridSettings);
};
const selectedLabel = localeOptions.find((o) => o.value === locale)?.label;
7 collapsed lines
const data = [
{ shift: 'Morning', start: '09:00', breakStart: '12:00', end: '17:00' },
{ shift: 'Afternoon', start: '13:30', breakStart: '16:00', end: '21:00' },
{ shift: 'Night', start: '22:00', breakStart: '01:00', end: '06:00' },
{ shift: 'Split', start: '08:00', breakStart: '12:30', end: '20:00' },
{ shift: 'Short day', start: '10:00', breakStart: '13:00', end: '15:00' },
];
return (
<>
<div className="example-controls-container">
<div className="controls">
<div className="theme-dropdown" ref={dropdownRef}>
<button
className="theme-dropdown-trigger"
type="button"
aria-haspopup="listbox"
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
>
<span>{selectedLabel}</span>
<svg className="theme-dropdown-chevron" aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 9l6 6l6 -6"/></svg>
</button>
{isOpen && (
<ul className="theme-dropdown-menu" role="listbox">
{localeOptions.map((opt) => (
<li
key={opt.value}
role="option"
aria-selected={locale === opt.value}
onClick={() => handleSelect(opt.value)}
>
{opt.label}
</li>
))}
</ul>
)}
</div>
</div>
</div>
<HotTable
ref={hotRef}
data={data}
colHeaders={['Shift', 'Start', 'Break start', 'End']}
locale={locale}
31 collapsed lines
columns={[
{
type: 'text',
data: 'shift',
},
{
type: 'intl-time',
data: 'start',
timeFormat: {
timeStyle: 'short',
},
},
{
type: 'intl-time',
data: 'breakStart',
timeFormat: {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
},
},
{
type: 'intl-time',
data: 'end',
timeFormat: {
hour: 'numeric',
hourCycle: 'h12',
dayPeriod: 'short',
},
},
]}
columnSorting={true}
filters={true}
dropdownMenu={true}
height="auto"
autoWrapRow={true}
autoWrapCol={true}
licenseKey="non-commercial-and-evaluation"
/>
</>
);
};
export default ExampleComponent;

Use the time cell type

Use the object-style configuration by setting the type option to 'intl-time' and timeFormat to an object (recommended). The locale is controlled via the locale option.

// set the time cell type for the entire grid (Intl, recommended)
type="intl-time"
locale="en-US"
timeFormat={{
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
hour12: true
}}
// set the time cell type for a single column
columns={[{
type: 'intl-time',
locale: 'en-US',
timeFormat: { timeStyle: 'medium' }
}]}
// set the time cell type for a single cell
cell={[{
row: 0,
col: 2,
type: 'intl-time',
locale: 'en-US',
timeFormat: { hour: '2-digit', minute: '2-digit', hour12: true }
}]}

For intl-time cells, source data must be in 24-hour time format (HH:mm, HH:mm:ss, or HH:mm:ss.SSS) for times to work correctly. The timeFormat object only affects how times are displayed; sorting and filtering rely on the underlying value.

Format times

To control how times are displayed in cell renderers, use the timeFormat option.

Since Handsontable 17.0, the recommended approach is the object form of timeFormat with the intl-time cell type, which uses the native Intl.DateTimeFormat API. The locale is controlled separately via the locale option.

The timeFormat option accepts properties of Intl.DateTimeFormat options relevant to time. Use it with type: 'intl-time'.

<HotTable
columns={[{
type: 'intl-time',
locale: 'en-US',
timeFormat: {
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
hour12: true
}
}, {
type: 'intl-time',
locale: 'de-DE',
timeFormat: { timeStyle: 'medium' }
}]}
/>

Time-specific options

Style shortcuts:

PropertyPossible valuesDescription
timeStyle'full', 'long', 'medium', 'short'Time formatting style (hour, minute, second, timeZoneName)

Time component options:

PropertyPossible valuesDescription
hour'numeric', '2-digit'Hour representation
minute'numeric', '2-digit'Minute representation
second'numeric', '2-digit'Second representation
fractionalSecondDigits1, 2, 3Fraction-of-second digits
dayPeriod'narrow', 'short', 'long'Day period (e.g. “am”)
timeZoneName'long', 'short', 'shortOffset', 'longOffset', 'shortGeneric', 'longGeneric'Time zone display

Locale and other options:

PropertyPossible valuesDescription
localeMatcher'best fit' (default), 'lookup'Locale matching algorithm
timeZoneIANA time zone (e.g. 'UTC', 'America/New_York')Time zone for formatting
hour12true, false12-hour vs 24-hour time
hourCycle'h11', 'h12', 'h23', 'h24'Hour cycle
formatMatcher'basic', 'best fit' (default)Format matching algorithm

For a complete reference, see the timeFormat API documentation or MDN: Intl.DateTimeFormat.

Using string format with Moment.js (deprecated)

The time cell type with a string timeFormat is still supported but will be removed in next major release.

Deprecated options:

OptionDescriptionReplacement
timeFormat (string)Moment.js format (e.g. 'h:mm:ss a')Use intl-time with timeFormat object (see above)
correctFormatAuto-correct entered time to match formatMay be handled by valueParser and/or valueSetter options

Migration example:

// Before (deprecated)
columns: [{
type: 'time',
timeFormat: 'h:mm:ss a',
}]
// After (recommended)
columns: [{
type: 'intl-time',
locale: 'en-US',
timeFormat: {
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
hour12: true,
}
}]

Editor behavior

The timeFormat option controls how times are displayed in the cell. The editor may show the value in a normalized form; for intl-time, the underlying value remains in 24-hour format (HH:mm, HH:mm:ss, or HH:mm:ss.SSS).

Related guides

Configuration options

Core methods

Hooks