从 UTC 创建日期

默认情况下,Date 对象创建为本地时间。这并不总是令人满意的,例如在服务器和客户端之间传送不在同一时区的日期时。在这种情况下,根本不需要担心时区,直到日期需要在当地时间显示,如果甚至根本不需要的话。

问题

在这个问题中,我们希望与不同时区的某个人通知特定日期(日,月,年)。第一个实现天真地使用本地时间,这导致错误的结果。第二种实现使用 UTC 日期来避免不需要它们的时区。

使用错误结果的朴素方法

function formatDate(dayOfWeek, day, month, year) {
  var daysOfWeek = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
  var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
  return daysOfWeek[dayOfWeek] + " " + months[month] + " " + day + " " + year;
}

//Foo lives in a country with timezone GMT + 1
var birthday = new Date(2000,0,1);
console.log("Foo was born on: " + formatDate(`birthday.getDay()`, `birthday.getDate()`,
      `birthday.getMonth()`, `birthday.getFullYear()`));

sendToBar(`birthday.getTime()`);

样本输出:Foo was born on: Sat Jan 1 2000

//Meanwhile somewhere else...

//Bar lives in a country with timezone GMT - 1
var birthday = new Date(`receiveFromFoo()`);
console.log("Foo was born on: " + formatDate(`birthday.getDay()`, `birthday.getDate()`,
      `birthday.getMonth()`, `birthday.getFullYear()`));

样品输出:Foo was born on: Fri Dec 31 1999

因此,Bar 总是相信 Foo 出生于 1999 年的最后一天。

正确的做法

function formatDate(dayOfWeek, day, month, year) {
  var daysOfWeek = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
  var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
  return daysOfWeek[dayOfWeek] + " " + months[month] + " " + day + " " + year;
}

//Foo lives in a country with timezone GMT + 1
var birthday = new Date(Date.UTC(2000,0,1));
console.log("Foo was born on: " + formatDate(`birthday.getUTCDay()`, `birthday.getUTCDate()`,
      `birthday.getUTCMonth()`, `birthday.getUTCFullYear()`));

sendToBar(`birthday.getTime()`);

样品输出:Foo was born on: Sat Jan 1 2000

//Meanwhile somewhere else...

//Bar lives in a country with timezone GMT - 1
var birthday = new Date(`receiveFromFoo()`);
console.log("Foo was born on: " + formatDate(`birthday.getUTCDay()`, `birthday.getUTCDate()`, 
      `birthday.getUTCMonth()`, `birthday.getUTCFullYear()`));

样品输出:Foo was born on: Sat Jan 1 2000

从 UTC 创建日期

如果想要基于 UTC 或 GMT 创建 Date 对象,可以使用 Date.UTC(...) 方法。它使用与最长的 Date 构造函数相同的参数。此方法将返回一个数字,表示自 1970 年 1 月 1 日 00:00:00 UTC 以来经过的时间。

console.log(Date.UTC(2000,0,31,12));

样本输出:949320000000

var utcDate = new Date(Date.UTC(2000,0,31,12));
console.log(utcDate);

样品输出:Mon Jan 31 2000 13:00:00 GMT+0100 (West-Europa (standaardtijd))

不出所料,UTC 时间和本地时间之间的差异实际上是时区偏移转换为毫秒。

var utcDate = new Date(Date.UTC(2000,0,31,12));
var localDate = new Date(2000,0,31,12);

console.log(localDate - utcDate === `utcDate.getTimezoneOffset()` * 60 * 1000);

样品输出:true

更改 Date 对象

所有 Date 对象修饰符,例如 setDate(...)setFullYear(...) 都具有等效的 UTC 时间而不是本地时间的参数。

var date = new Date();
date.setUTCFullYear(2000,0,31);
date.setUTCHours(12,0,0,0);
console.log(date);

样品输出:Mon Jan 31 2000 13:00:00 GMT+0100 (West-Europa (standaardtijd))

其他 UTC 特定的修饰符是 .setUTCMonth().setUTCDate()(当月的某天),.setUTCMinutes().setUTCSeconds().setUTCMilliseconds()

使用 getTime() 和 setTime()避免歧义

如果需要上述方法来区分日期中的歧义,通常更容易将日期作为自 1970 年 1 月 1 日 00:00:00 UTC 以来经过的时间进行通信。此单个数字表示单个时间点,并且可以在必要时转换为本地时间。

var date = new Date(Date.UTC(2000,0,31,12));
var timestamp = date.getTime();
//Alternatively
var timestamp2 = Date.UTC(2000,0,31,12);
console.log(timestamp === timestamp2);

样品输出:true

//And when constructing a date from it elsewhere...
var otherDate = new Date(timestamp);

//Represented as an universal date
console.log(`otherDate.toUTCString()`);
//Represented as a local date
console.log(otherDate);

样本输出:

Mon, 31 Jan 2000 12:00:00 GMT
Mon Jan 31 2000 13:00:00 GMT+0100 (West-Europa (standaardtijd))