@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
285 lines (260 loc) • 9.63 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 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.111.5
* @namespace
* @alias sap.ui.core.format.TimezoneUtil
* @private
* @ui5-restricted sap.ui.core.Configuration, sap.ui.core.format.DateFormat
*/
var TimezoneUtil = {};
/**
* 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.core.Configuration, sap.ui.core.format.DateFormat
*/
TimezoneUtil.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)
* TimezoneUtil.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.unified, sap.m
*/
TimezoneUtil.convertToTimezone = function(oDate, sTargetTimezone) {
var oFormatParts = this._getParts(oDate, sTargetTimezone);
return TimezoneUtil._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
*/
TimezoneUtil._getParts = function(oDate, sTargetTimezone) {
var oIntlDate = oIntlDateTimeFormatCache.get(sTargetTimezone);
// clone the date object before passing it to the Intl API, to ensure that no
// UniversalDate gets passed to it
var oParts = oIntlDate.formatToParts(new Date(oDate.getTime()));
var oDateParts = Object.create(null);
for (var sKey in oParts) {
var 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
*/
TimezoneUtil._getDateFromParts = function(oParts) {
var oDate = new Date(0);
var 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");
* TimezoneUtil.calculateOffset(oDate, "America/New_York");
* // => +14400 seconds (4 * 60 * 60 seconds)
*
* TimezoneUtil.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
* TimezoneUtil.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)
*
* TimezoneUtil.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");
* TimezoneUtil.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
*/
TimezoneUtil.calculateOffset = function(oDate, sTimezoneSource) {
var oFirstGuess = this.convertToTimezone(oDate, sTimezoneSource);
var iInitialOffset = oDate.getTime() - oFirstGuess.getTime();
// to get the correct summer/wintertime (daylight saving time) handling use the source date (apply the diff)
var oDateSource = new Date(oDate.getTime() + iInitialOffset);
var oDateTarget = this.convertToTimezone(oDateSource, sTimezoneSource);
return (oDateSource.getTime() - oDateTarget.getTime()) / 1000;
};
/**
* Retrieves the browser's local IANA timezone ID.
*
* @returns {string} The local IANA timezone ID of the browser, e.g <code>"Europe/Berlin"</code>
* @private
* @ui5-restricted sap.ui.core.Configuration,sap.m.DateTimeField
*/
TimezoneUtil.getLocalTimezone = function() {
if (sLocalTimezone) {
return sLocalTimezone;
}
sLocalTimezone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
return sLocalTimezone;
};
return TimezoneUtil;
});