如何初始化一个JavaScript日期到一个特定的时区

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

885099 次浏览

背景

JavaScript的Date对象在内部跟踪UTC时间,但通常接受输入并以运行它的计算机的本地时间生成输出。它很少有使用其他时区时间的设施。

Date对象的内部表示是一个数字,表示从1970-01-01 00:00:00 UTC开始经过的毫秒数,不考虑闰秒。

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(又名"Intl")的环境中,Date对象可以生成特定于地区的字符串,调整为给定的时区标识符。这是通过timeZone选项到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-based库

新的开发应该从这些实现中选择一个,这些实现依赖于Intl API的时区数据:

Non-Intl库

这些库是可以维护的,但是需要打包它们自己的时区数据,这些数据可能相当大。

*虽然Moment和Moment- timezone之前是推荐的,Moment团队现在更喜欢用户选择Luxon进行新的开发。

停止库

这些库已经正式停止使用,不应该再使用。

未来的建议

TC39时间提案旨在提供一组新的标准对象,用于在JavaScript语言本身中处理日期和时间。这将包括对时区感知对象的支持。

常见的错误

有几种经常尝试的方法是错误的,通常应该避免。

解析

new Date(new Date().toLocaleString('en', {timeZone: 'America/New_York'}))

上面的方法正确地使用Intl API创建特定时区的字符串,但是它错误地将该字符串传递回Date构造函数。在这种情况下,解析将是特定于实现的,并且可能完全失败。如果成功,结果Date对象现在很可能表示错误的时间,因为在解析期间将应用计算机的本地时区。

时代的转变

var d = new Date();
d.setTime(d.getTime() + someOffset * 60000);

上面的方法试图通过将Unix时间戳按其他时区偏移量移动来操作Date对象的时区。但是,由于Date对象只跟踪UTC时间,它实际上只是使Date对象表示不同的时间点。

有时直接在构造函数上使用相同的方法,这也是无效的。

Epoch shift有时在日期库内部用作避免编写日历算术的快捷方式。这样做时,必须避免对非utc属性的任何访问。例如,一旦移位,对getUTCHours的调用是可以接受的,但是对getHours的调用是无效的,因为它使用本地时区。

它被称为“epoch shift”,因为如果正确使用,Unix epoch (1970-01-01T00:00:00.000Z)现在不再与0的时间戳相关,而是根据偏移量转移到不同的时间戳。

如果您没有创建日期库,则不应该进行epoch转移。

更多关于时代转移的细节,请观看格雷格·米勒在2015年CppCon上的这个视频片段。该视频是关于c++中的time_t,但解释和问题是相同的。(对于JavaScript的人来说,每次你听到Greg提到time_t,就想想“Date对象”。)

试着做一个“UTC日期”

var d = new Date();
var utcDate = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()));

在本例中,dutcDate都是相同的。构造utcDate的工作是多余的,因为d已经用UTC表示了。检查toISOStringgetTimevalueOf函数的输出将为两个变量显示相同的值。

类似的方法如下:

var d = new Date();
var utcDate = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds());

这种方法将UTC值传递给需要使用本地时间值的Date构造函数。结果的Date对象现在表示一个完全不同的时间点。这本质上与前面描述的历元偏移的结果相同,因此应该避免。

获取基于utc的Date对象的正确方法是new Date()。如果需要UTC格式的字符串表示,则使用new Date().toISOString()

你可以在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时间,当你调用像toString这样的东西时,javascript将把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。)

我知道这3年太晚了,但也许它可以帮助其他人,因为除了moment-timezone库之外,我还没有找到任何类似的东西,这与他在这里要求的不完全相同。

我已经做了类似的德国时区, 这有点复杂,因为日光节约时间和闰年,你有366天

它可能需要与“isDaylightSavingTimeInGermany”函数做一些工作,而不同的时区在不同的日光节约时间发生变化。

不管怎样,看看这个页面: # EYZ0 < / p >

主要方法有: convertLocalDateToGermanTimezone convertGermanDateToLocalTimezone < / p >

我已经努力记录了它,所以它不会那么令人困惑。

尝试:date-from-timezone,它在本地可用的Intl.DateTimeFormat的帮助下解析预期日期。

我在我的一个项目中使用这种方法已经有几年了,但现在我决定把它作为一个小的OS项目发布:)

我在单元测试中遇到了类似的问题(特别是开玩笑地说,单元测试在本地运行以创建快照,然后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;
});


});

遇到了同样的问题,用了这个吗

Console.log(日期。解析(“2018年6月13日10:50:39 GMT+1”);

它将返回毫秒,你可以检查有+100 timzone初始化英国时间 希望能有所帮助!!< / p >

尝试使用npm的ctoc。 # EYZ0 < / p >

它有简单的功能来改变时区(大多数时区大约400)和你想要显示的所有自定义格式。

我发现最受支持的方法是使用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函数将返回所需的结果。

正如马特·约翰逊所说

如果你可以限制你的使用现代网络浏览器,你现在可以做 下面没有任何特殊库:

# EYZ0

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

所以尽管浏览器在创建日期时不能读取IANA时区,或者有任何方法来更改现有date对象上的时区,但似乎有一个hack围绕它:

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); // needs to substract


}


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


console.log(`Here: ${here.toString()}\nToronto: ${there.toString()}`);

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

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

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

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

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)

对于Ionic用户,我有地狱,因为.toISOString()必须与html模板一起使用。

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

我用这个解决了它:

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

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

我有同样的问题,但我们可以使用我们想要的时区
我们使用.toLocaleDateString()

< p >如:
var day=new Date();
const options= {day:'numeric', month:'long', year:"numeric", timeZone:"Asia/Kolkata"};
const today=day.toLocaleDateString("en-IN", options);
console.log(today);

//For Mumbai time difference is 5.5 hrs so
city_time_diff=5.5; //change according to your city


let time_now = Date.now();
time_now = time_now + (3600000 * city_time_diff); //Add our city time (in msec);
let new_date = new Date(time_now);
console.log("My city time is: ", new_date);

感谢@commonpike的回答,我写了一个函数,它接受ISO字符串日期,如2020-10-10T08:00:00.000作为输入,并发送一个包含2个主要属性的对象。

第一个是fromUtc是一个日期,对应于作为参数输入的timeZone。

第二个是toUtc,它允许您格式化源自fromUtc的日期。

const timeZoneTransformer = (stringDate, timeZone = "Europe/Paris") => {
const now = new Date();
const serverDate = new Date(stringDate);
const utcDate = new Date(
Date.UTC(
serverDate.getFullYear(),
serverDate.getMonth(),
serverDate.getDate(),
serverDate.getHours(),
serverDate.getMinutes(),
serverDate.getSeconds()
)
);
const invdate = new Date(
serverDate.toLocaleString("en-US", {
timeZone,
})
);
const diff = now.getTime() - invdate.getTime();
const adjustedDate = new Date(now.getTime() - diff);
return {
toUtc: utcDate,
fromUtc: adjustedDate,
};
};
const fromUtc = timeZoneTransformer("2020-10-10T08:00:00.000").fromUtc;
console.log(fromUtc);
const toUtc = timeZoneTransformer(fromUtc).toUtc;
console.log(toUtc);

我在运行GCP云函数时遇到了这个问题。当然,它在本地机器上工作,但在云中运行会使操作系统默认的new Date()(本地)无关紧要。在我的例子中,来自云的api调用需要东部标准时间,ISO格式(没有"Z")偏移量为"-0500"或“-0400”;根据夏令时,例如:

# EYZ0

同样,这不是浏览器格式问题,所以我被迫采用这种格式,以便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);

试试这样的方法,

public static getTimezoneOffset(timeZone: string, date = new Date()): number {
const localDate = date.toLocaleString('fr', { timeZone, timeZoneName: 'long' });
const tz = localDate.split(' ');
const TZ = localDate.replace(tz[0], '').replace(tz[1], '').replace(' ', '');
const dateString = date.toString();
const offset = (Date.parse(`${dateString} UTC`) - Date.parse(`${dateString}${TZ}`)) / (3600 * 1000);
return offset;
}

这里有几个工作答案,但不知为何,他们中的很多人似乎让你到字符串,但不是回到一个日期对象,你开始,所以这里是我的简单的非函数采取如何在JS日期更改时区:

var TZ='Australia/Brisbane'; //Target timezone from server
var date = new Date();       //Init this to a time if you don't want current time
date=new Date(Date.parse(date.toLocaleString("en-US", {timeZone: TZ})));
//Just a clarification on what happens
// 1) First new Date() gives you a Date object at current time in the clients browser local timezone
// 2) .toLocaleString takes that time, and returns a string if time in the target timezone
// 3) Date.parse converts that new string to a Unix epoch number
// 4) new Date() converts the Unix epoch into a Date object in the new TimeZone.
// Now I can use my usual getHours and other Date functions as required.

希望这对其他人有所帮助(如果你找到了下面的答案!)

简单的# EYZ0

传入时区与UTC时间的偏移量

function initDateInTimezone(offsetHours) {
const timezoneOffsetInMS = offsetHours * 60 * 60000;
let d = new Date().getTimezoneOffset() * 60000 + timezoneOffsetInMS;
const date = new Date(new Date().getTime() - d);
return date
}

我不知道为什么所有这些答案都如此复杂。在本地/所需时区中创建仅限日期的日期时,只需使用YYYY-MM-DD ZZZ

创建一个本地日期:

var myDate = new Date('2022-11-29 CST')

日期将以UTC格式存储在存储器中,很好。

从存储中获取日期并显示为本地:

myDate.toLocaleDateString()

11/29/2022