如何在JavaScript中使用ISO 8601格式化带有时区偏移的日期?

211

目标: 查找本地时间和UTC时间偏移量,然后按以下格式构造URL。

示例URL: /Actions/Sleep?duration=2002-10-10T12:00:00−05:00

该格式基于W3C推荐。文档说明如下:

例如,2002-10-10T12:00:00−05:00(2002年10月10日中午,美国中部夏令时和东部标准时间)等同于2002-10-10T17:00:00Z,比2002-10-10T12:00:00Z晚五个小时。

因此,根据我的理解,我需要使用 new Date() 获取本地时间,然后使用 getTimezoneOffset() 函数计算差异,然后将其附加到字符串末尾。

  1. 使用format获取本地时间

var local = new Date().format("yyyy-MM-ddThh:mm:ss"); // 2013-07-02T09:00:00
  • 按小时获取UTC时间偏移量

  • var offset = local.getTimezoneOffset() / 60; // 7
    
  • 构建URL(仅限时间部分)

  • var duration = local + "-" + offset + ":00"; // 2013-07-02T09:00:00-7:00
    
    上面的输出意味着我的本地时间是2013/07/02 早上9点,与UTC的差别是7小时(UTC比当地时间快7小时)。
    到目前为止,它似乎可以工作,但如果 getTimezoneOffset() 返回负值,例如-120怎么办?
    我想知道在这种情况下格式应该是什么样子,因为我从W3C文档中无法弄清楚。

    我看到很多答案使用字符串分割(这是有用的),但请注意,即使在相同的时间点,不同时区的日期可能会有所不同(实际上可能会相差2天)。 - Jay Wick
    22个回答

    310
    这是一个简单的辅助函数,可以为您格式化JS日期。

    function toIsoString(date) {
      var tzo = -date.getTimezoneOffset(),
          dif = tzo >= 0 ? '+' : '-',
          pad = function(num) {
              return (num < 10 ? '0' : '') + num;
          };
    
      return date.getFullYear() +
          '-' + pad(date.getMonth() + 1) +
          '-' + pad(date.getDate()) +
          'T' + pad(date.getHours()) +
          ':' + pad(date.getMinutes()) +
          ':' + pad(date.getSeconds()) +
          dif + pad(Math.floor(Math.abs(tzo) / 60)) +
          ':' + pad(Math.abs(tzo) % 60);
    }
    
    var dt = new Date();
    console.log(toIsoString(dt));


    3
    标志表示当地时间与格林威治标准时间(GMT)的偏差。 - Steven Moseley
    3
    这里的“pad”函数返回日期每个部分的正确字符串,除了毫秒部分。例如,输入5毫秒将返回05,但实际上应该是005。下面是一个带有最小修改版本的该函数的链接:jsbin - Safique Ahmed Faruque
    22
    请注意:本答案存在一个微妙的错误。如果时区偏移量是一个负数分钟(例如-09:30),则时区偏移量将无法被正确计算,例如法国和加拿大的某些地区。取模运算返回一个负数,因此在取绝对值之前执行floor()会意外地使时区偏移量更加负面。为了纠正这个错误,请先执行abs(),然后再执行floor()。尝试编辑答案,但显然“此编辑旨在解决帖子作者,并且作为编辑没有意义”。 - tytk
    19
    @tytk - 很好的发现!确实很微妙。我已将您的修复措施添加到答案中。感谢您的评论! - Steven Moseley
    3
    @RobG说得好,对于Math.floor也可以这样说。更新了! - Steven Moseley
    显示剩余14条评论

    83

    getTimezoneOffset()返回的格式与您提到的规范所要求的相反。

    这种格式也被称为ISO8601,更准确地说是RFC3339

    在这种格式中,UTC用Z表示,而所有其他格式都由距离UTC的偏移量表示。 其含义与JavaScript相同,但是减法的顺序相反,因此结果带有相反的符号。

    此外,原生的Date对象中没有名为format的方法,因此您在#1中的函数将失败,除非您使用库来实现此目的。 请参考此文档

    如果您正在寻找可以直接处理此格式的库,则建议尝试moment.js。 实际上,这是默认格式,因此您只需执行以下操作:

    var m = moment();    // get "now" as a moment
    var s = m.format();  // the ISO format is the default so no parameters are needed
    
    // sample output:   2013-07-01T17:55:13-07:00
    

    这是一个经过充分测试的、跨浏览器的解决方案,而且还有许多其他有用的功能。


    使用 toISOString() 不起作用。+01:00 格式要求时间部分为本地时间。toISOString() 将提供一个 UTC 时间字符串。 - Austin France
    1
    @AustinFrance - 你说得对!我很惊讶自己在当时犯了这个错误,因为我经常纠正别人的这个问题。很抱歉我没有看到你两年前的评论!回答已经被编辑过了。 - Matt Johnson-Pint

    58

    我认为值得考虑的是,你可以通过单个API调用标准库来获取所需信息...

    new Date().toLocaleString( 'sv', { timeZoneName: 'short' } );
    
    // produces "2019-10-30 15:33:47 GMT−4"
    

    如果您想添加“T”分隔符,删除“GMT-”,或在末尾添加“:00”,则需要进行文本交换。
    但是,如果您想使用12小时制或省略秒等选项,则可以轻松处理其他选项
    请注意,我使用瑞典作为区域设置,因为它是使用ISO 8601格式的国家之一。 我认为大多数ISO国家都使用此“GMT-4”格式来表示时区偏移量,而加拿大除外,其使用时区缩写例如“EDT”表示东部夏令时。
    您可以从较新的标准i18n函数“Intl.DateTimeFormat()”中获得相同的内容,但必须告诉它通过选项包含时间,否则它将只提供日期。

    2
    嗯,对于sv,我实际上得到的是“CET”,而对于夏令时日期,则是“CEST”...但感谢您的输入,标准API对我来说已经足够了(需要在日志消息前缀中加入日期)。如果我想认真处理时间和时区,我想我会选择moment库... - mmey
    使用“sv”格式的 toLocaleString 和加号 - Pawel Cioch
    这是甘薯! - Timbokun
    1
    你确定 sv 代表瑞典吗?当我搜索“sv国家代码”时,所有的顶部结果都是关于萨尔瓦多。而当我搜索“瑞典国家代码两个字母”时,谷歌显示它是 se - Venryx
    2
    @Venryx 根据 ISO 639-1 https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes,sv 代表瑞典语。我敢打赌这是因为他们称自己的语言为 Svenska。 - Kerry Johnson
    7
    请注意,当地区更改其首选日期格式时,可能会随时发生。 - sffc

    27

    对于那些只想以YYYY-MM-DD格式获取当地时区的今天日期的人,我的答案略有变化。

    让我明确一下:

    我的目标:获取用户所在时区的今天日期,但格式为ISO8601(YYYY-MM-DD)

    以下是代码:

    new Date().toLocaleDateString("sv") // "2020-02-23" // 
    
    这有效是因为瑞典语区使用ISO 8601格式。

    无效的答案,这回答了问题,但不是这个问题... 这个答案可以很容易地在SO上找到。 - AgainPsychoX
    7
    我没有完全阅读问题,所以无法确定这是否回答了问题,但这正是我要找的(而且非常简短)。谢谢! - interestinglythere
    sv不是瑞典,谷歌称它为萨尔瓦多。瑞典的短代码是“se”。 - KnechtRootrecht
    @KnechtRootrecht 根据 ISO 639-1(https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes),sv 代表瑞典语。我敢打赌这是因为他们称自己的语言为 Svenska。 - Kerry Johnson
    2
    @KnechtRootrecht:这是语言代码,而非国家代码。 - Herbert Van-Vliet
    据我所知,ECMAScript标准tc39.es/ecma402/#datetimeformat-objects指的是https://www.unicode.org/reports/tr35/#Unicode_locale_identifier,该标准又参考了BCP 47 [并告诉我们“由于历史原因,这被称为Unicode语言环境标识符。然而,它实际上(有少数例外)作为语言标识符,并访问基于语言的数据。”],这使我们得到了IETF标签,如https://en.wikipedia.org/wiki/IETF_language_tag所述,其中sv代表瑞典语。迄今为止,这一过程非常痛苦。 - Chris F Carroll

    11

    请查看:

    function dateToLocalISO(date) {
        const off    = date.getTimezoneOffset()
        const absoff = Math.abs(off)
        return (new Date(date.getTime() - off*60*1000).toISOString().substr(0,23) +
                (off > 0 ? '-' : '+') + 
                Math.floor(absoff / 60).toFixed(0).padStart(2,'0') + ':' + 
                (absoff % 60).toString().padStart(2,'0'))
    }
    
    // Test it:
    d = new Date()
    
    dateToLocalISO(d)
    // ==> '2019-06-21T16:07:22.181-03:00'
    
    // Is similar to:
    
    moment = require('moment')
    moment(d).format('YYYY-MM-DDTHH:mm:ss.SSSZ') 
    // ==> '2019-06-21T16:07:22.181-03:00'
    

    9

    这是我编写的客户端时区函数,它非常轻巧和简单。

      function getCurrentDateTimeMySql() {        
          var tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in milliseconds
          var localISOTime = (new Date(Date.now() - tzoffset)).toISOString().slice(0, 19).replace('T', ' ');
          var mySqlDT = localISOTime;
          return mySqlDT;
      }
    

    @tsh,你确定吗?你正在获取当前时间偏移量,然后用它来模拟本地时间。也许在那个时候偏移量不同,但这并不重要,因为你只关心现在的时间。 - Andrew

    6
    你可以使用几个简单的扩展方法实现这一点。下面的日期扩展方法返回ISO格式的时区组件,然后你可以定义另一个用于日期/时间部分,并将它们组合成完整的日期-时间偏移字符串。
    Date.prototype.getISOTimezoneOffset = function () {
        const offset = this.getTimezoneOffset();
        return (offset < 0 ? "+" : "-") + Math.floor(Math.abs(offset / 60)).leftPad(2) + ":" + (Math.abs(offset % 60)).leftPad(2);
    }
    
    Date.prototype.toISOLocaleString = function () {
        return this.getFullYear() + "-" + (this.getMonth() + 1).leftPad(2) + "-" +
            this.getDate().leftPad(2) + "T" + this.getHours().leftPad(2) + ":" +
            this.getMinutes().leftPad(2) + ":" + this.getSeconds().leftPad(2) + "." +
            this.getMilliseconds().leftPad(3);
    }
    
    Number.prototype.leftPad = function (size) {
        var s = String(this);
        while (s.length < (size || 2)) {
            s = "0" + s;
        }
        return s;
    }
    

    使用示例:

    var date = new Date();
    console.log(date.toISOLocaleString() + date.getISOTimezoneOffset());
    // Prints "2020-08-05T16:15:46.525+10:00"
    

    我知道现在已经是2023年了,大多数人可能都在使用Moment.js,但是有时候还是需要一个简单的可复制粘贴的解决方案。

    (我将日期/时间方法和偏移量方法分开的原因是因为我正在使用一个旧的 Datejs 库,该库已经提供了具有自定义格式说明符的灵活的toString方法,但不包括时区偏移量。 因此,我添加了toISOLocaleString方法,以供没有该库的任何人使用。)


    感谢Extragrorey提供这个方便的代码! - Amey

    4

    以下是我的个人见解

    我在处理日期时间时遇到了这个问题,所以我做了如下操作:

    const moment = require('moment-timezone')
    
    const date = moment.tz('America/Bogota').format()
    

    然后将日期保存到数据库中,以便能够从某个查询中进行比较。


    要安装moment-timezone

    npm i moment-timezone
    

    3
    使用Temporal
    Temporal.Now.zonedDateTimeISO().toString()
    // '2022-08-09T14:16:47.762797591-07:00[America/Los_Angeles]'
    

    要省略小数秒和IANA时区:

    Temporal.Now.zonedDateTimeISO().toString({
      timeZoneName: "never",
      fractionalSecondDigits: 0
    })
    // '2022-08-09T14:18:34-07:00'
    

    注意:Temporal目前(2022年)作为polyfill提供,但很快将在主流浏览器中推出。


    3
    • 将日期转换为ISO字符串,
    • 使用本地(计算机)时区,
    • 可选择是否包含毫秒数

    ISO参考: https://en.wikipedia.org/wiki/ISO_8601

    使用方法:toIsoLocalTime(new Date())

    function toIsoLocalTime(value) {
        if (value instanceof Date === false)
            value = new Date();    
        const off = value.getTimezoneOffset() * -1;
        const del = value.getMilliseconds() ? 'Z' : '.'; // have milliseconds ?
        value = new Date(value.getTime() + off * 60000); // add or subtract time zone
        return value
            .toISOString()
            .split(del)[0]
            + (off < 0 ? '-' : '+')
            + ('0' + Math.abs(Math.floor(off / 60))).substr(-2)
            + ':'
            + ('0' + Math.abs(off % 60)).substr(-2);
    }
    
    function test(value) {
        const event = new Date(value);
        console.info(value + ' -> ' + toIsoLocalTime(event) + ', test = ' + (event.getTime() === (new Date(toIsoLocalTime(event))).getTime() ));
    }
    
    test('2017-06-14T10:00:00+03:00'); // test with timezone
    test('2017-06-14T10:00:00'); // test with local timezone
    test('2017-06-14T10:00:00Z'); // test with UTC format
    test('2099-12-31T23:59:59.999Z'); // date with milliseconds
    test((new Date()).toString()); // now


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接