使用
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 友好的,因为总是显示默认日期。
我在构建一个日期选择器时遇到了这个问题,它需要在浏览器的区域设置中显示选定的日期,并解析用户在自己的区域设置中手动键入的日期。我的解决方案的简化版本在这里:
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
。
这很难做对:(
我想出了这两个函数,它们也处理短年份和可选时间:
// 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)}`)
当然你可以使用
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"