如何解析从 Intl.DateTimeFormat 生成的日期字符串

问题描述 投票:0回答:3

使用

Intl.DateTimeFormat.format
函数,您可以生成针对特定区域设置格式化的日期字符串。传递给
Intl.DateTimeFormat.format()
函数的选项允许了解有关格式的一些信息,例如年份是两位数还是四位数字,但有些信息是未知的,例如使用的分隔符或年、月的顺序和白天元素。

尝试使用

Date
将该字符串解析回
Date.parse
对象并不总是可行。

例如,此代码在西班牙语语言环境中失败,但在英语语言环境中有效:

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]
方法不提供任何帮助,因为它只是提供返回构造函数上传递的选项以及默认值。

问题

有没有办法在不使用

Intl
或任何其他外部库的情况下将使用
Date
格式化的字符串解析回
moment.js
对象?

我的用例

我正在使用一个日期时间组件,它接受格式和解析函数来处理日期。当用户使用日期时间输入的日历控件选择日期时,没有问题,并且格式函数使用

Intl
根据区域设置和一组格式选项对其进行格式化。 有时,用户手动编辑显示的日期。如果使用西班牙语区域设置,他可以看到显示
"27/11/2020"
日期,并决定将日期更改为
"28/11/2020"
。这将失败,因为
Date.parse()
无法解析此日期(见上文)。在其他地区,这种情况可能会变得更糟。 我试图避免包含外部日期库,但没有找到克服这个问题的方法。 我知道用户可以以任意格式编辑日期,但我想至少接受控件中显示的相同格式,我认为这是最 UI 友好的,因为总是显示默认日期。

javascript date datetime internationalization
3个回答
6
投票

我在构建一个日期选择器时遇到了这个问题,它需要在浏览器的区域设置中显示选定的日期,并解析用户在自己的区域设置中手动键入的日期。我的解决方案的简化版本在这里:

function getDateFormat(locale = undefined) {
  const formatted = new Intl.DateTimeFormat(locale).format(new Date(2000, 0, 2));
  return formatted
    .replace('2000', 'YYYY')
    .replace('01', 'MM')
    .replace('1', 'M')
    .replace('02', 'DD')
    .replace('2', 'D');
}

function getFormattedDateRegex(format) {
  return new RegExp(
      '^\\s*' + format.toUpperCase().replaceAll(/([MDY])\1*/g, '(?<$1>\\d+)') + '\\s*$'
    );
}

function parseFormattedDate(value, locale = undefined) {
  const format = getDateFormat(locale);
  const regex = getFormattedDateRegex(format);

  const { groups } = value.match(regex) ?? {};

  if (!groups) return null;

  const y = Number(groups.Y);
  const m = Number(groups.M);
  const d = Number(groups.D);

  // Validate range of year and month
  if (y < 1000 || y > 2999) return null;
  if (m < 1 || m > 12) return null;

  const date = new Date(y, m - 1, d);

  // Validate day of month exists
  if (d !== date.getDate()) return null;

  return isNaN(date.valueOf()) ? null : date;
}

getDateFormat
生成已知日期,将其格式化为请求的区域设置(或浏览器区域设置),然后使用输出来确定 Intl 使用的格式。

getFormattedDateRegex
采用格式字符串(例如“MM/DD/YYYY”)并创建一个具有命名捕获组的正则表达式,用于解析该格式的日期。

parseFormattedDate
只需接受格式化的日期字符串(以及可选的区域设置)并尝试解析它。如果日期字符串与 Intl 在当前语言环境中使用的格式不匹配,它将返回
null


0
投票

这很难做对:(

我想出了这两个函数,它们也处理短年份和可选时间:

// format local date using locale (ie "dd/mm/yyyy" or "mm/dd/yyyy")
function formatLocalDate(dateObj, withTime=true, shortYear=false) {
    if (!dateObj) return ''
    const locale = Intl.DateTimeFormat().resolvedOptions().locale
    let date = new Intl.DateTimeFormat(locale, { dateStyle: "short" }).format(dateObj)
    let time = new Intl.DateTimeFormat(locale, { timeStyle: "short", hour12: false }).format(dateObj)
    if (shortYear && date.length == 10) date = date.substr(0, 6) + date.substr(8)
    return withTime ? date+' '+time : date
}

// parse a date string that was created with formatDate
function parseLocalDate(dateStr, withTime = true, shortYear = false) {
    let f = formatLocalDate(new Date(2022, 11 - 1, 12, 13, 14), withTime, shortYear)
    let regEx = shortYear ? f.replace('22', '(?<year>\\d\\d)') : f.replace('2022', '(?<year>\\d\\d\\d\\d)') 
    regEx = regEx.replace('11', '(?<month>\\d\\d*)').replace('12', '(?<day>\\d\\d*)').replace('13', '(?<hour>\\d\\d*)').replace('14', '(?<min>\\d\\d*)')
    regEx = `^\\s*${regEx}\\s*$`
    const { groups } = dateStr.match(regEx) ?? {};
    if (!groups) return null;
    let year = parseInt(groups.year), month = parseInt(groups.month), day = parseInt(groups.day), hour = withTime ? parseInt(groups.hour) : 0, min = withTime ? parseInt(groups.min) : 0  
    if (year < 100) year += 2000
    const date = new Date(year, month - 1, day, hour, min);
    return isNaN(date.valueOf()) ? null : date;
}   

// test
const d = new Date(2023, 8, 13, 21, 35)
console.log(`\n${d}\n${formatLocalDate(d)}\n${parseLocalDate(formatLocalDate(d))}`)
console.log(`\n${d}\n${formatLocalDate(d, false)}\n${parseLocalDate(formatLocalDate(d, false), false)}`)
console.log(`\n${d}\n${formatLocalDate(d, true, true)}\n${parseLocalDate(formatLocalDate(d, true, true), true, true)}`)


-2
投票

当然你可以使用

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"
© www.soinside.com 2019 - 2024. All rights reserved.