@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
490 lines (464 loc) • 17.6 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([], function() {
"use strict";
/**
* Static collection of utility functions to handle time zone related conversions
*
* @author SAP SE
* @version 1.147.0
* @namespace
* @alias module:sap/base/i18n/date/TimezoneUtils
* @private
*/
var TimezoneUtils = {};
/**
* Cache for the (browser's) local IANA timezone ID
*
* @type {string}
*/
var sLocalTimezone = "";
/**
* Cache for valid time zones provided by <code>Intl.supportedValuesOf("timeZone")</code>
*
* @type {Array}
*/
var aSupportedTimezoneIDs;
/**
* Cache for Intl.DateTimeFormat instances
*/
var oIntlDateTimeFormatCache = {
_oCache: new Map(),
/**
* When cache limit is reached, it gets cleared
*/
_iCacheLimit: 10,
/**
* Creates or gets an instance of Intl.DateTimeFormat.
*
* @param {string} sTimezone IANA timezone ID
* @returns {Intl.DateTimeFormat} Intl.DateTimeFormat instance
*/
get: function (sTimezone) {
var cacheEntry = this._oCache.get(sTimezone);
if (cacheEntry) {
return cacheEntry;
}
var oOptions = {
hourCycle: "h23",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
fractionalSecondDigits: 3,
day: "2-digit",
month: "2-digit",
year: "numeric",
timeZone: sTimezone,
timeZoneName: 'short',
era: 'narrow',
weekday: "short"
};
var oInstance = new Intl.DateTimeFormat("en-US", oOptions);
// only store a limited number of entries in the cache
if (this._oCache.size === this._iCacheLimit) {
this._oCache = new Map();
}
this._oCache.set(sTimezone, oInstance);
return oInstance;
}
};
/**
* Uses the <code>Intl.supportedValuesOf('timeZone')</code> and <code>Intl.DateTimeFormat</code>
* API to check if the browser can handle the given IANA timezone ID.
* <code>Intl.supportedValuesOf('timeZone')</code> offers direct access to the list of supported
* time zones. It is not yet supported by all browsers but if it is supported and the given time
* zone is in the list it is faster than probing.
*
* <code>Intl.supportedValuesOf('timeZone')</code> does not return all IANA timezone IDs which
* the <code>Intl.DateTimeFormat</code> can handle, e.g. "Japan", "Etc/UTC".
*
* @param {string} sTimezone The IANA timezone ID which is checked, e.g <code>"Europe/Berlin"</code>
* @returns {boolean} Whether the time zone is a valid IANA timezone ID
* @private
* @ui5-restricted sap.ui.comp.util.DateTimeUtil, sap.ui.core.format.DateFormat, sap.viz,
* sap/base/i18n/Localization, sap/ui/core/format/TimezoneUtil
*/
TimezoneUtils.isValidTimezone = function(sTimezone) {
if (!sTimezone) {
return false;
}
if (Intl.supportedValuesOf) {
try {
aSupportedTimezoneIDs = aSupportedTimezoneIDs || Intl.supportedValuesOf('timeZone');
if (aSupportedTimezoneIDs.includes(sTimezone)) {
return true;
}
// although not contained in the supportedValues it still can be valid, therefore continue
} catch (oError) {
// ignore error
aSupportedTimezoneIDs = [];
}
}
try {
oIntlDateTimeFormatCache.get(sTimezone);
return true;
} catch (oError) {
return false;
}
};
/**
* Converts a date to a specific time zone.
* The resulting date reflects the given time zone such that the "UTC" Date methods
* can be used, e.g. Date#getUTCHours() to display the hours in the given time zone.
*
* @example
* var oDate = new Date("2021-10-13T15:22:33Z"); // UTC
* // time zone difference UTC-4 (DST)
* TimezoneUtils.convertToTimezone(oDate, "America/New_York");
* // result is:
* // 2021-10-13 11:22:33 in America/New_York
* // same as new Date("2021-10-13T11:22:33Z"); // UTC
*
* @param {Date} oDate The date which should be converted.
* @param {string} sTargetTimezone The target IANA timezone ID, e.g <code>"Europe/Berlin"</code>
* @returns {Date} The new date in the target time zone.
* @private
* @ui5-restricted sap.ui.core.format.DateFormat, sap.ui.comp.util.DateTimeUtil, sap.viz,
* sap/ui/core/format/TimezoneUtil
*/
TimezoneUtils.convertToTimezone = function(oDate, sTargetTimezone) {
var oFormatParts = this._getParts(oDate, sTargetTimezone);
return TimezoneUtils._getDateFromParts(oFormatParts);
};
/**
* Uses the <code>Intl.DateTimeFormat</code> API to convert a date to a specific time zone.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/formatToParts
* @param {Date} oDate The date which should be converted.
* @param {string} sTargetTimezone The target IANA timezone ID, e.g <code>"Europe/Berlin"</code>
* @returns {{
* day: string,
* era: string,
* fractionalSecond: string,
* hour: string,
* minute: string,
* month: string,
* second: string,
* timeZoneName: string,
* weekday: string,
* year: string
* }} An object containing the date and time fields considering the target time zone.
* @private
* @ui5-restricted sap.viz, sap/ui/core/date/UI5Date, sap/ui/core/format/TimezoneUtil
*/
TimezoneUtils._getParts = function(oDate, sTargetTimezone) {
var sKey, oPart,
oDateParts = Object.create(null),
oIntlDate = oIntlDateTimeFormatCache.get(sTargetTimezone),
// clone the date object before passing it to the Intl API, to ensure that no
// UniversalDate gets passed to it;
// no need to use UI5Date.getInstance as only the UTC timestamp is used
oParts = oIntlDate.formatToParts(new Date(oDate.getTime()));
for (sKey in oParts) {
oPart = oParts[sKey];
if (oPart.type !== "literal") {
oDateParts[oPart.type] = oPart.value;
}
}
return oDateParts;
};
/**
* Creates a Date from the provided date parts.
*
* @param {object} oParts Separated date and time fields as object, see {@link #_getParts}.
* @returns {Date} Returns the date object created from the provided parts.
* @private
* @ui5-restricted sap.viz, sap/ui/core/date/UI5Date, sap/ui/core/format/TimezoneUtil
*/
TimezoneUtils._getDateFromParts = function(oParts) {
// no need to use UI5Date.getInstance as only the UTC timestamp is used
var oDate = new Date(0),
iUTCYear = parseInt(oParts.year);
if (oParts.era === "B") {
// The JS Date uses astronomical year numbering which supports year zero and negative
// year numbers.
// The Intl.DateTimeFormat API uses eras (no year zero and no negative year numbers).
// years around zero overview:
// | Astronomical | In Era
// | 2 | 2 Anno Domini (era: "A")
// | 1 | 1 Anno Domini (era: "A")
// | 0 | 1 Before Christ (era: "B")
// | -1 | 2 Before Christ (era: "B")
// | -2 | 3 Before Christ (era: "B")
// For the conversion to the JS Date the parts returned by the Intl.DateTimeFormat API
// need to be adapted.
iUTCYear = (iUTCYear * -1) + 1;
}
// Date.UTC cannot be used here to be able to support dates before the UNIX epoch
oDate.setUTCFullYear(iUTCYear,
parseInt(oParts.month) - 1,
parseInt(oParts.day));
oDate.setUTCHours(
parseInt(oParts.hour),
parseInt(oParts.minute),
parseInt(oParts.second),
parseInt(oParts.fractionalSecond || 0)); // some older browsers don't support fractionalSecond, e.g. Safari < 14.1 */
return oDate;
};
/**
* Gets the offset to UTC in seconds for a given date in the time zone specified.
*
* For non-unique points in time, the daylight saving time takes precedence over the standard
* time shortly after the switch back (e.g. clock gets set back 1 hour, duplicate hour).
*
* @example
* var oDate = new Date("2021-10-13T13:22:33Z");
* TimezoneUtils.calculateOffset(oDate, "America/New_York");
* // => +14400 seconds (4 * 60 * 60 seconds)
*
* TimezoneUtils.calculateOffset(oDate, "Europe/Berlin");
* // => -7200 seconds (-2 * 60 * 60 seconds)
*
* // daylight saving time (2018 Sun, 25 Mar, 02:00 CET → CEST +1 hour (DST start) UTC+2h)
* // the given date is taken as it is in the time zone
* TimezoneUtils.calculateOffset(new Date("2018-03-25T00:00:00Z"), "Europe/Berlin");
* // => -3600 seconds (-1 * 60 * 60 seconds), interpreted as: 2018-03-25 00:00:00 (CET)
*
* TimezoneUtils.calculateOffset(new Date("2018-03-25T03:00:00Z"), "Europe/Berlin");
* // => -7200 seconds (-2 * 60 * 60 seconds)
*
* var oHistoricalDate = new Date("1800-10-13T13:22:33Z");
* TimezoneUtils.calculateOffset(oHistoricalDate, "Europe/Berlin");
* // => -3208 seconds (-3208 seconds)
*
* @param {Date} oDate The date in the time zone used to calculate the offset to UTC.
* @param {string} sTimezoneSource The source IANA timezone ID, e.g <code>"Europe/Berlin"</code>
* @returns {number} The difference to UTC between the date in the time zone.
* @private
* @ui5-restricted sap.ui.core.format.DateFormat, sap.viz, sap/ui/core/date/UI5Date,
* sap/ui/core/format/TimezoneUtil
*/
TimezoneUtils.calculateOffset = function(oDate, sTimezoneSource) {
const oDateInTimezone = TimezoneUtils.convertToTimezone(oDate, sTimezoneSource);
const iGivenTimestamp = oDate.getTime();
const iInitialOffset = iGivenTimestamp - oDateInTimezone.getTime();
// no need to use UI5Date.getInstance as only the UTC timestamp is used
const oFirstGuess = new Date(iGivenTimestamp + iInitialOffset);
const oFirstGuessInTimezone = TimezoneUtils.convertToTimezone(oFirstGuess, sTimezoneSource);
const iFirstGuessInTimezoneTimestamp = oFirstGuessInTimezone.getTime();
const iSecondOffset = oFirstGuess.getTime() - iFirstGuessInTimezoneTimestamp;
let iTimezoneOffset = iSecondOffset;
if (iInitialOffset !== iSecondOffset) {
const oSecondGuess = new Date(iGivenTimestamp + iSecondOffset);
const oSecondGuessInTimezone = TimezoneUtils.convertToTimezone(oSecondGuess, sTimezoneSource);
const iSecondGuessInTimezoneTimestamp = oSecondGuessInTimezone.getTime();
// if time is different, the given date/time does not exist in the target time zone (switch to Daylight
// Saving Time) -> take the offset for the greater date
if (iSecondGuessInTimezoneTimestamp !== iGivenTimestamp
&& iFirstGuessInTimezoneTimestamp > iSecondGuessInTimezoneTimestamp) {
iTimezoneOffset = iInitialOffset;
}
}
return iTimezoneOffset / 1000;
};
/**
* Map outdated IANA timezone IDs used in CLDR to correct and IANA IDs as maintained in ABAP systems.
*
* @private
*/
TimezoneUtils.mTimezoneAliases2ABAPTimezones = {
"Africa/Asmera": "Africa/Asmara",
"Africa/Timbuktu": "Africa/Bamako",
"America/Argentina/ComodRivadavia": "America/Argentina/Catamarca",
"America/Atka": "America/Adak",
"America/Buenos_Aires": "America/Argentina/Buenos_Aires",
"America/Catamarca": "America/Argentina/Catamarca",
"America/Coral_Harbour": "America/Atikokan",
"America/Cordoba": "America/Argentina/Cordoba",
"America/Ensenada": "America/Tijuana",
"America/Fort_Wayne": "America/Indiana/Indianapolis",
"America/Godthab": "America/Nuuk",
"America/Indianapolis": "America/Indiana/Indianapolis",
"America/Jujuy": "America/Argentina/Jujuy",
"America/Knox_IN": "America/Indiana/Knox",
"America/Louisville": "America/Kentucky/Louisville",
"America/Mendoza": "America/Argentina/Mendoza",
"America/Montreal": "America/Toronto",
"America/Nipigon": "America/Toronto",
"America/Pangnirtung": "America/Iqaluit",
"America/Porto_Acre": "America/Rio_Branco",
"America/Rainy_River": "America/Winnipeg",
"America/Rosario": "America/Argentina/Cordoba",
"America/Santa_Isabel": "America/Tijuana",
"America/Shiprock": "America/Denver",
"America/Thunder_Bay": "America/Toronto",
"America/Virgin": "America/St_Thomas",
"America/Yellowknife": "America/Edmonton",
"Antarctica/South_Pole": "Antarctica/McMurdo",
"Asia/Ashkhabad": "Asia/Ashgabat",
"Asia/Calcutta": "Asia/Kolkata",
"Asia/Choibalsan": "Asia/Ulaanbaatar",
"Asia/Chongqing": "Asia/Shanghai",
"Asia/Chungking": "Asia/Shanghai",
"Asia/Dacca": "Asia/Dhaka",
"Asia/Harbin": "Asia/Shanghai",
"Asia/Istanbul": "Europe/Istanbul",
"Asia/Kashgar": "Asia/Urumqi",
"Asia/Katmandu": "Asia/Kathmandu",
"Asia/Macao": "Asia/Macau",
"Asia/Rangoon": "Asia/Yangon",
"Asia/Saigon": "Asia/Ho_Chi_Minh",
"Asia/Tel_Aviv": "Asia/Jerusalem",
"Asia/Thimbu": "Asia/Thimphu",
"Asia/Ujung_Pandang": "Asia/Makassar",
"Asia/Ulan_Bator": "Asia/Ulaanbaatar",
"Atlantic/Faeroe": "Atlantic/Faroe",
"Atlantic/Jan_Mayen": "Arctic/Longyearbyen",
"Australia/ACT": "Australia/Sydney",
"Australia/Canberra": "Australia/Sydney",
"Australia/Currie": "Australia/Hobart",
"Australia/LHI": "Australia/Lord_Howe",
"Australia/NSW": "Australia/Sydney",
"Australia/North": "Australia/Darwin",
"Australia/Queensland": "Australia/Brisbane",
"Australia/South": "Australia/Adelaide",
"Australia/Tasmania": "Australia/Hobart",
"Australia/Victoria": "Australia/Melbourne",
"Australia/West": "Australia/Perth",
"Australia/Yancowinna": "Australia/Broken_Hill",
"Brazil/Acre": "America/Rio_Branco",
"Brazil/DeNoronha": "America/Noronha",
"Brazil/East": "America/Sao_Paulo",
"Brazil/West": "America/Manaus",
"CET": "Europe/Brussels",
"CST6CDT": "America/Chicago",
"Canada/Atlantic": "America/Halifax",
"Canada/Central": "America/Winnipeg",
"Canada/East-Saskatchewan": "America/Regina",
"Canada/Eastern": "America/Toronto",
"Canada/Mountain": "America/Edmonton",
"Canada/Newfoundland": "America/St_Johns",
"Canada/Pacific": "America/Vancouver",
"Canada/Saskatchewan": "America/Regina",
"Canada/Yukon": "America/Whitehorse",
"Chile/Continental": "America/Santiago",
"Chile/EasterIsland": "Pacific/Easter",
"Cuba": "America/Havana",
"EET": "Europe/Athens",
"EST": "America/Panama",
"EST5EDT": "America/New_York",
"Egypt": "Africa/Cairo",
"Eire": "Europe/Dublin",
"Etc/GMT+0": "Etc/GMT",
"Etc/GMT-0": "Etc/GMT",
"Etc/GMT0": "Etc/GMT",
"Etc/Greenwich": "Etc/GMT",
"Etc/UCT": "Etc/UTC",
"Etc/Zulu": "Etc/UTC",
"Europe/Belfast": "Europe/London",
"Europe/Kyiv": "Europe/Kiev",
"Europe/Nicosia": "Asia/Nicosia",
"Europe/Tiraspol": "Europe/Chisinau",
"Europe/Uzhgorod": "Europe/Kiev",
"Europe/Zaporozhye": "Europe/Kiev",
"GB": "Europe/London",
"GB-Eire": "Europe/London",
"GMT": "Etc/GMT",
"GMT+0": "Etc/GMT",
"GMT-0": "Etc/GMT",
"GMT0": "Etc/GMT",
"Greenwich": "Etc/GMT",
"HST": "Pacific/Honolulu",
"Hongkong": "Asia/Hong_Kong",
"Iceland": "Atlantic/Reykjavik",
"Iran": "Asia/Tehran",
"Israel": "Asia/Jerusalem",
"Jamaica": "America/Jamaica",
"Japan": "Asia/Tokyo",
"Kwajalein": "Pacific/Kwajalein",
"Libya": "Africa/Tripoli",
"MET": "Europe/Brussels",
"MST": "America/Phoenix",
"MST7MDT": "America/Denver",
"Mexico/BajaNorte": "America/Tijuana",
"Mexico/BajaSur": "America/Mazatlan",
"Mexico/General": "America/Mexico_City",
"NZ": "Pacific/Auckland",
"NZ-CHAT": "Pacific/Chatham",
"Navajo": "America/Denver",
"PRC": "Asia/Shanghai",
"PST8PDT": "America/Los_Angeles",
"Pacific/Enderbury": "Pacific/Kanton",
"Pacific/Johnston": "Pacific/Honolulu",
"Pacific/Ponape": "Pacific/Pohnpei",
"Pacific/Samoa": "Pacific/Pago_Pago",
"Pacific/Truk": "Pacific/Chuuk",
"Pacific/Yap": "Pacific/Chuuk",
"Poland": "Europe/Warsaw",
"Portugal": "Europe/Lisbon",
"ROC": "Asia/Taipei",
"ROK": "Asia/Seoul",
"Singapore": "Asia/Singapore",
"Turkey": "Europe/Istanbul",
"UCT": "Etc/UTC",
"US/Alaska": "America/Anchorage",
"US/Aleutian": "America/Adak",
"US/Arizona": "America/Phoenix",
"US/Central": "America/Chicago",
"US/East-Indiana": "America/Indiana/Indianapolis",
"US/Eastern": "America/New_York",
"US/Hawaii": "Pacific/Honolulu",
"US/Indiana-Starke": "America/Indiana/Knox",
"US/Michigan": "America/Detroit",
"US/Mountain": "America/Denver",
"US/Pacific": "America/Los_Angeles",
"US/Pacific-New": "America/Los_Angeles",
"US/Samoa": "Pacific/Pago_Pago",
"UTC": "Etc/UTC",
"Universal": "Etc/UTC",
"W-SU": "Europe/Moscow",
"WET": "Europe/Lisbon",
"Zulu": "Etc/UTC"
};
/**
* Retrieves the browser's local IANA timezone ID; if the browser's timezone ID is not the up-to-date IANA
* timezone ID, the corresponding IANA timezone ID is returned.
*
* @returns {string} The local IANA timezone ID of the browser as up-to-date IANA timezone ID,
* e.g. <code>"Europe/Berlin"</code> or <code>"Asia/Kolkata"</code>
*
* @private
* @ui5-restricted sap.gantt, sap.gantt, sap.viz, lib/cldr-openui5/lib/Generator,
* sap/base/i18n/Localization, sap/ui/core/date/UI5Date, sap/ui/core/format/TimezoneUtil
*/
TimezoneUtils.getLocalTimezone = function() {
if (sLocalTimezone === "") { // timezone may be undefined, only value "" means empty cache
sLocalTimezone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
sLocalTimezone = TimezoneUtils.getABAPTimezone(sLocalTimezone);
}
return sLocalTimezone;
};
/**
* Returns the ABAP time zone ID for the given IANA time zone ID.
*
* @param {string} sTimezone The IANA time zone ID
*
* @returns {string} The ABAP time zone ID
* @private
*/
TimezoneUtils.getABAPTimezone = function (sTimezone) {
return TimezoneUtils.mTimezoneAliases2ABAPTimezones[sTimezone] || sTimezone;
};
/**
* Clears the cache for the browser's local IANA timezone ID.
*
* @private
*/
TimezoneUtils._clearLocalTimezoneCache = function () {
sLocalTimezone = "";
};
return TimezoneUtils;
});