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


232

我有一个特定时区中的日期时间作为字符串,我想将其转换为本地时间。但是,我不知道如何在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);  

据我所知,我可以设置UTC时间或本地时间。但是,如何设置其他时区的时间?

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

如何在javascript中将时间从其他时区转换为本地时间?



1
日期对象没有时区,它们是UTC。
RobG

Answers:


349

背景

JavaScript的Date对象在内部跟踪UTC的时间,但通常在运行它的计算机的本地时间接受输入并产生输出。它几乎没有其他地方的时间来处理时间。

Date对象的内部表示形式是单个数字,表示自以来经过的毫秒数1970-01-01 00:00:00 UTC,而与leap秒无关。 Date对象本身没有存储时区或字符串格式。 当各种功能Date使用对象的,计算机的本地时区将应用于内部表示。如果函数产生一个字符串,则可以考虑计算机的语言环境信息以确定如何产生该字符串。具体细节因功能而异,有些具体取决于实现。

唯一的操作Date对象可以用非本地时区做是:

  • 它可以解析包含任何时区的数字UTC偏移量的字符串。它使用它来调整要解析的值,并存储等效的UTC。原始本地时间和偏移量未保留在结果Date对象中。例如:

    var d = new Date("2020-04-13T00:00:00.000+08:00");
    d.toISOString()  //=> "2020-04-12T16:00:00.000Z"
    d.valueOf()      //=> 1586707200000  (this is what is actually stored in the object)
  • 在已实现ECMASCript国际化API(又称为“国际”)的环境中,Date对象可以生成针对特定时区标识符的特定于语言环境的字符串。这可以通过timeZoneto 的选项toLocaleString及其变体来实现。大多数实施都将支持IANA时区标识符,例如'America/New_York'。例如:

    var d = new Date("2020-04-13T00:00:00.000+08:00");
    d.toLocaleString('en-US', { timeZone: 'America/New_York' })
    //=> "4/12/2020, 12:00:00 PM"
    // (midnight in China on Apring 13th is noon in New York on April 12th)

    大多数现代环境都支持IANA时区标识符的完整集合(请参阅此处的兼容性表)。但是,请记住,Intl 需要支持的唯一标识符是'UTC',因此您应该仔细检查是否需要支持旧版浏览器或非典型环境(例如,轻型IoT设备)。

图书馆

有几个可用于时区的库。尽管他们仍然不能使Date对象的行为有所不同,但是它们通常实现标准的IANA时区数据库并提供在JavaScript中使用该对象的功能。现代库使用Intl API提供的时区数据,但是较旧的库通常会产生开销,尤其是如果您在Web浏览器中运行,因为数据库可能会很大。这些库中的某些库还允许您有选择地减少数据集,方法是支持哪种时区和/或可以使用的日期范围。

这里是要考虑的库:

基于国际的图书馆

新开发应从以下实现之一中进行选择,这些实现依赖于Intl API的时区数据:

非国际图书馆

这些库得到维护,但负担打包自己的时区数据的负担,这可能会很大。

*虽然以前建议使用Moment和Moment-Timezone,但Moment团队现在更喜欢用户选择Luxon进行新开发。

停产的图书馆

这些库已正式停产,不应再使用。

未来提案

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


1
由于表示的时间戳与时区无关
因此

1
@Bergi-我对此进行了重新考虑,并同意您的看法。相应地更新了我的答案。
马特·约翰逊

1
当您在Firebug控制台中执行此操作时 var date_time = new Date(),的值为date_time(在AEST中对我而言)Date {Sun Aug 21 2016 22:18:47 GMT+1000 (AEST)},因此似乎存储了时区。如何确保date_time纯粹是UTC时间,不包括任何时区?
user1063287 '16

嗯,根据此答案(stackoverflow.com/a/8047885/1063287),它是这样的: new Date().getTime();
user1063287 '16

1
@ user1063287- toString方法使用主机时区偏移量来生成“本地”时区中的日期和时间。日期对象本身具有一个时间值,该时间值与1970-01-01T00:00:00Z之间存在偏移,因此实际上是UTC。要查看UTC日期和时间,请使用toISOString
RobG '16

68

正如马特·约翰逊(Matt Johnson)所说

如果可以将使用限制为现代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()}`);


5
我想知道为什么这个答案没有得到更多的爱。对我来说,这绝对是最好的解决方案。在我的应用中,所有日期均为UTC,但需要在UI中的明确IANA时区中输入或输出。为此,我使用了此解决方案,并且效果很好。简单,有效,标准。
logidelic

2
@logidelic这是一个很新的帖子。老实说,当我震惊时,我很惊讶自己可以toLocaleString用来达到相反的效果,是的,这应该是常识!去写一篇关于它的文章,并提到我:-)
commonpike

1
对于不在GMT中的节点环境,上述代码需要从var invdate = new Date(date.toLocaleString('en-US',{timeZone:ianatz}))更改。放入var invdate = new Date($ {date.toLocaleString('en-US',{timeZone:ianatz})} GMT`);
Mike P.

5
浏览器不需要解析toLocaleString的输出,因此它基于错误的前提。
RobG

3
“……为什么这个答案没有得到更多的爱?” -因为Date它返回的对象是谎言。即使显示了示例,字符串输出也包含本地计算机的时区。在我的计算机上,两个结果显示为“ GMT-0700(太平洋夏令时)”,这仅对第一个正确(多伦多实际上是美国东部时间。)除了字符串输出之外,Date对象中保存的时间戳也已转移到另一个时间点。这可能是一种有用的技术,但有很多警告-主要是Date对象函数将不会具有正常的输出。
Matt Johnson-Pint

30

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

new Date('Feb 28 2013 19:00:00 EST')

要么

new Date('Feb 28 2013 19:00:00 GMT-0500')

由于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)"

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

> new Date('Feb 28 2013 19:00:00 GMT-0500').getHours()
< 8

(这8意味着将时间转换为我的当地时间后UTC+8,小时数为8。)


16
解析除ISO 8601扩展格式以外的任何格式均取决于实现,因此不应依赖。时区缩写没有标准,例如“ EST”可能代表3个不同时区中的任何一个。
RobG '16

1
此注释中的示例通常在Internet Explorer中不起作用。如本文前面的评论所述,ISO 8601非常重要。我通过阅读ECMA-262(Javascript 5th)版语言规范来确认。
Dakusan

12

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

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;
};

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

dateWithTimeZone("America/Los_Angeles",2019,8,8,0,0,0)

此解决方案适合我的需要。我不确定我该如何准确地解释夏令时。
侯赛因·阿明

为什么不直接返回tzDate
莱维

8

我发现执行此操作最受支持的方法而无需担心第三方库,方法是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();

编辑

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


您在哪里计算timezone_offset
阿南·

1
糟糕,我给我的变量之一贴错了标签。timezone_offset应该是offset,反之亦然。我已经编辑了答案以显示正确的变量名。
Shaun Cockerill

3

我在单元测试中遇到了类似的问题(特别是在单元测试在本地运行以创建快照,然后CI服务器(可能)在另一个时区运行(可能导致快照比较失败)的情况下)。我嘲笑了我们Date的支持方法,例如:

describe('...', () => {
  let originalDate;

  beforeEach(() => {
    originalDate = Date;
    Date = jest.fn(
      (d) => {
        let newD;
        if (d) {
          newD = (new originalDate(d));
        } else {
          newD = (new originalDate('2017-05-29T10:00:00z'));
        }
        newD.toLocaleString = () => {
          return (new originalDate(newD.valueOf())).toLocaleString("en-US", {timeZone: "America/New_York"});
        };
        newD.toLocaleDateString = () => {
          return (new originalDate(newD.valueOf())).toLocaleDateString("en-US", {timeZone: "America/New_York"});
        };
        newD.toLocaleTimeString = () => {
          return (new originalDate(newD.valueOf())).toLocaleTimeString("en-US", {timeZone: "America/New_York"});
        };
        return newD;
      }
    );
    Date.now = () => { return (Date()); };
  });

  afterEach(() => {
    Date = originalDate;
  });

});


2

在上面的答案的基础上,我正在使用本机的一种衬纸将长时区字符串转换为三个字母字符串:

var longTz = 'America/Los_Angeles';
var shortTz = new Date().
    toLocaleString("en", {timeZoneName: "short", timeZone: longTz}).
    split(' ').
    pop();

这将根据提供的日期提供PDT或PST。在我的特定用例中,在Salesforce(Aura / Lightning)上进行开发,我们能够从后端获取长格式的用户时区。


仅仅因为我喜欢不时地玩学究,“ America / Los_Angeles”不是时区,而是特定偏移量和夏令时规则以及这些更改的历史记录的“代表位置”(根据IANA时区数据库) 。;-)
RobG

哈哈,您必须在这里对多个答案发表评论:P
Shane

2

我知道它的3年为时已晚,但是也许可以帮助其他人,因为除了矩时区库(除了他在这里要的东西)外,我没有找到其他类似的东西。

我在德国时区做了类似的事情,这有点复杂,因为夏令时和and年有366天。

它可能需要使用“ isDaylightSavingTimeInGermany”功能进行一些工作,而不同的时区会在夏令时的不同时间改变。

无论如何,请查看此页面:https : //github.com/zerkotin/german-timezone-converter/wiki

主要方法是:convertLocalDateToGermanTimezone convertGermanDateToLocalTimezone

我已尽力将其记录在文档中,因此不会那么混乱。


这应该是评论,而不是答案。
RobG


0

对于Ionic用户,我不得不这么做,因为.toISOString()必须将其与html模板一起使用。

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

我用这个解决了:

date = new Date();
public currentDate: any = new Date(this.date.getTime() - this.date.getTimezoneOffset()*60000).toISOString();

* 60000表示UTC -6是CST,因此无论需要什么TimeZone,都可以更改数量和差异。


这不是发问者要寻找的答案。他正在寻找将日期设置为特定时区的方法。
理查德·韦尔吉斯

@RichardVergis该问题明确指出用户想要更改为本地时间,但没有声明他们所在的时区。我以我的时区为例,用户可以根据我的示例清楚地读取内容,他们可以输入他们的时区偏移量。
Stephen Romero

0

也许这会对您有帮助

/**
 * Shift any Date timezone.
 * @param {Date} date - Date to update.
 * @param {string} timezone - Timezone as `-03:00`.
 */
function timezoneShifter(date, timezone) {
  let isBehindGTM = false;
  if (timezone.startsWith("-")) {
    timezone = timezone.substr(1);
    isBehindGTM = true;
  }

  const [hDiff, mDiff] = timezone.split(":").map((t) => parseInt(t));
  const diff = hDiff * 60 + mDiff * (isBehindGTM ? 1 : -1);
  const currentDiff = new Date().getTimezoneOffset();

  return new Date(date.valueOf() + (currentDiff - diff) * 60 * 1000);
}



const _now = new Date()
console.log(
  [
    "Here: " + _now.toLocaleString(),
    "Greenwich: " + timezoneShifter(_now, "00:00").toLocaleString(),
    "New York: " + timezoneShifter(_now, "-04:00").toLocaleString(),
    "Tokyo: " + timezoneShifter(_now, "+09:00").toLocaleString(),
    "Buenos Aires: " + timezoneShifter(_now, "-03:00").toLocaleString(),
  ].join('\n')
);


-2

面临同样的问题,用过这个

Console.log(Date.parse(“ 2018年6月13日10:50:39 GMT + 1”));

它将返回毫秒,您可以检查到毫秒数,以+100 timzone初始化英国时间希望对您有所帮助!


3
(此帖子似乎无法为问题提供高质量的答案。请编辑您的答案,或仅将其发布为对问题的评论)。
sɐunıɔןɐqɐp
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.