如何解析从 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 生成的日期字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何在JavaScript中获取时区名称?

为啥 toLocaleTimeString(),总是零填充分钟和秒

如何快速从解析中加载图像?

iOS 如何从相机加载照片以解析 Swift

Swinject - 如何从一个解析器两次解析同一个控制器?

如何从 Coinmarketcap 解析历史 BTC 数据?