UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

285 lines (260 loc) 9.63 kB
/*! * 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; });