简体   繁体   English

将 ISO 8601 日期时间字符串转换为 **Date** object 时,如何将日期时间重新定位到当前时区?

[英]How to reposition datetime into the current timezone when converting an ISO 8601 datetime string to a **Date** object?

Let's say we're in London at midnight on 2020-01-01 and make an entry into an app that stores the datetime as an ISO-8601 string like this.假设我们在 2020 年 1 月 1 日午夜在伦敦,并进入一个应用程序,该应用程序将日期时间存储为这样的 ISO-8601 字符串。
2020-01-01T00:00:00-00:00

Later, I am in Los Angeles and want to view this date on a chart that requires a javascript date object.后来,我在洛杉矶,想在需要 javascript 日期 object 的图表上查看此日期。

Getting the localized date object is easy.获取本地化日期 object 很容易。

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)

But, sometimes we want to "ignore" the timezone offset and analyze the data as if it happened in the current timezone.但是,有时我们想“忽略”时区偏移并分析数据,就好像它发生在当前时区一样。

This is the result I'm looking for but don't know how to accomplish.这是我正在寻找但不知道如何完成的结果。

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)

How do you reposition the date and return a date object?如何重新定位日期并返回日期 object?



/* Helper function

Returns the object type
https://stackoverflow.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();
}

This is really a duplicate of Why does Date.parse give incorrect results?这真的是为什么 Date.parse 给出不正确结果的副本? , but that may not seem apparent at first glance. ,但乍一看可能并不明显。

The first rule of parsing timestamps is "do not use the built–in parser", even for the 2 or 3 formats supported by ECMA-262.解析时间戳的第一条规则是“不要使用内置解析器”,即使对于 ECMA-262 支持的 2 或 3 种格式也是如此。

To reliably parse a timestamp, you must know the format.要可靠地解析时间戳,您必须知道格式。 Built–in parsers try and work it out, so there are differences between them that may well produce unexpected results.内置解析器会尝试解决它,因此它们之间存在差异,很可能会产生意想不到的结果。 It just happens that '2020-01-01T00:00:00+00:00' is probably the only supported format that is actually reliably parsed.碰巧 '2020-01-01T00:00:00+00:00' 可能是唯一实际可靠解析的受支持格式。 But it does differ slightly from strict ISO 8601, and different browsers differ in how strictly they apply the ECMAScript parsing rules so again, very easy to get wrong.但它确实与严格的 ISO 8601略有不同,并且不同的浏览器在应用 ECMAScript 解析规则的严格程度上也有所不同,因此很容易出错。

You can convert it to a "local" timestamp by just trimming the offset information, ie '2020-01-01T00:00:00', however Safari at least gets it wrong and treats it as UTC anyway.您可以通过仅修剪偏移信息将其转换为“本地”时间戳,即“2020-01-01T00:00:00”,但是 Safari 至少会出错并将其视为 UTC。 ECMAScrip itself is inconsistent with ISO 8601 by treating date–only forms of ISO 8601 as UTC (ie '2020-01-01' as UTC when ISO 8601 says to treat it as local). ECMAScrip 本身与 ISO 8601 不一致,将 ISO 8601 的仅日期 forms 视为 UTC(即当 ISO 8601 将其视为本地时,将“2020-01-01”视为 UTC)。

So just write your own parser or use a library, there are plenty to choose from.所以只需编写自己的解析器或使用库,有很多可供选择。 If you're only looking for parsing and formatting, there are some that are less than 2k minified (and there are examples on SO ).如果您只是在寻找解析和格式化,那么有一些小于 2k 的缩小(并且在 SO 上有示例)。

Writing your own is not that challenging if you just want to support straight forward ISO 8601 like formats, eg如果您只想支持直接的 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()); });

 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);

But, sometimes we want to "ignore" the timezone offset and analyze the data as if it happened in the current timezone.但是,有时我们想“忽略”时区偏移并分析数据,就好像它发生在当前时区一样。

Ok, then ignore it.好吧,那就无视吧。

const iso8601Date = '2020-01-01T00:00:00+00:00';
const theDate = new Date(iso8601Date.substring(0, 19));

This works because you're creating a Date object from 2020-01-01T00:00:00 - an ISO 8601 date-time without offset.这是有效的,因为您正在从2020-01-01T00:00:00创建一个Date object - 一个没有偏移的 ISO 8601 日期时间。

ECMAScript section 20.3.1.15 - Date Time String Format says: ECMAScript 第 20.3.1.15 节 - 日期时间字符串格式说:

When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time .当时区偏移不存在时,仅日期 forms 被解释为 UTC 时间,日期时间 forms 被解释为本地时间

Building off of @RobG's answer I was able to speed this one up a little by using a single regex.在@RobG 的回答的基础上,我可以通过使用单个正则表达式来加快这个速度。 Posting here for posterity.在这里张贴给后代。

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://stackoverflow.com/a/61242262/25197

  const dateTimeParts = iso8601.match(
    /(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d{0,7}))?(?:([+-])(\d{2}):(\d{2}))?)?/,
  );

  // 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;
};

The key difference is a single regex that returns all the matching groups at once.关键区别在于一个单一的正则表达式,它一次返回所有匹配的组。

正则表达式可视化

Here's a regex101 with some examples of it matching/grouping.这是一个 regex101 ,其中包含一些匹配/分组的示例。


It's about double the speed of the @RobG's awesome accepted answer and 4-6x faster than moment.js and date-fns packages.它的速度大约是 @RobG 令人敬畏的公认答案的两倍,比moment.jsdate-fns fns 包快 4-6 倍。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 将ISO 8601字符串转换为Javascript中的当前TimeZone DateTime对象 - Converting an ISO 8601 string to Current TimeZone DateTime Object in Javascript 使用 moment.js 和 moment 时区将日期、时间和偏移量转换为 ISO8601 DateTime - Converting a date, time and offset into an ISO8601 DateTime using moment.js and moment timezone ISO8601 DateTime字符串以本地用户时区显示 - ISO8601 DateTime String to be displayed in local users timezone 如何将带有时区的ISO 8601转换为C#日期时间? - How to convert ISO 8601 with timezone to C# Datetime? 将日期字符串转换为ISO 8601格式字符串,最后转换为日期对象 - converting a date string to ISO 8601 format string and eventually a date object 为时区获取简化的ISO 8601日期字符串 - Getting the timezone a simplified ISO 8601 date string 将中心时间日期转换为当前时区的ISO字符串 - Converting Central Time date to ISO string in current timezone AngularJS输入类型= datetime-local:期望ISO-8601-有效日期字符串为日期 - AngularJS input type=datetime-local: expected ISO-8601-valid date string to be a date 将DateTime对象转换为特定时区 - Converting a DateTime Object to a specific timezone 使用 UTC 时区解析 ISO8601 日期字符串到日期 - Parse ISO8601 date string to date with UTC Timezone
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM