Finally I've found a solution.
Instead of static timezone offsets I used the IANA code (e.g. Asia/Kabul
) so I can calculate its value live (GMT+4:30
-> +0430
).
1 - Get timezone offset from IANA zone identifier
I used
Intl, a native browser class to format dates. Example:
new Intl.DateTimeFormat("en", {
timeZone: "Pacific/Palau",
day: "2-digit",
month: "2-digit",
year: "numeric",
weekday: "long",
hour: "2-digit",
minute: "2-digit",
hour12: false,
second: "2-digit",
timeZoneName: "short",
}).format(new Date());
("Thursday, 07/15/2021, 17:19:51 PM GMT+9");
With this I could create the Date, but Date constructor will use the local timezone offset and calculate the difference, so the final time in the selected timezone will not be the specified one.
2 - Get timezone from string
From Intl returned date string ("Thursday, 07/15/2021, 17:19:51 PM GMT+9"
) I get the last part (GMT+9
) like this:
const dateSplit = dateStr.split(" ");
const timezoneAbbr = dateSplit[dateSplit.length - ONE];
3 - Map non standard timezone abbreviations to its offset
Now, because some countries have not-standard timezone abbreviations (like AST), we need to map time zone abbreviations to UTC before creating the Date
object (because Date
constructor returns error for some time zone abbreviations). I created this relationship using an enum like this:
export enum TimezoneAbbreviation {
ACDT = '+1030',
ACST = '+0930',
ACT = '-0500',
ADT = '-0300',
4 - Create Date
Finally create date string with format yyyy/MM/dd hh:mm ZZ
(e.g.: 2021/05/10 10:30 UTC-1
) and call Date
constructor with it.
Warn
There's no consensus on timezone abbreviations and may vary (they use CLDR format). Also there is confusing data like duplicated abbreviations:
Abbr |
Name |
UTC offset |
AST |
Arabia Standard Time |
UTC+03 |
AST |
Atlantic Standard Time |
UTC−04 |
SST |
Samoa Standard Time |
UTC−11 |
SST |
Singapore Standard Time |
UTC+08 |