如何将JavaScript日期初始化为特定时区

pav*_*red 176 javascript timezone

我将特定时区的日期时间作为字符串,我想将其转换为本地时间.但是,我不知道如何在Date对象中设置时区.

例如,Feb 28 2013 7:00 PM ET,我可以

var mydate = new Date();
mydate.setFullYear(2013);
mydate.setMonth(02);
mydate.setDate(28);
mydate.setHours(7);
mydate.setMinutes(00);  
Run Code Online (Sandbox Code Playgroud)

据我所知,我可以设置UTC时间或本地时间.但是,我如何在另一个时区设置时间?

我试图使用添加/减去UTC的偏移量,但我不知道如何应对夏令时.我不确定我是否正朝着正确的方向前进.

如何在javascript中将时间从不同的时区转换为当地时间?

Mat*_*int 284

背景

JavaScript的Date对象在内部以UTC格式跟踪时间,但通常在运行它的计算机的本地时间内接受输入和输出.它没有任何在其他时区使用时间的设施.它可以解析和输出UTC或Local的日期,但不能直接与其他时区一起使用.

绝对精确地说,Date对象的内部表示是一个数字,表示自那以后经过的毫秒数1970-01-01 00:00:00 UTC,而不考虑闰秒. Date对象本身没有存储时区或字符串格式.Date使用对象的各种功能时,计算机的本地时区将应用于内部表示.如果函数生成字符串,则可以考虑计算机的区域设置信息以确定如何生成该字符串.细节因功能而异,有些是特定于实现的.

图书馆

幸运的是,有些库可用于处理时区.虽然它们仍然无法使Date对象的行为有所不同,但它们通常实现标准的Olson/IANA时区数据库,并提供在JavaScript中使用它的功能.如果您在Web浏览器中运行,有些会产生开销,因为如果您想要整个数据库,数据库可能会变得有点大.幸运的是,许多这些库允许您有选择地选择要支持的区域,使数据大小更加可口.还有一些使用现代功能从IntlAPI 获取时区数据,而不必自己发货.

我知道有几个这样的库:

对于所有现代用途来说,Luxon可能是最安全的选择,并且由于它使用IntlAPI作为其时区数据,因此是最轻的重量.

Moment-timezone是moment.js的扩展,它带来了自己的时区数据.

js-joda是Joda-Time API(来自Java)的JavaScript实现,包括通过单独模块的时区支持.

date-fns-tz是date-fns 2.x 的扩展名.date-fns-timezone是date-fns 1.x 的扩展名.

BigEasy/TimeZone似乎也在正确的轨道上.

WallTime-js 已经达到使用寿命,所有者正在迁移到时刻时区.

TimeZoneJS已经存在时间最长,但已知有一些长期存在的错误,特别是在夏令时转换时.希望这些将在未来的某个时刻得到修复.

tz.js已经存在了一段时间,但记录不是很好,恕我直言.

您应该评估这些库以查看哪些库可以满足您的需求.如果不确定,请使用时刻/时刻时区.

现代浏览器中的原生支持

如果您可以将使用限制为现代Web浏览器,则现在可以在不使用任何特殊库的情况下执行以下操作:

new Date().toLocaleString("en-US", {timeZone: "America/New_York"})
Run Code Online (Sandbox Code Playgroud)

这不是一个全面的解决方案,但它适用于仅需要输出转换的许多场景(从UTC或本地时间到特定时区,但不是另一个方向).这是ECMAScript国际化API(ECMA-402)的一部分.有关详细信息,请参阅此帖子.此兼容性表跟踪支持的版本.这是Intl上面提到的API,某些库现在正在内部使用.

未来的提案

TC39颞议案的目的是为使用日期和时间在JavaScript语言本身的工作提供了一套新的标准对象.这将包括对时区感知对象的支持.

  • @user1063287——*toString* 方法使用主机时区偏移量生成“本地”时区的日期和时间。日期对象本身具有一个与 1970-01-01T00:00:00Z 的偏移量的时间值,因此有效地为 UTC。要查看 UTC 日期和时间,请使用 [*toISOString*](http://ecma-international.org/ecma-262/7.0/index.html#sec-date.prototype.toisostring)。 (2认同)

com*_*ike 39

正如马特约翰逊所说

如果您可以将使用限制为现代Web浏览器,则现在可以在不使用任何特殊库的情况下执行以下操作:

new Date().toLocaleString("en-US", {timeZone: "America/New_York"})

这不是一个全面的解决方案,但它适用于仅需要输出转换的许多场景(从UTC或本地时间到特定时区,但不是另一个方向).

因此,虽然浏览器在创建日期时无法读取IANA时区,或者有任何方法可以更改现有Date对象的时区,但似乎有一个黑客攻击:

function changeTimezone(date, ianatz) {

  // suppose the date is 12:00 UTC
  var invdate = new Date(date.toLocaleString('en-US', {
    timeZone: ianatz
  }));

  // then invdate will be 07:00 in Toronto
  // and the diff is 5 hours
  var diff = date.getTime() - invdate.getTime();

  // so 12:00 in Toronto is 17:00 UTC
  return new Date(date.getTime() + diff);

}

// E.g.
var there = new Date();
var here = changeTimezone(there, "America/Toronto");

console.log(`Here: ${here.toString()}\nToronto: ${there.toString()}`);
Run Code Online (Sandbox Code Playgroud)

  • “……为什么这个答案没有得到更多的喜爱?” - 因为它返回的“Date”对象是一个谎言。即使所示的示例,字符串输出也包括本地计算机的时区。在我的计算机上,*两个*结果都显示“GMT-0700(太平洋夏令时)”,其中仅对第一个结果正确(多伦多实际上采用东部时间。)除了字符串输出之外,时间戳保存在` Date` 对象已转移到不同的时间点。这*可以*是一种有用的技术,但有很多警告 - 主要是“Date”对象函数不会有正常的输出。 (12认同)
  • 我想知道为什么这个答案没有得到更多的爱。就我而言,这绝对是最好的解决方案。在我的应用程序中,所有日期都是 UTC,但它们需要在 UI 中的显式 IANA 时区中输入或输出。为此,我使用了这个解决方案,它工作得很好。简单、有效、标准。 (7认同)
  • 浏览器不需要解析* toLocaleString *的输出,因此它基于错误的前提。 (3认同)
  • @logidelic 这是一篇很新的帖子。老实说,当我想到你可以使用“toLocaleString”来实现相反的效果时,我自己也很惊讶,是的,这应该是常识!去写一篇关于它的文章并提到我:-) (2认同)
  • 对于尚未处于 GMT 的节点环境,上述代码需要更改为 var invdate = new Date(date.toLocaleString('en-US', { timeZone: ianatz })); 进入 var invdate = new Date(${date.toLocaleString('en-US', { timeZone: ianatz })} GMT`); (2认同)

chi*_*ens 38

这应该可以解决您的问题,请随时提供修复。此方法还将考虑给定日期的夏令时。

dateWithTimeZone = (timeZone, year, month, day, hour, minute, second) => {
  let date = new Date(Date.UTC(year, month, day, hour, minute, second));

  let utcDate = new Date(date.toLocaleString('en-US', { timeZone: "UTC" }));
  let tzDate = new Date(date.toLocaleString('en-US', { timeZone: timeZone }));
  let offset = utcDate.getTime() - tzDate.getTime();

  date.setTime( date.getTime() + offset );

  return date;
};
Run Code Online (Sandbox Code Playgroud)

如何使用时区和本地时间:

dateWithTimeZone("America/Los_Angeles",2019,8,8,0,0,0)
Run Code Online (Sandbox Code Playgroud)

  • 这是一个坏主意,因为未指定 *toLocaleString* 的格式,并且不需要内置解析器来解析它,特别是当包含选项时。此外,在遵守夏令时的区域中,它可能会产生不可靠的结果,其中要解析的日期和时间要么不存在(进入 DST),要么存在两次(退出 DST),其中实现必须选择如何解决这些问题。 (5认同)

mao*_*wtm 20

您可以指定时区偏移量new Date(),例如:

new Date('Feb 28 2013 19:00:00 EST')
Run Code Online (Sandbox Code Playgroud)

要么

new Date('Feb 28 2013 19:00:00 GMT-0500')
Run Code Online (Sandbox Code Playgroud)

由于Date存储UTC时间(即getTime以UTC返回),javascript会将时间转换为UTC,当你调用toStringjavascript之类的东西时会将UTC时间转换为浏览器的本地时区并在本地时区返回字符串,即如果我正在使用UTC+8:

> new Date('Feb 28 2013 19:00:00 GMT-0500').toString()
< "Fri Mar 01 2013 08:00:00 GMT+0800 (CST)"
Run Code Online (Sandbox Code Playgroud)

您也可以使用常规getHours/Minute/Second方法:

> new Date('Feb 28 2013 19:00:00 GMT-0500').getHours()
< 8
Run Code Online (Sandbox Code Playgroud)

(这8意味着在时间转换为我的当地时间之后 - UTC+8小时数是8.)

  • 解析除ISO 8601扩展格式以外的任何格式都取决于实现,不应依赖它.时区缩写没有标准,例如"EST"可能代表3个不同区域中的任何一个. (13认同)
  • 此评论中的示例通常不适用于 Internet Explorer。正如本文前面的评论中提到的,ISO 8601 很重要。我通过阅读 ECMA-262(Javascript 第 5 版)版本语言规范来确认。 (2认同)

Sha*_*ill 9

我发现最受支持的方法是使用getTimezoneOffset计算适当的时间戳,或更新时间,然后使用常规方法获取必要的日期和时间,而不用担心第三方库。

var mydate = new Date();
mydate.setFullYear(2013);
mydate.setMonth(02);
mydate.setDate(28);
mydate.setHours(7);
mydate.setMinutes(00);

// ET timezone offset in hours.
var timezone = -5;
// Timezone offset in minutes + the desired offset in minutes, converted to ms.
// This offset should be the same for ALL date calculations, so you should only need to calculate it once.
var offset = (mydate.getTimezoneOffset() + (timezone * 60)) * 60 * 1000;

// Use the timestamp and offset as necessary to calculate min/sec etc, i.e. for countdowns.
var timestamp = mydate.getTime() + offset,
    seconds = Math.floor(timestamp / 1000) % 60,
    minutes = Math.floor(timestamp / 1000 / 60) % 60,
    hours   = Math.floor(timestamp / 1000 / 60 / 60);

// Or update the timestamp to reflect the timezone offset.
mydate.setTime(mydate.getTime() + offset);
// Then Output dates and times using the normal methods.
var date = mydate.getDate(),
    hour = mydate.getHours();
Run Code Online (Sandbox Code Playgroud)

编辑

我以前UTC在执行日期转换时使用过方法,这是不正确的。通过将偏移量添加到时间,使用本地get函数将返回所需的结果。


cob*_*boy 7

现在是 2023 年,这个问题已经有 10 年历史了,有 110 万次浏览,但我对上述所有解决方案都不满意。

原来的问题是:

[如果]我有"Feb 28 2013 7:00 PM ET",那么[...]据我所知,我可以设置 UTC 时间或本地时间。但是,如何设置另一个时区的时间?

  • 马特·约翰逊-品特的回答非常出色,几乎已经足够了,但它没有直接回答问题。答案演示了在另一个时区设置日期,但使用 UTC 偏移量 ( +08:00) 而不是按照原始问题使用时区名称。
    • 这为我们指明了“正确”的解决方案:在创建日期时指定 UTC 偏移量;我们只需要从 IANA 时区转换为 UTC 偏移量
  • commonpike 的答案Chicken 的答案都重新解析特定于语言环境的格式,这不是标准的 Javascript,Mozilla 不建议这样做
    • 它们还创建了一个 Date 对象,该对象实际上并不代表所需的 UTC 日期和时间
  • maowtm 的答案几乎是正确的,但它同样没有使用 Javascript 规范中支持的唯一格式。
  • 其他答案受到 Matt Johnson-Pint 指出的错误/错误和/或重新解析非标准格式的影响。

2023年“正确”的方式

现代浏览器和 node.js>=18 通过以下查询给出约 400 个时区的列表:

const tzs = Intl.supportedValuesOf("timeZone"); // [ "Africa/Abidjan", "Africa/Accra" ...]
console.log(tzs);
Run Code Online (Sandbox Code Playgroud)

对于每个列出的时区,我们可以通过构造 DateTimeFormat 实例、使用longOffset格式化程序格式化新日期并解析结果来获取 GMT/UTC 偏移量:

// gives '2/28/2013, GMT-05:00'
const tzname = "America/Detroit";
const longOffsetFormatter = new Intl.DateTimeFormat("en-US", {timeZone: tzname ,timeZoneName: "longOffset"});
const longOffsetString = longOffsetFormatter.format(new Date("2013-02-28T19:00:00.000")); // '2/28/2013, GMT-05:00'

// longOffsetString.split('GMT')[1] will give us '-05:00'
const gmtOffset = longOffsetString.split('GMT')[1];

console.log(longOffsetString);
console.log(gmtOffset);
Run Code Online (Sandbox Code Playgroud)

现在我们可以使用时区偏移字符串正确构造一个新的日期(即在内部它将具有正确的 UTC 日期/时间)。

// Feb 28 2013 7:00 PM EST
const gmtOffset = '-05:00'; // see above to figure out where this came from
const d = new Date("2013-02-28T19:00:00.000" + gmtOffset);

console.log(d.toISOString()); // '2013-03-01T00:00:00.000Z' (UTC)
console.log(d.toLocaleString("en-US", {timeZone: "America/Detroit"})); // '2/28/2013, 7:00:00 PM'
Run Code Online (Sandbox Code Playgroud)


Ste*_*ero 5

对于 Ionic 用户,我对此感到非常恼火,因为.toISOString()必须与 html 模板一起使用。

这将获取当前日期,但当然可以添加到所选日期的先前答案中。

我用这个修复了它:

date = new Date();
public currentDate: any = new Date(this.date.getTime() - this.date.getTimezoneOffset()*60000).toISOString();
Run Code Online (Sandbox Code Playgroud)

*60000 表示 UTC -6,即 CST,因此无论需要什么时区,都可以更改数字和差异。


smo*_*re4 5

我在运行 GCP Cloud Function 时遇到了这个问题。当然,它可以在本地计算机上运行,​​但是在云中运行使得操作系统默认(本地)变得 new Date()无关紧要。就我而言,来自云端的 api 调用需要东部标准时间,采用 ISO 格式(不带“Z”),偏移量为“-0500”或“-0400”,具体取决于 DST,例如:

2021-12-01T00:00:00.000-0500

同样,这不是浏览器格式问题,因此我被迫采用这种格式才能使 api 调用正常工作。

使用 @chickens 代码作为开始,这就是有效的:

var date = new Date(); 
var now_utc =  Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(),
date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());

var dt = new Date(now_utc);

let utcDate = new Date(dt.toLocaleString('en-US', { timeZone: "UTC" }));
let tzDate = new Date(dt.toLocaleString('en-US', { timeZone: "America/New_York" }));
let offset1 = utcDate.getTime() - tzDate.getTime();
let offset2 = offset1/60000;
let o1 = Math.abs(offset2);
console.log(offset2)
var offsetValue1 =  (offset2 < 0 ? "+" : "-") + ("00" + Math.floor(o1 / 60)).slice(-2) + ("00" + (o1 % 60)).slice(-2);
console.log(offsetValue1)
dt.setTime(dt.getTime() - offset1);

console.log(dt.toISOString());
console.log(dt.toISOString().slice(0,-1)+offsetValue1);
Run Code Online (Sandbox Code Playgroud)