【问题标题】:How to JSON stringify a javascript Date and preserve timezone如何 JSON 字符串化 javascript 日期并保留时区
【发布时间】:2015-09-14 18:17:51
【问题描述】:

我有一个用户创建的日期对象,时区由浏览器填写,如下所示:

var date = new Date(2011, 05, 07, 04, 0, 0);
> Tue Jun 07 2011 04:00:00 GMT+1000 (E. Australia Standard Time)

不过,当我将其字符串化时,时区会再见

JSON.stringify(date);
> "2011-06-06T18:00:00.000Z"

在保留浏览器时区的同时获得 ISO8601 字符串的最佳方法是使用 moment.js 并使用 moment.format(),但如果我通过使用 @ 的东西序列化整个命令,那当然行不通987654324@ 内部(在本例中为 AngularJS)

var command = { time: date, contents: 'foo' };
$http.post('/Notes/Add', command);

为了完整起见,我的域确实需要本地时间和偏移量。

【问题讨论】:

    标签: javascript json date datetime momentjs


    【解决方案1】:

    假设您有某种包含Date 的对象:

    var o = { d : new Date() };
    

    您可以覆盖Date 原型的toJSON 函数。这里我使用moment.js从日期创建一个moment对象,然后使用moment的format不带参数的函数,它发出包含偏移量的ISO8601扩展格式。

    Date.prototype.toJSON = function(){ return moment(this).format(); }
    

    现在当您序列化对象时,它将使用您要求的日期格式:

    var json = JSON.stringify(o);  //  '{"d":"2015-06-28T13:51:13-07:00"}'
    

    当然,这会影响 所有 Date 对象。如果您只想更改特定日期对象的行为,您可以只覆盖该特定对象的 toJSON 函数,如下所示:

    o.d.toJSON = function(){ return moment(this).format(); }
    

    【讨论】:

    • 感谢有关覆盖 toJSON 方法的提示,这确实解决了我的问题。但是有什么安全的方法吗?我的意思是如果我覆盖它,它将在任何地方被覆盖,如果我使用它,它会在其他地方弄乱 .stringify。
    • @KamyarGhasemlou - 更新了问题以显示一种方式。您可以改为更改单个 Date 对象实例的 toJSON 函数。
    • 感谢您的回复和快速添加。我应该考虑为特定对象覆盖方法,非常聪明。 :)
    • 让我睡了个好觉 :) 我被困在这个问题上很长一段时间了。只需在 module.js 中导入并覆盖它(角度 2+)
    【解决方案2】:

    我总是倾向于不弄乱系统对象原型中的函数,比如日期,你永远不知道它会在以后的代码中以某种意想不到的方式咬你。

    相反,JSON.stringify 方法接受您可以提供的“替换器”函数 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter),允许您覆盖 JSON.stringify 如何执行其“字符串化”的内部结构;所以你可以做这样的事情;

    var replacer = function(key, value) {
    
       if (this[key] instanceof Date) {
          return this[key].toUTCString();
       }
       
       return value;
    }
    
    console.log(JSON.stringify(new Date(), replacer));
    console.log(JSON.stringify({ myProperty: new Date()}, replacer));
    console.log(JSON.stringify({ myProperty: new Date(), notADate: "I'm really not", trueOrFalse: true}, replacer));

    【讨论】:

    • 我刚刚意识到我也不能乱用value。必须使用this[key]
    • @Polv 是的,这不是最直观的!
    • 为了拯救其他人同样的斗争:递归调用replacer,其中this是对象的当前节点,key是被转换的属性,value是预转换的属性值,当 original 是 Date 时是一个字符串。因此:this[key],它给你一个日期而不是value,它给你一个字符串。
    • 太棒了,非常感谢这个(不是双关语)。值得指出的是,this[key] 在箭头函数中不起作用。
    【解决方案3】:

    基于 Matt Johnsons 的answer,我重新实现了toJSON,而不必依赖moment(我认为这是一个很棒的库,但是依赖于像toJSON这样的低级方法困扰我)。

    Date.prototype.toJSON = function () {
      var timezoneOffsetInHours = -(this.getTimezoneOffset() / 60); //UTC minus local time
      var sign = timezoneOffsetInHours >= 0 ? '+' : '-';
      var leadingZero = (Math.abs(timezoneOffsetInHours) < 10) ? '0' : '';
    
      //It's a bit unfortunate that we need to construct a new Date instance 
      //(we don't want _this_ Date instance to be modified)
      var correctedDate = new Date(this.getFullYear(), this.getMonth(), 
          this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds(), 
          this.getMilliseconds());
      correctedDate.setHours(this.getHours() + timezoneOffsetInHours);
      var iso = correctedDate.toISOString().replace('Z', '');
    
      return iso + sign + leadingZero + Math.abs(timezoneOffsetInHours).toString() + ':00';
    }
    

    当提供的值“溢出”时,setHours 方法将调整日期对象的其他部分。来自MDN

    如果您指定的参数超出预期范围,setHours() 会尝试相应地更新 Date 对象中的日期信息。例如,如果您使用 100 作为 secondsValue,则分钟将增加 1(minutesValue + 1),并且将使用 40 作为秒。

    【讨论】:

    • 也许在时区偏移中允许分钟会更好。 IE。 var offsetMinutes = this.getTimezoneOffset() % 60
    • 如果我还没有 moment js 作为依赖项,我想我会使用这样的解决方案
    • 你说得对,通常是整小时,但我知道一个事实,时区也有分钟偏移。例如,尼泊尔是 +05:45。我认为发布有关时间和时区的通用解决方案是一个好主意,以使其尽可能健壮。
    • @Endless,根据您的要求,我删除了: string(三个位置)和: Date(一个位置)。现在它是普通的 javascript。
    • 进一步了解@JonathanWilson:澳大利亚也有非小时制时区
    【解决方案4】:

    不过,当我将其字符串化时,时区会再见

    那是因为Tue Jun 07 2011 04:00:00 GMT+1000 (E. Australia Standard Time) 实际上是Date 对象的toString 方法的结果,而stringify 似乎调用了toISOString 方法。

    所以如果toString 格式是您想要的,那么只需将 that 字符串化:

    JSON.stringify(date.toString());
    

    或者,因为您想稍后将“命令”字符串化,所以首先将 那个 值放在那里:

    var command = { time: date.toString(), contents: 'foo' };
    

    【讨论】:

    • 我知道我可以自己做,但我正在寻找一种用现有对象做的方法。当我只想指定一个部分如何序列化时,必须翻译整个对象是非常狡猾的。
    • 您可以date.toJSON = function () { return this.toString(); } 指定您希望如何对整个对象的这个“日期”进行字符串化。
    • toString 可能有偏移量,但不是 ISO 格式。
    【解决方案5】:

    let date = new Date(JSON.parse(JSON.stringify(new Date(2011, 05, 07, 04, 0, 0))));

    【讨论】:

      【解决方案6】:

      我创建了一个小型库,在JSON.stringify 之后使用 ISO8601 字符串保留时区。该库可让您轻松更改本机 Date.prototype.toJSON 方法的行为。

      npm:https://www.npmjs.com/package/lbdate

      例子:

      lbDate().init();
      
      const myObj = {
        date: new Date(),
      };
      
      const myStringObj = JSON.stringify(myObj);
      
      console.log(myStringObj);
      
      // {"date":"2020-04-01T03:00:00.000+03:00"}
      

      如果需要,该库还为您提供自定义序列化结果的选项。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-09-10
        • 2021-09-17
        • 1970-01-01
        • 2015-11-24
        • 2017-07-29
        • 2023-04-07
        • 2017-07-17
        • 2021-01-16
        相关资源
        最近更新 更多