如何解析从 Intl.DateTimeFormat 生成的日期字符串
Posted
技术标签:
【中文标题】如何解析从 Intl.DateTimeFormat 生成的日期字符串【英文标题】:How parse a date string generated from Intl.DateTimeFormat 【发布时间】:2021-02-10 14:49:02 【问题描述】:使用Intl.DateTimeFormat.format
函数,您可以生成针对特定语言环境格式化的日期字符串。传递给Intl.DateTimeFormat.format()
函数的选项允许了解有关格式的一些信息,例如年份是两位数还是四位,但有些事情是未知的,例如使用的分隔符或年、月的顺序和日元素。
使用Date.parse
并不总是可以尝试将该字符串解析回Date
对象。
例如,此代码在西班牙语言环境下失败,但适用于英语:
const date = new Date(2020, 10, 28);
const regionEs = new Intl.DateTimeFormat('es', timeZone: 'UTC' );
const regionEn = new Intl.DateTimeFormat('en', timeZone: 'UTC' );
const stringEs = regionEs.format(date); // "28/11/2020"
const stringEn = regionEn.format(date); // "11/28/2020"
const parseEs = new Date(Date.parse(stringEs)); // Error -> Trying to set month to 28
const parseEn = new Date(Date.parse(stringEn)); // Ok
但如果用于生成字符串的格式模板可以从Intl
获得,那就很容易了:类似于"dd/mm/yyyy"
。通过这种方式,字符串可以安全地拆分为可用于构建Date
对象的部分。问题是似乎无法从Intl.DateTimeFormat
获取该信息。
[Intl.resolvedOptions()][1]
方法没有提供任何帮助,因为它只是返回了传递给构造函数的选项以及默认值。
问题
有什么方法可以在不使用moment.js
或任何其他外部库的情况下将Intl
格式的字符串解析回Date
对象?
我的用例
我正在使用一个日期时间组件,它接受格式和解析函数来处理日期。当用户使用日期时间输入的日历控件选择日期时,没有问题,并且格式化函数使用Intl
根据区域设置和一组格式选项对其进行格式化。
有时用户手动编辑显示的日期。如果使用西班牙语言环境,他可以看到显示的"27/11/2020"
日期,他决定将日期更改为"28/11/2020"
。这将失败,因为Date.parse()
无法解析此日期(见上文)。这在其他地区可能会变得更糟。
我试图避免包含外部日期库,但没有找到解决此问题的方法。
我知道用户可以以任意格式编辑日期,但我想至少接受控件中显示的相同格式,我认为这是最友好的 UI,因为始终显示默认日期。
【问题讨论】:
您应该始终使用已知的(最好是标准化的)格式,例如 ISO 8601,并且只使用其他格式进行演示。解析任意格式的日期很麻烦,而且肯定会导致问题,请参阅Why does Date.parse give incorrect results? 如果您不知道格式并且无法将其提供给解析器,那么库并不比 Date.parse(如果未指定格式并且不适合其支持的格式之一,大多数人将退回到它)。 所以基本问题是如何在给定语言环境和一组选项(即 4 位数年份)的情况下获得格式?该信息必须在某个地方,因为Intl.DateTimeFormat
使用它来格式化日期。
相关标准是ECMA-402,它没有提供语言到格式的映射。您寻求的信息在每个实现中都进行了编码,并且它们之间不一定是一致的。但是,ECMA-402 建议实现使用Unicode Common Locale Data Repository 建议的语言来格式化映射。你可以试试ICU Locale Explorer。
我认为这是一项西西弗斯式的任务。即使只使用没有选项的语言,也有大量的变体。添加选项会以不一致的方式更改格式,例如here.
我看到唯一的解决方法是在用户尝试手动编辑日期时将视图更改为 ISO 8601 格式,这样至少他可以看到预期的格式是什么,然后切换到他完成编辑后的区域设置视图。
【参考方案1】:
当然你可以使用moment.js
做这样的事情,但是你需要知道你使用的每个区域的格式,根据你的代码
const parseEs = moment("28/11/2020", "DD/MM/YYYY");
const parseEn = moment("11/28/2020", "MM/DD/YYYY");
最后moment
提供toDate()
函数,因此您可以再次将其传递为区域格式(以防您使用任何组件或库添加/减去或编辑日期)
const stringEs = regionEs.format(parseEs.toDate()); // "28/11/2020"
const stringEn = regionEn.format(parseEn.ToDate()); // "11/28/2020"
【讨论】:
关键是OP必须知道格式,然后任何库都可以(而且有很多可供选择,很多比moment.js轻得多)。以上是关于如何解析从 Intl.DateTimeFormat 生成的日期字符串的主要内容,如果未能解决你的问题,请参考以下文章
为啥 toLocaleTimeString(),总是零填充分钟和秒