Handsontable 日期单元格类型(date / intl-date)完全指南:格式化、校验、排序与过滤
2026/9/20 13:25:58 网站建设 项目流程

Handsontable 日期单元格类型(date / intl-date)完全指南:格式化、校验、排序与过滤

【免费下载链接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡项目地址: https://gitcode.com/gh_mirrors/ha/handsontable

导读

本文以 Handsontable 官方文档 date-cell-type.md 为核心,系统讲解日期单元格类型(date/intl-date)的配置与实战:如何用Intl.DateTimeFormat选项对象控制显示格式、如何保证 ISO 8601 源数据与校验、原生日期选择器(date picker)的编辑行为,以及排序、过滤如何依赖底层 ISO 值。文中结合当前仓库的源码(如 intlDateType.ts、dateRenderer.ts、dateValidator.ts、dateEditor.ts)逐层拆解实现原理,读完你可以直接在项目中落地一个"显示本地化、存储标准化"的日期表格。

日期单元格类型概述

日期单元格类型(date cell type)让你的单元格值以日期的方式被对待:按照配置格式化显示、校验输入合法性,并在编辑时弹出交互式日期选择器。

在 Handsontable 中,日期相关的单元格类型有两个入口:

  • intl-date:基于原生Intl.DateTimeFormatAPI 的推荐类型(Handsontable 18.0 起主推)。
  • date:与intl-date共享同一套渲染、校验、编辑与格式化逻辑的别名类型。

两者配合 ISO 8601 日期字符串(YYYY-MM-DD)使用:源数据必须是 ISO 8601 格式,显示格式则由dateFormat对象独立控制。这一"源数据标准化、显示本地化"的设计,让排序、过滤、导出等依赖底层值的功能始终稳定可靠。

从源码结构看,intl-date是一个典型的"组合型"单元格类型。在 intlDateType.ts 中可以看到它把编辑、渲染、校验三个环节组装在一起:

export const CELL_TYPE = 'intl-date'; export const IntlDateCellType = { CELL_TYPE, editor: IntlDateEditor, renderer: intlDateRenderer, validator: intlDateValidator, sourceDataValidator, sourceDataWarningMessage: SOURCE_DATA_WARNING_MESSAGE, valueFormatter, };

其中editor用于编辑、renderer负责显示格式化、validatorsourceDataValidator负责两套校验(编辑时校验与批量源数据校验)、valueFormatter负责把 ISO 值转成显示文本。date类型则对应 dateCellType 中的同名实现,二者复用同一批底层组件。

日期单元格类型演示

官方文档提供了一个多列演示(example1),演示三种不同的格式化风格,全部基于 ISO 8601 源数据:

  • Product date(产品日期)dateStyle: 'short'短样式格式化;
  • Payment date(付款日期):自定义month/day/year组合格式化;
  • Registration date(注册日期):自定义包含weekday(星期)、monthdayyear的完整格式化。

以 JavaScript 版本的 example1.js 为例,核心配置如下:

const data = [ { car: 'Mercedes A 160', product_date: '2002-06-15', payment_date: '2002-05-20', registration_date: '2002-07-01', }, // ... 更多行 ]; const hot = new Handsontable(container, { data, colHeaders: ['Car', 'Product date', 'Payment date', 'Registration date'], columns: [ { type: 'text', data: 'car' }, { type: 'intl-date', data: 'product_date', dateFormat: { dateStyle: 'short' }, }, { type: 'intl-date', data: 'payment_date', dateFormat: { month: 'long', day: 'numeric', year: 'numeric' }, }, { type: 'intl-date', data: 'registration_date', dateFormat: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }, }, ], columnSorting: true, filters: true, dropdownMenu: true, height: 'auto', licenseKey: 'non-commercial-and-evaluation', autoWrapRow: true, autoWrapCol: true, });

注意演示中同时开启了columnSorting: truefilters: truedropdownMenu: true,配合日期列展示"排序和过滤基于 ISO 底层值"的行为。演示还包含一个语言(locale)切换下拉菜单:点击菜单项后调用hot.updateSettings({ locale: item.dataset.value }),即可实时切换整张表的显示语言(例如en-USde-DE),这正是"显示格式与源数据解耦"的直观体现——切换 locale 只改变显示文本,单元格底层值始终是 ISO 8601 字符串。该演示在不同框架下的等价实现分别位于 react/example1.jsx、angular/example1.ts、vue/example1.vue。

使用日期单元格类型

使用**对象风格(object-style)**配置:把type设置为'intl-date''date',把dateFormat设置为一个对象。语言环境(locale)由独立的locale选项控制。

日期类型可以在三个粒度上配置:

1. 整个表格(grid 级)

type: 'intl-date', locale: 'en-US', dateFormat: { year: 'numeric', month: '2-digit', day: '2-digit' },

2. 单个列(column 级)

columns: [ { type: 'intl-date', locale: 'en-US', dateFormat: { dateStyle: 'short' } } ],

3. 单个单元格(cell 级)

cell: [ { row: 0, col: 2, type: 'intl-date', locale: 'en-US', dateFormat: { dateStyle: 'medium' } } ],

React 中使用 JSX 属性写法:

<HotTable type="intl-date" locale="en-US" dateFormat={{ year: 'numeric', month: '2-digit', day: '2-digit' }} columns={[{ type: 'intl-date', locale: 'en-US', dateFormat: { dateStyle: 'short' } }]} />

源数据格式要求

对于intl-datedate单元格,源数据必须使用 ISO 8601 日期格式(YYYY-MM-DD,日期才能正常工作。dateFormat对象只影响显示;排序和过滤依赖的是底层的 ISO 值,而不是格式化后的显示文本。

这一点在源码中得到严格印证:

  • dateValidator.ts 中的dateValidator直接调用isValidISODate(value)判定合法性:
    export function dateValidator(this: CellMeta, value: unknown, callback: (valid: boolean) => void): void { if (this.allowEmpty && isEmpty(value)) { callback(true); return; } callback(isValidISODate(value)); }
  • 同时导出的sourceDataValidator用于批量源数据校验,它除了放行allowEmpty的空值和 Formulas 插件的公式表达式(以=开头的字符串)外,同样要求isValidISODate(value)为真:
    export function sourceDataValidator(value: unknown, cellMeta: CellMeta): boolean { if (cellMeta.allowEmpty && isEmpty(value)) { return true; } if (typeof value === 'string' && value.startsWith('=')) { return true; } return isValidISODate(value); }

    有趣的是,该函数被标记为sourceDataValidator.rowIndependent = true,注释说明它的结果只依赖列级/全局 meta(如allowEmpty),从不依赖行级 meta,因此源码数据校验运行器可以跨行复用同一个列级 meta 对象,避免为每个单元格物化 meta——这是批量校验性能上的一个优化细节。

  • 校验失败时,intlDateValidator.ts 会给出明确的警告文案SOURCE_DATA_WARNING_MESSAGE,提示"期望与 ISO 8601 日期格式(YYYY-MM-DD)兼容的值"。

另外,isValidISODateparseToLocalDate等日期解析工具集中在 helpers/dateTime.ts,是渲染、校验、编辑共用的底层基础设施。

格式化日期

要控制日期在单元格渲染器中的显示效果,使用dateFormat选项。

从 Handsontable 18.0 开始,intl-datedate单元格类型必须使用对象形式的dateFormat,它基于原生Intl.DateTimeFormatAPI;语言环境由独立的locale选项控制。

::: tip 提示 与时间相关的dateFormat选项(hourminutesecondtimeStylehour12hourCyclefractionalSecondDigits只影响显示。由于date/intl-date的源数据只含日期,这些选项渲染出的时间永远是午夜(00:00:00)。如果需要编辑并存储"日期 + 时间",请使用日期时间单元格类型(intl-datetime)。这一约束在 metaSchema.ts 的dateFormat文档注释中也有明确说明。 :::

渲染与格式化:源码如何工作

dateFormat在渲染阶段如何生效?看 dateRenderer.ts 中的valueFormatter实现:

const DEFAULT_INTL_FORMAT: Intl.DateTimeFormatOptions = { year: 'numeric', month: '2-digit', day: '2-digit', }; export function valueFormatter(value: unknown, cellProperties: CellProperties): unknown { const { dateFormat, locale, allowEmpty, instance } = cellProperties; if (isEmpty(value)) { return allowEmpty ? value : BAD_VALUE_TEXT; // 空值:允许为空则原样返回,否则显示 '#bad-value#' } if (typeof dateFormat === 'string') { // 字符串形式的 dateFormat 已不支持,仅警告一次并原样返回 ... return value; } const date = parseToLocalDate(value); if (date === null) { return BAD_VALUE_TEXT; // 非法 ISO 值显示 '#bad-value#' } const intlFormat = isObject(dateFormat) ? dateFormat as Intl.DateTimeFormatOptions : DEFAULT_INTL_FORMAT; return new Intl.DateTimeFormat(locale, intlFormat).format(date); }

几个关键点:

  1. 默认格式:即使不配置dateFormat,也会使用{ year: 'numeric', month: '2-digit', day: '2-digit' }作为兜底(对应YYYY-MM-DD样式的本地化显示)。
  2. 字符串形式已废弃:如果传入字符串形式的dateFormat,渲染器会对每个实例仅警告一次"请改用Intl.DateTimeFormatOptions对象",然后原样返回值。
  3. 非法值占位:空值且不允许为空、或无法解析为日期时,显示#bad-value#(定义于 helpers/constants.ts 的BAD_VALUE_TEXT)。
  4. intlDateRenderer(intlDateRenderer.ts)直接委托给dateRenderer并把valueFormatter挂载为静态属性,供编辑器等其他环节复用同一套格式化逻辑。

使用 Intl.DateTimeFormat 选项

dateFormat选项接受Intl.DateTimeFormatoptions 的全部属性,配合type: 'intl-date'type: 'date'使用。不同列可以搭配不同 locale 与格式:

columns: [ { type: 'intl-date', locale: 'en-US', dateFormat: { year: 'numeric', month: '2-digit', day: '2-digit' } }, { type: 'intl-date', locale: 'de-DE', dateFormat: { dateStyle: 'long' } } ]

React 等价写法:

<HotTable columns={[{ type: 'intl-date', locale: 'en-US', dateFormat: { year: 'numeric', month: '2-digit', day: '2-digit' } }, { type: 'intl-date', locale: 'de-DE', dateFormat: { dateStyle: 'long' } }]} />
日期专用选项速查表

样式快捷方式(Style shortcuts):

属性可选值说明
dateStyle'full''long''medium''short'日期格式化样式(星期、日、月、年、纪元)
timeStyle'full''long''medium''short'时间部分样式(时、分、秒、时区名);用于日期 + 时间场景

日期时间分量选项(Date-time component options):

属性可选值说明
weekday'long''short''narrow'星期的表示方式
era'long''short''narrow'纪元的表示方式
year'numeric''2-digit'年份表示方式
month'numeric''2-digit''long''short''narrow'月份表示方式
day'numeric''2-digit'日表示方式
dayPeriod'narrow''short''long'日周期(例如 "am")
hour'numeric''2-digit'小时(若包含时间)
minute'numeric''2-digit'分钟
second'numeric''2-digit'
fractionalSecondDigits123秒的小数位数
timeZoneName'long''short''shortOffset''longOffset''shortGeneric''longGeneric'时区显示方式

语言环境与其他选项(Locale and other options):

属性可选值说明
localeMatcher'best fit'(默认)、'lookup'区域匹配算法
calendar'chinese''gregory''persian'使用的日历系统
numberingSystem'latn''arab''hans'数字系统
timeZoneIANA 时区(如'UTC''America/New_York'格式化使用的时区
hour12truefalse12 小时制 vs 24 小时制
hourCycle'h11''h12''h23''h24'小时周期
formatMatcher'basic''best fit'(默认)格式匹配算法

完整的属性参考见dateFormatAPI 文档 与 MDN: Intl.DateTimeFormat。

编辑器行为

dateFormat控制的是单元格内的显示。编辑器(日期选择器或文本输入)可能以归一化形式展示该值;对于intl-datedate,底层值始终保持 ISO 8601 格式。

这一点在 dateEditor.ts 中有非常清晰的实现:

  • DateEditor继承自TextEditor,但其createElements()把文本域改造成原生日期输入
    createElements(type?: string): void { super.createElements('input'); this.TEXTAREA.setAttribute('type', 'date'); }
  • prepare()阶段,编辑器拿到的显示值已经过valueFormatter格式化,但它会把originalValue替换为原始 ISO 源数据,让原生日期输入框始终收到YYYY-MM-DD字符串:
    prepare(row, col, prop, td, value, cellProperties) { super.prepare(row, col, prop, td, value, cellProperties); ... const physicalRow = this.hot.toPhysicalRow(row); this.originalValue = this.hot.getSourceDataAtCell(physicalRow, col); }
  • setValue()在值为空时回退到defaultDate(如果配置了),并对非 ISO 值发出警告后清空输入:
    setValue(value?: unknown): void { if (isEmpty(value)) { value = this.cellProperties.defaultDate; } if (!isValidISODate(value)) { warn('DateEditor: value must be in ISO date format ("YYYY-MM-DD") ...'); super.setValue(''); return; } super.setValue(value); }
  • open()通过showPicker()程序化唤起浏览器的原生日期选择器;focus()时全选输入内容方便直接键入。
  • 另外,init()中注册了afterSetTheme钩子:切换主题(非首次运行时)会关闭编辑器,避免主题切换时编辑器状态错乱。

defaultDate选项配置日期选择器在单元格为空时预选的日期(例如defaultDate: '2015-02-02'),它只影响选择器的初始选中值,不影响已填写单元格的值,详见 metaSchema.ts 中defaultDate的说明。

结果与行为验证

完成日期单元格类型配置后:

  • 单元格按你的dateFormat配置显示格式化后的日期文本;
  • 点击intl-datedate单元格会打开浏览器的原生日期选择器
  • 无论显示格式如何,源数据始终以 ISO 8601 格式(YYYY-MM-DD)存储。

排序与过滤依赖 ISO 底层值

文档明确指出:"排序和过滤依赖于底层的 ISO 值"。源码同样印证:

  • 列排序插件为intl-date提供了专门的比较函数工厂 intlDate.ts,其compareFunctionFactory调用 columnSorting/utils.ts 中的createIntlDateCompareFunction——基于 ISO 日期构造可比较的值,从而保证按真实时间顺序排序,而不是按显示文本的字典序。
  • 过滤插件在 filters/constants.ts 中为intl-date注册了完整的条件集合(beforeafterbetweentodayyesterdaytomorrow等),对应的单测位于 filters/tests/condition/intlDate;filters/sortComparators.ts则复用了与列排序一致的比较逻辑。

因此,只要源数据保持 ISO 8601,无论显示成2/15/12还是February 15, 2012,排序和过滤的结果都是正确的。

导出与注册

intl-date同样参与了导出(XLSX)与模块注册体系:xlsx 导出类型映射见 plugins/exportFile/types/xlsx.ts,模块全量注册测试见 registry/registerAllCellTypes.unit.js。如果你使用按需注册方式,需要显式registerCellType(intlDateCellType)或调用registerAllModules()(演示代码中即采用后者)。

键盘快捷键

intl-datedate单元格编辑器打开的是浏览器原生日期选择器。选择器内部的键盘导航行为由浏览器提供,因此在不同浏览器与操作系统之间存在差异。在日期选择器之外,Handsontable 标准的编辑类键盘快捷键依然生效(如 Enter 开始编辑、Esc 取消编辑、Tab 切换单元格等)。

相关资源

相关指南

  • 单元格类型(Cell type)

配置选项

  • dateFormat
  • locale
  • type
  • defaultDate
  • valueFormatter
  • valueParser
  • valueSetter
  • valueGetter

核心方法

  • getCellMeta()
  • getCellMetaAtRow()
  • getCellsMeta()
  • getDataType()
  • setCellMeta()
  • setCellMetaObject()
  • removeCellMeta()

钩子(Hooks)

  • afterGetCellMeta
  • afterSetCellMeta
  • beforeGetCellMeta
  • beforeSetCellMeta

【免费下载链接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡项目地址: https://gitcode.com/gh_mirrors/ha/handsontable

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询