JSON Stringify改变日期时间因为UTC

137

因为我所在的位置,JavaScript 中的日期对象始终表示为 UTC+2。因此如下所示:

Mon Sep 28 10:00:00 UTC+0200 2009

问题是进行JSON.stringify会将上述日期转换为
2009-09-28T08:00:00Z  (notice 2 hours missing i.e. 8 instead of 10)

我需要的是日期和时间被尊重,但事实并非如此,因此应该:
2009-09-28T10:00:00Z  (this is how it should be)

基本上我使用这个:

var jsonData = JSON.stringify(jsonObject);

我尝试传递替换器参数(stringify 的第二个参数),但问题是值已经被处理。

我还尝试在日期对象上使用 toString() 和 toUTCString(),但这些也不能给我想要的结果...

有人能帮助我吗?


24
2009-09-28T10:00:00ZMon Sep 28 10:00:00 UTC+0200 2009并不代表相同的时间点。ISO 8601日期中的Z表示UTC,UTC上的10点与+0200上的10点是不同的时间点。如果您想将日期序列化为正确的时区,则需要另行处理,但您正在要求我们将其序列化为明显、客观上错误的表示形式。 - Mark Amery
1
补充Mark的评论,大多数情况下最佳实践是将日期时间存储为UTC时间,这样您可以支持不同时区的用户。 - JoeCodeFrog
这个被接受的答案解决了我的问题 https://dev59.com/IV0Z5IYBdhLWcg3wwyne#31104671 - Chathura Sudarshana
20个回答

78

最近我遇到了同样的问题。通过以下代码解决:

x = new Date();
let hoursDiff = x.getHours() - x.getTimezoneOffset() / 60;
let minutesDiff = (x.getHours() - x.getTimezoneOffset()) % 60;
x.setHours(hoursDiff);
x.setMinutes(minutesDiff);

3
谢谢。我在这里找到了一个很棒的库,http://blog.stevenlevithan.com/archives/date-time-format你只需要这样做(也许会对你有所帮助),传递false,它就不会进行转换。var something = dateFormat(myStartDate, "isoDateTime", false); - mark smith
15
这是不正确的,因为它会使你的代码不具备时区安全性 - 当你读取日期时,应该校正时区。 - olliej
@marksmith,你分享的链接(http://blog.stevenlevithan.com/archives/date-time-format)提供的解决方案对我非常有效。我差点就要疯了。谢谢你。+1 - Laci
9
这个答案是错误的。提问者没有意识到“2009-09-28T08:00:00Z”和“Mon Sep 28 10:00:00 UTC+0200 2009”是同一时刻,调整时区偏移实际上会产生错误的时间。 - RobG
你还需要注意分钟。否则,带有30分钟偏移量的国家(印度标准时间为UTC+5:30)需要添加 x.setMinutes(x.getMinutes() - (x.getTimezoneOffset() % 60)); - JScoobyCed
显示剩余3条评论

51

JSON使用Date.prototype.toISOString函数,该函数不表示本地时间--它表示未经修改的UTC时间--如果您查看日期输出,您会发现您处于UTC+2小时,这就是为什么JSON字符串会变化两个小时,但如果这样可以在多个时区正确表示相同的时间。


2
从没想过,但你是对的。这就是解决方案:我可以通过使用原型指定任何我喜欢的格式。 - racs

28

date.toJSON() 方法将UTC时间转为字符串格式(在转换为JSON格式时加入了偏移量)。

date = new Date();
new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toJSON();

10
通常添加代码说明是个好主意。这可以让新开发者理解代码的工作原理。 - Caleb Kleveter
你能解释一下为什么答案中的代码应该有效吗? - Cristik
这对我也有效,但你能解释一下你是怎么做到的吗? - Deven Patil
1
我来解释一下这段代码的第二行。date.getTime() 返回的是毫秒级别的时间,因此我们也应该将第二个操作数转换为毫秒。由于 date.getTimezoneOffset() 返回的是分钟偏移量,我们需要将其乘以 60000,因为 1 分钟 = 60000 毫秒。因此,通过从当前时间减去偏移量,我们可以得到 UTC 时间。 - Maksim Pavlov
1
对我来说,这是最好的答案。 - auron344

23

17

快速解决让JSON.stringify忽略时区问题的方案:

  • 纯Javascript实现(基于Anatoliy的答案):

// Before: JSON.stringify apply timezone offset
const date =  new Date();
let string = JSON.stringify(date);
console.log(string);

// After: JSON.stringify keeps date as-is!
Date.prototype.toJSON = function(){
    const hoursDiff = this.getHours() - this.getTimezoneOffset() / 60;
    this.setHours(hoursDiff);
    return this.toISOString();
};
string = JSON.stringify(date);
console.log(string);

使用 moment + moment-timezone 库:

const date =  new Date();
let string = JSON.stringify(date);
console.log(string);

Date.prototype.toJSON = function(){
    return moment(this).format("YYYY-MM-DDTHH:mm:ss:ms");;
};
string = JSON.stringify(date);
console.log(string);
<html>
  <header>
    <script src="https://momentjs.com/downloads/moment.min.js"></script>
    <script src="https://momentjs.com/downloads/moment-timezone-with-data-10-year-range.min.js"></script>
</header>
</html>


谢谢,但最好创建一个新的日期实例。 - Kjartan Valur Þórðarson
这取决于你的项目:如果你有大量现有日期,并且在部署到其他国家时发现了这个问题,那么你别无选择,只能应用全局补丁(有时你无法直接访问JSON.stringify,例如使用Angular或Axios)。然而,如果你可以修改每个创建日期实例的代码片段,那么其他问题会更好解决。 - Benjamin Caure

12

这里是另一个答案(我个人认为更合适)

var currentDate = new Date(); 
currentDate = JSON.stringify(currentDate);

// Now currentDate is in a different format... oh gosh what do we do...

currentDate = new Date(JSON.parse(currentDate));

// Now currentDate is back to its original form :)

@Rohaan感谢你指出这一点,但问题的标签提到了JavaScript。 - aug

4

您可以使用moment.js来格式化本地时间:

Date.prototype.toISOString = function () {
    return moment(this).format("YYYY-MM-DDTHH:mm:ss");
};

2
不要覆盖常用的Date类。如果使用一些外部模块,这很容易破坏你的应用程序。 - Viacheslav Dobromyslov

3

我有点晚了,但是你总可以使用Prototype覆盖toJson函数以处理日期,像这样:

Date.prototype.toJSON = function(){
    return Util.getDateTimeString(this);
};

在我的情况下,Util.getDateTimeString(this) 返回类似于 "2017-01-19T00:00:00Z" 的字符串。


4
请注意,覆盖浏览器全局变量可能会破坏你嵌入的第三方库,并且这是一种非常不好的反模式。在生产环境中永远不要这样做。 - jakub.g

3
我在处理一些遗留的东西时遇到了这个问题,这些东西只在美国东部工作,并且不存储UTC日期,而是使用EST。我必须根据浏览器中用户输入的日期来过滤日期,因此必须以本地时间的JSON格式传递日期。
只是为了更详细地说明已经发布的解决方案 - 这是我使用的:
// Could be picked by user in date picker - local JS date
date = new Date();

// Create new Date from milliseconds of user input date (date.getTime() returns milliseconds)
// Subtract milliseconds that will be offset by toJSON before calling it
new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toJSON();

我的理解是根据时区偏移量(以分钟为单位返回),从起始日期中减去时间(以毫秒为单位(因此为60000)),以便为将要添加的toJSON()时间增加做好准备。


3

JavaScript通常会将本地时区转换为协调世界时(UTC)。

date = new Date();
date.setMinutes(date.getMinutes()-date.getTimezoneOffset())
JSON.stringify(date)

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