将 ISO 8601 日期时间字符串转换为 **Date** 对象时,如何将日期时间重新定位到当前时区?
Posted
技术标签:
【中文标题】将 ISO 8601 日期时间字符串转换为 **Date** 对象时,如何将日期时间重新定位到当前时区?【英文标题】:How to reposition datetime into the current timezone when converting an ISO 8601 datetime string to a **Date** object? 【发布时间】:2020-04-16 02:15:21 【问题描述】:假设我们在 2020 年 1 月 1 日午夜在伦敦,并进入一个应用程序,该应用程序将日期时间存储为这样的 ISO-8601 字符串。2020-01-01T00:00:00-00:00
稍后,我在洛杉矶,想在需要 javascript 日期对象的图表上查看此日期。
获取本地化的日期对象很容易。
const iso8601Date = '2020-01-01T00:00:00+00:00';
const theDate = new Date(iso8601Date);
console.log(typeOf(theDate)); // date
console.log(theDate); // Tue Dec 31 2019 16:00:00 GMT-0800 (PST)
但是,有时我们想“忽略”时区偏移并分析数据,就好像它发生在当前时区一样。
这是我正在寻找但不知道如何完成的结果。
const iso8601Date = '2020-01-01T00:00:00+00:00';
const theRepositionedDate = someMagic(iso8601Date);
console.log(typeOf(theRepositionedDate)); // date
console.log(theRepositionedDate); // Wed Jan 01 2020 00:00:00 GMT-0800 (PST)
如何重新定位日期并返回一个日期对象?
/* Helper function
Returns the object type
https://***.com/a/28475133/25197
typeOf(); //undefined
typeOf(null); //null
typeOf(NaN); //number
typeOf(5); //number
typeOf(); //object
typeOf([]); //array
typeOf(''); //string
typeOf(function () ); //function
typeOf(/a/) //regexp
typeOf(new Date()) //date
*/
function typeOf(obj)
return .toString
.call(obj)
.split(' ')[1]
.slice(0, -1)
.toLowerCase();
【问题讨论】:
【参考方案1】:这实际上是 Why does Date.parse give incorrect results? 的复制品,但乍一看可能并不明显。
解析时间戳的第一条规则是“不要使用内置解析器”,即使对于 ECMA-262 支持的 2 或 3 种格式也是如此。
要可靠地解析时间戳,您必须知道格式。内置解析器会尝试解决它,因此它们之间存在差异,很可能会产生意想不到的结果。碰巧 '2020-01-01T00:00:00+00:00' 可能是唯一实际可靠解析的受支持格式。但它与严格的 ISO 8601 确实略有不同,并且不同的浏览器在应用 ECMAScript 解析规则的严格程度上也有所不同,因此很容易出错。
您可以通过修剪偏移信息将其转换为“本地”时间戳,即“2020-01-01T00:00:00”,但 Safari 至少会出错并将其视为 UTC。 ECMAScrip 本身与 ISO 8601 不一致,因为它将 ISO 8601 的仅日期形式视为 UTC(即当 ISO 8601 将其视为本地时,将“2020-01-01”视为 UTC)。
所以只需编写自己的解析器或使用库,有很多可供选择。如果你只是在寻找解析和格式化,有一些小于 2k 的缩小(还有examples on SO)。
如果您只想支持直接的 ISO 8601 等格式,例如,编写自己的代码并没有那么困难,例如
// Parse ISO 8601 timestamps in YYYY-MM-DDTHH:mm:ss±HH:mm format
// Optional "T" date time separator and
// Optional ":" offset hour minute separator
function parseIso(s, local)
let offset = (s.match(/[+-]\d\d:?\d\d$/) || [])[0];
let b = s.split(/\D/g);
// By default create a "local" date
let d = new Date(
b[0],
b[1]-1,
b[2] || 1,
b[3] || 0,
b[4] || 0,
b[5] || 0
);
// Use offset if present and not told to ignore it
if (offset && !local)
let sign = /^\+/.test(offset)? 1 : -1;
let [h, m] = offset.match(/\d\d/g);
d.setMinutes(d.getMinutes() - sign * (h*60 + m*1) - d.getTimezoneOffset());
return d;
// Samples
['2020-01-01T00:00:00+00:00', // UTC, ISO 8601 standard
'2020-01-01 00:00:00+05:30', // IST, missing T
'2020-01-01T00:00:00-0400', // US EST, missing T and :
'2020-01-01 00:00:00', // No timezone, local always
'2020-01-01' // Date-only as local (differs from ECMA-262)
].forEach(s =>
console.log(s);
console.log('Using offset\n' + parseIso(s).toString());
console.log('Ignoring offset\n' + parseIso(s, true).toString());
);
【讨论】:
嘿@RobG。我整天为此工作,但没有成功。我非常感谢您的回复。它运行良好。 ? 再次感谢您。我今天又玩了一些,希望尽可能加快速度。如果您对结果感兴趣,请在codereview.*** 上发布。【参考方案2】:const createDate = (isoDate) =>
isoDate = new Date(isoDate)
return new Date(Date.UTC(
isoDate.getUTCFullYear(),
isoDate.getUTCMonth(),
isoDate.getUTCDate(),
isoDate.getUTCMinutes(),
isoDate.getUTCSeconds(),
isoDate.getUTCMilliseconds()
));
const iso8601Date = '2020-01-01T00:00:00+00:00';
const theRepositionedDate = createDate(iso8601Date);
console.log(theRepositionedDate instanceof Date); // true
console.log(theRepositionedDate);
【讨论】:
谢谢...但这将返回一个string
。我需要一个date
。
可以使用Date.prototype.getUTCDate()等UTC方法获取你想要的值。
感谢@aagamezl 的帮助。我已经尝试了很多东西,包括***上其他问题的一堆东西,但我无法得到我正在寻找的结果。带有工作代码的答案会非常有帮助。
这里的问题是它假设输入偏移量总是为零(UTC)。 OP表示数据可能来自伦敦。英国在冬季使用 +00:00,但在夏季使用 +01:00。
@aagamezl 请不要因为第一次尝试而气馁。继续提供帮助!【参考方案3】:
但是,有时我们想“忽略”时区偏移并分析数据,就好像它发生在当前时区一样。
好的,那就忽略它。
const iso8601Date = '2020-01-01T00:00:00+00:00';
const theDate = new Date(iso8601Date.substring(0, 19));
之所以有效,是因为您正在从 2020-01-01T00:00:00
创建一个 Date
对象 - 一个没有偏移的 ISO 8601 日期时间。
ECMAScript section 20.3.1.15 - Date Time String Format 说:
当不存在时区偏移时,仅日期形式被解释为 UTC 时间,日期时间形式被解释为本地时间。
【讨论】:
是的,但 Safari 仍然会出错并将 2020-01-01T00:00:00 视为 UTC。可惜Date Time String Format: default time zone difference from ES5 not web-compatible。 :-( @RobG - Arrrggghhh... 你是对的。 Mac 上的 Safari 最新版 (13) 没有正确实现这一点。 (Screenshot) 仅供参考 - 我使用他们的反馈助手工具向 Apple 报告了该错误。 (这里没有提供链接到这里的公共网址,抱歉)【参考方案4】:基于@RobG 的回答,我可以通过使用单个正则表达式来加快这个速度。在这里发布以供后代使用。
const isoToDate = (iso8601, ignoreTimezone = false) =>
// Differences from default `new Date()` are...
// - Returns a local datetime for all without-timezone inputs, including date-only strings.
// - ignoreTimezone processes datetimes-with-timezones as if they are without-timezones.
// - Accurate across all mobile browsers. https://***.com/a/61242262/25197
const dateTimeParts = iso8601.match(
/(\d4)-(\d2)-(\d2)(?:[T ](\d2):(\d2):(\d2)(?:\.(\d0,7))?(?:([+-])(\d2):(\d2))?)?/,
);
// Create a "localized" Date by always specifying a time. If you create a date without specifying
// time a date set at midnight in UTC Zulu is returned. https://www.diigo.com/0hc3eb
const date = new Date(
dateTimeParts[1], // year
dateTimeParts[2] - 1, // month (0-indexed)
dateTimeParts[3] || 1, // day
dateTimeParts[4] || 0, // hours
dateTimeParts[5] || 0, // minutes
dateTimeParts[6] || 0, // seconds
dateTimeParts[7] || 0, // milliseconds
);
const sign = dateTimeParts[8];
if (sign && ignoreTimezone === false)
const direction = sign === '+' ? 1 : -1;
const hoursOffset = dateTimeParts[9] || 0;
const minutesOffset = dateTimeParts[10] || 0;
const offset = direction * (hoursOffset * 60 + minutesOffset * 1);
date.setMinutes(date.getMinutes() - offset - date.getTimezoneOffset());
return date;
;
主要区别在于单个正则表达式一次返回所有匹配的组。
Here's a regex101 及其匹配/分组的一些示例。
它的速度大约是 @RobG 令人敬畏的公认答案的两倍,比 moment.js
和 date-fns
包快 4-6 倍。 ?
【讨论】:
以上是关于将 ISO 8601 日期时间字符串转换为 **Date** 对象时,如何将日期时间重新定位到当前时区?的主要内容,如果未能解决你的问题,请参考以下文章
Python - 将日期的字符串表示形式转换为 ISO 8601
一种将当前日期时间转换为 ISO 8601 格式的优雅方法 [重复]