【问题标题】:How to reposition datetime into the current timezone when converting an ISO 8601 datetime string to a **Date** object?将 ISO 8601 日期时间字符串转换为 **Date** 对象时,如何将日期时间重新定位到当前时区?
【发布时间】:2023-03-09 09:02:01
【问题描述】:

假设我们在 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://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();
}

【问题讨论】:

    标签: javascript datetime


    【解决方案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.stackoverflow 上发布。
    【解决方案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 的帮助。我已经尝试了很多东西,包括stackoverflow上其他问题的一堆东西,但我无法得到我正在寻找的结果。带有工作代码的答案会非常有帮助。
    • 这里的问题是它假设输入偏移量总是为零(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 时间,日期时间形式被解释为本地时间

    【讨论】:

    【解决方案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://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;
    };
    

    主要区别在于单个正则表达式一次返回所有匹配的组。

    Here's a regex101 及其匹配/分组的一些示例。


    它的速度大约是 @RobG 令人敬畏的公认答案的两倍,比 moment.jsdate-fns 包快 4-6 倍。 ?

    【讨论】:

      猜你喜欢
      • 2019-02-27
      • 1970-01-01
      • 1970-01-01
      • 2017-07-25
      • 2013-01-30
      • 1970-01-01
      • 2017-09-10
      • 2023-04-09
      • 2016-10-20
      相关资源
      最近更新 更多