@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,265 lines (1,169 loc) • 108 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
//Provides the locale object sap.ui.core.LocaleData
sap.ui.define([
"./Locale",
"sap/base/assert",
"sap/base/i18n/Formatting",
"sap/base/i18n/LanguageTag",
"sap/base/i18n/Localization",
"sap/base/i18n/date/CalendarType",
"sap/base/i18n/date/CalendarWeekNumbering",
"sap/base/util/extend",
"sap/base/util/LoaderExtensions",
"sap/ui/base/Object",
"sap/ui/base/SyncPromise"
], function(Locale, assert, Formatting, LanguageTag, Localization, CalendarType, CalendarWeekNumbering,
extend, LoaderExtensions, BaseObject, SyncPromise) {
"use strict";
var rCIgnoreCase = /c/i,
rEIgnoreCase = /e/i,
rNumberInScientificNotation = /^([+-]?)((\d+)(?:\.(\d+))?)[eE]([+-]?\d+)$/,
rTrailingZeroes = /0+$/;
const rFallbackPatternTextParts = /(.*)?\{[0|1]}(.*)?\{[0|1]}(.*)?/;
const rOnlyZeros = /^0+$/;
const aSupportedWidths = ["narrow", "abbreviated", "wide"];
/**
* With the upgrade of the CLDR to version 41 some unit keys have changed.
* For compatibility reasons this map is used for formatting units.
* It maps a legacy unit key to its renamed key.
*
* @deprecated As of version 1.122.0, this map is no longer maintained and stays for compatibility reasons
* only. Reason for the depreciation: The assumption of homogeneous unit keys in the CLDR data has been proven
* wrong. Additionally, it is unclear if, those CLDR unit keys are actually used. Implementing a complex logic
* to maintain potentially unused entries did not seem reasonable. Therefore, it was decided to deprecate this
* feature.
* This map was last updated with CLDR V43, in 1.119.0.
* @private
*/
const mLegacyUnit2CurrentUnit = {
"acceleration-meter-per-second-squared": "acceleration-meter-per-square-second",
"concentr-milligram-per-deciliter": "concentr-milligram-ofglucose-per-deciliter",
"concentr-part-per-million": "concentr-permillion",
"consumption-liter-per-100kilometers": "consumption-liter-per-100-kilometer",
"mass-metric-ton": "mass-tonne",
"pressure-millimeter-of-mercury": "pressure-millimeter-ofhg",
"pressure-pound-per-square-inch": "pressure-pound-force-per-square-inch",
"pressure-inch-hg": "pressure-inch-ofhg",
"torque-pound-foot": "torque-pound-force-foot"
};
/**
* The locale data cache. Maps a locale ID, formatted as either the language_region (e.g. "ar_SA"),
* language_script (e.g. "sr_Latn") or just the language code (e.g. "de") to its set of loaded
* CLDR data. In case of asynchronous loading, the locale ID is mapped to a <code>Promise</code> which resolves
* with the loaded CLDR data. As soon as the data is loaded the <code>Promise</code> is replaced by it.
*
* @type {Object<string, Object<string, any>|Promise<Object<string, any>>>}
* @private
*/
let mLocaleIdToData = {};
/**
* DO NOT call the constructor for <code>LocaleData</code>; use <code>LocaleData.getInstance</code> instead.
*
* @param {sap.ui.core.Locale} oLocale The locale
* @param {boolean} bAsync Whether to load the data asynchronously
*
* @alias sap.ui.core.LocaleData
* @author SAP SE
* @extends sap.ui.base.Object
* @class Provides access to locale-specific data, such as date formats, number formats, and currencies. For more
* information on terminology, such as field names used in the methods of this class, see
* {@link https://cldr.unicode.org/ Unicode CLDR}.
* @hideconstructor
* @public
* @version 1.147.0
*/
var LocaleData = BaseObject.extend("sap.ui.core.LocaleData", /** @lends sap.ui.core.LocaleData.prototype */ {
constructor: function(oLocale, bAsync) {
BaseObject.apply(this);
this.oLocale = Locale._getCoreLocale(oLocale);
this.loaded = loadData(this.oLocale, bAsync).then((oResult) => {
this.mData = oResult.mData;
this.sCLDRLocaleId = oResult.sCLDRLocaleId;
return this;
});
this.loaded.finally(() => {
delete this.loaded;
});
},
/**
* @private
* @ui5-restricted UI5 Web Components
*/
_get: function() {
return this._getDeep(this.mData, arguments);
},
/**
* Retrieves merged object if overlay data is available
* @private
* @return {object} merged object
*/
_getMerged: function() {
return this._get.apply(this, arguments);
},
/**
* Get month names in the given width. Result may contain alternative month names.
*
* @param {"abbreviated"|"narrow"|"wide"} sWidth
* The required width for the month names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType]
* The type of calendar; defaults to the calendar type either set in configuration or calculated from the
* locale
* @returns {array}
* The array of month names; if no alternative exists the entry for the month is its name as a string; if
* there are alternative month names the entry for the month is an array of strings with the alternative names
* @private
*/
_getMonthsWithAlternatives: function(sWidth, sCalendarType) {
return this._get(getCLDRCalendarName(sCalendarType), "months", "format", sWidth);
},
/**
* Get standalone month names in the given width. Result may contain alternative month
* names.
*
* @param {"abbreviated"|"narrow"|"wide"} sWidth
* The required width for the month names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType]
* The type of calendar; defaults to the calendar type either set in configuration or calculated from the
* locale
* @returns {array}
* The array of month names; if no alternative exists the entry for the month is its name as a string; if
* there are alternative month names the entry for the month is an array of strings with the alternative names
* @private
*/
_getMonthsStandAloneWithAlternatives: function(sWidth, sCalendarType) {
return this._get(getCLDRCalendarName(sCalendarType), "months", "stand-alone", sWidth);
},
_getDeep: function(oObject, aPropertyNames) {
var oResult = oObject;
for (var i = 0; i < aPropertyNames.length; i++) {
oResult = oResult[aPropertyNames[i]];
if (oResult === undefined) {
break;
}
}
return oResult;
},
/**
* Gets the text orientation.
*
* @returns {"left-to-right"|"right-to-left"} text orientation
* @public
*/
getOrientation: function() {
return this._get("orientation");
},
/**
* Get a display name for the language of the Locale of this LocaleData, using
* the CLDR display names for languages.
*
* The lookup logic works as follows:
* 1. language code and region is checked (e.g. "en-GB")
* 2. If not found: language code and script is checked (e.g. "zh-Hant")
* 3. If not found language code is checked (e.g. "en")
* 4. If it is then still not found <code>undefined</code> is returned.
*
* @returns {string} language name, e.g. "English", "British English", "American English"
* or <code>undefined</code> if language cannot be found
* @private
* @ui5-restricted sap.ushell
*/
getCurrentLanguageName: function () {
return this.getLanguageName(this.oLocale.toString());
},
/**
* Gets the locale-specific language name for the given language tag.
*
* The languages returned by {@link #getLanguages} from the CLDR raw data do not contain the
* language names if they can be derived from the language and the script or the territory.
* If the map of languages contains no entry for the given language tag, derive the language
* name from the used script or region.
*
* @param {string} sLanguageTag
* The language tag, for example "en", "en-US", "en_US", "zh-Hant", or "zh_Hant"
* @returns {string|undefined}
* The language name, or <code>undefined</code> if the name cannot be determined
* @throws {TypeError} When the given language tag isn't valid
*
* @public
*/
getLanguageName: function (sLanguageTag) {
const oLanguageTag = new LanguageTag(sLanguageTag);
let sLanguage = Localization.getModernLanguage(oLanguageTag.language);
let sScript = oLanguageTag.script;
// special case for "sr_Latn" language: "sh" should then be used
if (sLanguage === "sr" && sScript === "Latn") {
sLanguage = "sh";
sScript = null;
}
const sRegion = oLanguageTag.region;
const oLanguages = this._get("languages");
const sLanguageText = oLanguages[sLanguage];
if (!sScript && !sRegion || !sLanguageText) {
return sLanguageText;
}
const sResult = oLanguages[sLanguage + "_" + sRegion] || oLanguages[sLanguage + "_" + sScript];
if (sResult) {
return sResult;
}
if (sScript) {
const sScriptText = this._get("scripts")[sScript];
if (sScriptText) {
return sLanguageText + " (" + sScriptText + ")";
}
}
if (sRegion) {
const sRegionText = this._get("territories")[sRegion];
if (sRegionText) {
return sLanguageText + " (" + sRegionText + ")";
}
}
return sLanguageText;
},
/**
* Gets locale-specific language names, as available in the CLDR raw data.
*
* To avoid redundancies, with CLDR version 43 only language names are contained which cannot be derived from
* the language and the script or the territory. If a language tag is not contained in the map, use
* {@link #getLanguageName} to get the derived locale-specific language name for that language tag.
*
* @returns {Object<string, string>} Maps a language tag to the locale-specific language name
*
* @public
*/
getLanguages: function() {
const oLanguages = this._get("languages");
/** @deprecated As of version 1.120.0 */
[
"ar_001", "de_AT", "de_CH", "en_AU", "en_CA", "en_GB", "en_US", "es_419", "es_ES", "es_MX", "fa_AF",
"fr_CA", "fr_CH", "nds_NL", "nl_BE", "pt_BR", "pt_PT", "ro_MD", "sw_CD", "zh_Hans", "zh_Hant"
].forEach((sLanguageTag) => {
// for compatibility reasons, ensure that for these language tags the corresponding language names are
// available
if (!oLanguages[sLanguageTag]) {
oLanguages[sLanguageTag] = this.getLanguageName(sLanguageTag);
}
});
return oLanguages;
},
/**
* Gets locale-specific script names, as available in the CLDR raw data.
*
* To avoid redundancies, with CLDR version 43 only scripts are contained for which the language-specific name
* is different from the script key. If a script key is not contained in the map, use the script key as script
* name.
*
* @returns {Object<string, string>} Maps a script key to the locale-specific script name
*
* @public
*/
getScripts: function() {
return this._get("scripts");
},
/**
* Gets locale-specific territory names, as available in the CLDR raw data.
*
* To avoid redundancies, with CLDR version 43 only territories are contained for which the language-specific
* name is different from the territory key.
*
* @returns {Object<string, string>} Maps a territory key to the locale-specific territory name
*
* @public
*/
getTerritories: function() {
return this._get("territories");
},
/**
* Get month names in the given width.
*
* @param {"abbreviated"|"narrow"|"wide"} sWidth
* The required width for the month names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType]
* The type of calendar; defaults to the calendar type either set in configuration or calculated from the
* locale
* @returns {string[]}
* The array of month names
* @public
*/
getMonths: function(sWidth, sCalendarType) {
assert(aSupportedWidths.includes(sWidth), "sWidth must be narrow, abbreviated or wide");
return this._get(getCLDRCalendarName(sCalendarType), "months", "format", sWidth).map((vMonthName) => {
return Array.isArray(vMonthName) ? vMonthName[0] : vMonthName;
});
},
/**
* Get standalone month names in the given width.
*
* @param {"abbreviated"|"narrow"|"wide"} sWidth
* The required width for the month names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType]
* The type of calendar; defaults to the calendar type either set in configuration or calculated from the
* locale
* @returns {string[]}
* The array of standalone month names
* @public
*/
getMonthsStandAlone: function(sWidth, sCalendarType) {
assert(aSupportedWidths.includes(sWidth), "sWidth must be narrow, abbreviated or wide");
return this._get(getCLDRCalendarName(sCalendarType), "months", "stand-alone", sWidth).map((vMonthName) => {
return Array.isArray(vMonthName) ? vMonthName[0] : vMonthName;
});
},
/**
* Get day names in the given width.
*
* @param {"abbreviated"|"narrow"|"short"|"wide"} sWidth the required width for the day names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string[]} array of day names (starting with Sunday)
* @public
*/
getDays: function(sWidth, sCalendarType) {
assert(sWidth == "narrow" || sWidth == "abbreviated" || sWidth == "wide" || sWidth == "short", "sWidth must be narrow, abbreviate, wide or short");
return this._get(getCLDRCalendarName(sCalendarType), "days", "format", sWidth);
},
/**
* Get standalone day names in the given width.
*
* @param {"abbreviated"|"narrow"|"short"|"wide"} sWidth the required width for the day names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string[]} array of day names (starting with Sunday)
* @public
*/
getDaysStandAlone: function(sWidth, sCalendarType) {
assert(sWidth == "narrow" || sWidth == "abbreviated" || sWidth == "wide" || sWidth == "short", "sWidth must be narrow, abbreviated, wide or short");
return this._get(getCLDRCalendarName(sCalendarType), "days", "stand-alone", sWidth);
},
/**
* Get quarter names in the given width.
*
* @param {"abbreviated"|"narrow"|"wide"} sWidth the required width for the quarter names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string[]} array of quarters
* @public
*/
getQuarters: function(sWidth, sCalendarType) {
assert(sWidth == "narrow" || sWidth == "abbreviated" || sWidth == "wide", "sWidth must be narrow, abbreviated or wide");
return this._get(getCLDRCalendarName(sCalendarType), "quarters", "format", sWidth);
},
/**
* Get standalone quarter names in the given width.
*
* @param {"abbreviated"|"narrow"|"wide"} sWidth the required width for the quarter names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string[]} array of quarters
* @public
*/
getQuartersStandAlone: function(sWidth, sCalendarType) {
assert(sWidth == "narrow" || sWidth == "abbreviated" || sWidth == "wide", "sWidth must be narrow, abbreviated or wide");
return this._get(getCLDRCalendarName(sCalendarType), "quarters", "stand-alone", sWidth);
},
/**
* Get day periods in the given width.
*
* @param {"abbreviated"|"narrow"|"wide"} sWidth the required width for the day period names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string[]} array of day periods (AM, PM)
* @public
*/
getDayPeriods: function(sWidth, sCalendarType) {
assert(sWidth == "narrow" || sWidth == "abbreviated" || sWidth == "wide", "sWidth must be narrow, abbreviated or wide");
return this._get(getCLDRCalendarName(sCalendarType), "dayPeriods", "format", sWidth);
},
/**
* Get standalone day periods in the given width.
*
* @param {"abbreviated"|"narrow"|"wide"} sWidth the required width for the day period names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string[]} array of day periods (AM, PM)
* @public
*/
getDayPeriodsStandAlone: function(sWidth, sCalendarType) {
assert(sWidth == "narrow" || sWidth == "abbreviated" || sWidth == "wide", "sWidth must be narrow, abbreviated or wide");
return this._get(getCLDRCalendarName(sCalendarType), "dayPeriods", "stand-alone", sWidth);
},
/**
* Get date pattern in the given style.
*
* @param {"full"|"long"|"medium"|"short"} sStyle the required style for the date pattern
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string} the selected date pattern
* @public
*/
getDatePattern: function(sStyle, sCalendarType) {
assert(sStyle == "short" || sStyle == "medium" || sStyle == "long" || sStyle == "full", "sStyle must be short, medium, long or full");
return this._get(getCLDRCalendarName(sCalendarType), "dateFormats", sStyle);
},
/**
* Get flexible day periods in style format "abbreviated", "narrow" or "wide".
*
* @param {string} sWidth
* The required width for the flexible day period names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType]
* The type of calendar. If it's not set, it falls back to the calendar type either set in
* configuration or calculated from locale.
* @returns {object|undefined}
* Object of flexible day periods or 'undefined' if none can be found
*
* @example <caption>Output</caption>
* {
* "midnight": "midnight",
* "noon": "noon",
* "morning1": "in the morning",
* "afternoon1": "in the afternoon",
* "evening1": "in the evening",
* "night1": "at night"
* }
*
* @private
*/
getFlexibleDayPeriods : function (sWidth, sCalendarType) {
return this._get(getCLDRCalendarName(sCalendarType), "flexibleDayPeriods", "format",
sWidth);
},
/**
* Get flexible day periods in style format "abbreviated", "narrow" or "wide" for case
* "stand-alone".
*
* @param {string} sWidth
* The required width for the flexible day period names
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType]
* The type of calendar. If it's not set, it falls back to the calendar type either set in
* configuration or calculated from locale.
* @returns {object|undefined}
* Object of flexible day periods or 'undefined' if none can be found
*
* @example <caption>Output</caption>
* {
* "midnight": "midnight",
* "noon": "noon",
* "morning1": "in the morning",
* "afternoon1": "in the afternoon",
* "evening1": "in the evening",
* "night1": "at night"
* }
*
* @private
*/
getFlexibleDayPeriodsStandAlone : function (sWidth, sCalendarType) {
return this._get(getCLDRCalendarName(sCalendarType), "flexibleDayPeriods",
"stand-alone", sWidth);
},
/**
* Get flexible day period of time or a point in time
*
* @param {int} iHour Hour
* @param {int} iMinute Minute
* @returns {string} Key of flexible day period of time e.g. <code>afternoon2</code>
*
* @private
*/
getFlexibleDayPeriodOfTime : function (iHour, iMinute) {
var iAbsoluteMinutes, oDayPeriodRules, sPeriodMatch;
iAbsoluteMinutes = (iHour * 60 + iMinute) % 1440;
oDayPeriodRules = this._get("dayPeriodRules");
function parseToAbsoluteMinutes(sValue) {
var aSplit = sValue.split(":"),
sHour = aSplit[0],
sMinute = aSplit[1];
return parseInt(sHour) * 60 + parseInt(sMinute);
}
// unfortunately there are some overlaps:
// e.g. en.json
// "afternoon1": {
// "_before": "18:00",
// "_from": "12:00"
// },
// "noon": {
// "_at": "12:00"
// }
// -> 12:00 can be either "noon" or "afternoon1" because "_from" is inclusive
// therefore first check all exact periods
sPeriodMatch = Object.keys(oDayPeriodRules).find(function (sDayPeriodRule) {
var oDayPeriodRule = oDayPeriodRules[sDayPeriodRule];
return oDayPeriodRule["_at"] &&
parseToAbsoluteMinutes(oDayPeriodRule["_at"]) === iAbsoluteMinutes;
});
if (sPeriodMatch) {
return sPeriodMatch;
}
return Object.keys(oDayPeriodRules).find(function (sDayPeriodRule) {
var iEndValue, aIntervals, iStartValue,
oDayPeriodRule = oDayPeriodRules[sDayPeriodRule];
if (oDayPeriodRule["_at"]) {
return false;
}
iStartValue = parseToAbsoluteMinutes(oDayPeriodRule["_from"]);
iEndValue = parseToAbsoluteMinutes(oDayPeriodRule["_before"]);
// periods which span across days need to be split into individual intervals
// e.g. "22:00 - 03:00" becomes "22:00 - 24:00" and "00:00 - 03:00"
if (iStartValue > iEndValue) {
aIntervals = [
{start : iStartValue, end : 1440}, // 24 * 60
{start : 0, end : iEndValue}
];
} else {
aIntervals = [
{start : iStartValue, end : iEndValue}
];
}
return aIntervals.some(function (oInterval) {
return oInterval.start <= iAbsoluteMinutes && oInterval.end > iAbsoluteMinutes;
});
});
},
/**
* Get time pattern in the given style.
*
* @param {"full"|"long"|"medium"|"short"} sStyle the required style for the time pattern
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string} the selected time pattern
* @public
*/
getTimePattern: function(sStyle, sCalendarType) {
assert(sStyle == "short" || sStyle == "medium" || sStyle == "long" || sStyle == "full", "sStyle must be short, medium, long or full");
return this._get(getCLDRCalendarName(sCalendarType), "timeFormats", sStyle);
},
/**
* Get datetime pattern in the given style.
*
* @param {"full"|"long"|"medium"|"short"} sStyle the required style for the datetime pattern
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string} the selected datetime pattern
* @public
*/
getDateTimePattern: function(sStyle, sCalendarType) {
assert(sStyle == "short" || sStyle == "medium" || sStyle == "long" || sStyle == "full", "sStyle must be short, medium, long or full");
return this._get(getCLDRCalendarName(sCalendarType), "dateTimeFormats", sStyle);
},
/**
* Get combined datetime pattern with given date and time style. The combined datetime pattern is the datetime
* pattern as returned by {@link #getDateTimePattern}, where date and time placeholder are replaced with
* the corresponding patterns for the given styles.
*
* @param {"full"|"long"|"medium"|"short"} sDateStyle the required style for the date part
* @param {"full"|"long"|"medium"|"short"} sTimeStyle the required style for the time part
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string} the combined datetime pattern
* @public
*/
getCombinedDateTimePattern: function(sDateStyle, sTimeStyle, sCalendarType) {
assert(sDateStyle == "short" || sDateStyle == "medium" || sDateStyle == "long" || sDateStyle == "full", "sStyle must be short, medium, long or full");
assert(sTimeStyle == "short" || sTimeStyle == "medium" || sTimeStyle == "long" || sTimeStyle == "full", "sStyle must be short, medium, long or full");
var sDateTimePattern = this.getDateTimePattern(sDateStyle, sCalendarType),
sDatePattern = this.getDatePattern(sDateStyle, sCalendarType),
sTimePattern = this.getTimePattern(sTimeStyle, sCalendarType);
return sDateTimePattern.replace("{0}", sTimePattern).replace("{1}", sDatePattern);
},
/**
* Get combined pattern with datetime and timezone for the given date and time style.
*
* @example
* // locale de
* oLocaleData.getCombinedDateTimeWithTimezonePattern("long", "long");
* // "d. MMMM y 'um' HH:mm:ss z VV"
*
* // locale en_GB
* oLocaleData.getCombinedDateTimeWithTimezonePattern("long", "long");
* // "d MMMM y 'at' HH:mm:ss z VV"
*
* @param {string} sDateStyle The required style for the date part
* @param {string} sTimeStyle The required style for the time part
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] The type of calendar. If it's not set,
* it falls back to the calendar type either set in the configuration or calculated from
* the locale.
* @returns {string} the combined pattern with datetime and timezone
* @private
* @ui5-restricted sap.ui.core.format.DateFormat
* @since 1.101
*/
getCombinedDateTimeWithTimezonePattern: function(sDateStyle, sTimeStyle, sCalendarType) {
return this.applyTimezonePattern(this.getCombinedDateTimePattern(sDateStyle, sTimeStyle, sCalendarType));
},
/**
* Applies the timezone to the pattern
*
* @param {string} sPattern pattern, e.g. <code>y</code>
* @returns {string} applied timezone, e.g. <code>y VV</code>
* @private
* @ui5-restricted sap.ui.core.format.DateFormat
* @since 1.101
*/
applyTimezonePattern: function(sPattern) {
var aPatterns = [sPattern];
var aMissingTokens = [{
group: "Timezone",
length: 2,
field: "zone",
symbol: "V"
}];
this._appendItems(aPatterns, aMissingTokens);
return aPatterns[0];
},
/**
* Retrieves all timezone translations.
*
* E.g. for locale "en"
* <pre>
* {
* "America/New_York": "Americas, New York"
* ...
* }
* </pre>
*
* @return {Object<string, string>} the mapping, with 'key' being the IANA timezone ID, and
* 'value' being the translation.
* @ui5-restricted sap.ui.core.format.DateFormat, sap.ui.export, sap.ushell
* @private
*/
getTimezoneTranslations: function() {
var sLocale = this.oLocale.toString();
var mTranslations = LocaleData._mTimezoneTranslations[sLocale];
if (!mTranslations) {
LocaleData._mTimezoneTranslations[sLocale] = mTranslations =
_resolveTimezoneTranslationStructure(this._get("timezoneNames"));
}
// retrieve a copy such that the original object won't be modified.
return Object.assign({}, mTranslations);
},
/**
* Get custom datetime pattern for a given skeleton format.
*
* The format string does contain pattern symbols (e.g. "yMMMd" or "Hms") and will be converted into the pattern in the used
* locale, which matches the wanted symbols best. The symbols must be in canonical order, that is:
* Era (G), Year (y/Y), Quarter (q/Q), Month (M/L), Week (w/W), Day-Of-Week (E/e/c), Day (d/D),
* Hour (h/H/k/K/), Minute (m), Second (s), Timezone (z/Z/v/V/O/X/x)
*
* See https://unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
*
* @param {string} sSkeleton the wanted skeleton format for the datetime pattern
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string} the best matching datetime pattern
* @since 1.34
* @public
*/
getCustomDateTimePattern: function(sSkeleton, sCalendarType) {
var oAvailableFormats = this._get(getCLDRCalendarName(sCalendarType), "dateTimeFormats", "availableFormats");
return this._getFormatPattern(sSkeleton, oAvailableFormats, sCalendarType);
},
/**
* Returns the interval format with the given Id (see CLDR documentation for valid Ids)
* or the fallback format if no interval format with that Id is known.
*
* The empty Id ("") might be used to retrieve the interval format fallback.
*
* @param {string} sId Id of the interval format, e.g. "d-d"
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string} interval format string with placeholders {0} and {1}
* @public
* @since 1.17.0
*/
getIntervalPattern : function(sId, sCalendarType) {
var oIntervalFormats = this._get(getCLDRCalendarName(sCalendarType), "dateTimeFormats", "intervalFormats"),
aIdParts, sIntervalId, sDifference, oInterval, sPattern;
if (sId) {
aIdParts = sId.split("-");
sIntervalId = aIdParts[0];
sDifference = aIdParts[1];
oInterval = oIntervalFormats[sIntervalId];
if (oInterval) {
sPattern = oInterval[sDifference];
if (sPattern) {
return sPattern;
}
}
}
return oIntervalFormats.intervalFormatFallback;
},
/**
* Get combined interval pattern using a given pattern and the fallback interval pattern.
*
* If a skeleton based pattern is not available or not wanted, this method can be used to create an interval
* pattern based on a given pattern, using the fallback interval pattern.
*
* @param {string} sPattern the single date pattern to use within the interval pattern
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string} the calculated interval pattern
* @since 1.46
* @public
*/
getCombinedIntervalPattern: function (sPattern, sCalendarType) {
const oIntervalFormats = this._get(getCLDRCalendarName(sCalendarType), "dateTimeFormats",
"intervalFormats");
const [/*sAll*/, sTextBefore, sTextBetween, sTextAfter] =
rFallbackPatternTextParts.exec(oIntervalFormats.intervalFormatFallback);
// text part of intervalFormatFallback is not escaped
return LocaleData._escapeIfNeeded(sTextBefore) + sPattern + LocaleData._escapeIfNeeded(sTextBetween)
+ sPattern + LocaleData._escapeIfNeeded(sTextAfter);
},
/**
* @typedef {object} sap.ui.core.LocaleData.DateFieldGroupsDifference
*
* Type which describes the difference in the date field groups of the two dates of an date time interval.
* The keys are the names of the date field symbol groups. If one of them is set, the value should be set to
* <code>true</code>.
*
* @property {boolean} [Era] The era date field symbol group
* @property {boolean} [Year] The year date field symbol group
* @property {boolean} [Quarter] The quarter date field symbol group
* @property {boolean} [Month] The month date field symbol group
* @property {boolean} [Week] The week date field symbol group
* @property {boolean} [Day] The day date field symbol group
* @property {boolean} [DayPeriod] The day period date field symbol group
* @property {boolean} [Hour] The hour date field symbol group
* @property {boolean} [Minute] The minute date field symbol group
* @property {boolean} [Second] The second date field symbol group
*
* @public
*/
/**
* Get interval pattern for a given skeleton format.
*
* The format string does contain pattern symbols (e.g. "yMMMd" or "Hms") and will be converted into the pattern in the used
* locale, which matches the wanted symbols best. The symbols must be in canonical order, that is:
* Era (G), Year (y/Y), Quarter (q/Q), Month (M/L), Week (w/W), Day-Of-Week (E/e/c), Day (d/D),
* Hour (h/H/k/K/), Minute (m), Second (s), Timezone (z/Z/v/V/O/X/x)
*
* See {@link https://unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
* Unicode - Available Formats}
*
* @param {string} sSkeleton the wanted skeleton format for the datetime pattern
* @param {sap.ui.core.LocaleData.DateFieldGroupsDifference|string} vGreatestDiff
* is either a string which represents the symbol matching the greatest difference in the two dates to
* format or an object which contains key-value pairs. The value is always <code>true</code>. The key is one
* of the date field symbol groups whose value are different between the two dates. The key can only be set
* with:
* <code>'Era', 'Year', 'Quarter', 'Month', 'Week', 'Day', 'DayPeriod', 'Hour','Minute', 'Second'</code>.
* For more information, see {@link https://unicode.org/reports/tr35/tr35-dates.html#element-intervalformats
* Unicode - Element intervalFormats}.
* @param {module:sap/base/i18n/date/CalendarType} [sCalendarType] the type of calendar. If it's not set, it falls back to the calendar type either set in configuration or calculated from locale.
* @returns {string|string[]} the best matching interval pattern if interval difference is given otherwise an array with all possible interval patterns which match the given skeleton format
* @since 1.46
* @public
*/
getCustomIntervalPattern : function(sSkeleton, vGreatestDiff, sCalendarType) {
var oAvailableFormats = this._get(getCLDRCalendarName(sCalendarType), "dateTimeFormats", "intervalFormats");
return this._getFormatPattern(sSkeleton, oAvailableFormats, sCalendarType, vGreatestDiff);
},
/* Helper functions for skeleton pattern processing */
_getFormatPattern: function(sSkeleton, oAvailableFormats, sCalendarType, vDiff) {
var vPattern, aPatterns, oIntervalFormats;
if (!vDiff) {
// the call is from getCustomDateTimePattern
vPattern = oAvailableFormats[sSkeleton];
} else if (typeof vDiff === "string") {
// vDiff is given as a symbol
if (vDiff == "j" || vDiff == "J") {
vDiff = this.getPreferredHourSymbol();
}
oIntervalFormats = oAvailableFormats[sSkeleton];
vPattern = oIntervalFormats && oIntervalFormats[vDiff];
}
if (vPattern) {
if (typeof vPattern === "object") {
aPatterns = Object.keys(vPattern).map(function(sKey) {
return vPattern[sKey];
});
} else {
return vPattern;
}
}
if (!aPatterns) {
aPatterns = this._createFormatPattern(sSkeleton, oAvailableFormats, sCalendarType, vDiff);
}
if (aPatterns && aPatterns.length === 1) {
return aPatterns[0];
}
return aPatterns;
},
_createFormatPattern: function(sSkeleton, oAvailableFormats, sCalendarType, vDiff) {
var aTokens = this._parseSkeletonFormat(sSkeleton), aPatterns,
oBestMatch = this._findBestMatch(aTokens, sSkeleton, oAvailableFormats),
oToken, oAvailableDateTimeFormats, oSymbol, oGroup,
sPattern, sSinglePattern, sDiffSymbol, sDiffGroup,
rMixedSkeleton = /^([GyYqQMLwWEecdD]+)([hHkKjJmszZvVOXx]+)$/,
bSingleDate,
i;
if (vDiff) {
if (typeof vDiff === "string") {
sDiffGroup = mCLDRSymbols[vDiff] ? mCLDRSymbols[vDiff].group : "";
if (sDiffGroup) {
// if the index of interval diff is greater than the index of the last field
// in the sSkeleton, which means the diff unit is smaller than all units in
// the skeleton, return a single date pattern which is generated using the
// given skeleton
bSingleDate = mCLDRSymbolGroups[sDiffGroup].index > aTokens[aTokens.length - 1].index;
}
sDiffSymbol = vDiff;
} else {
bSingleDate = true;
// Special handling of "y" (Year) in case patterns contains also "G" (Era)
if (aTokens[0].symbol === "y" && oBestMatch && oBestMatch.pattern.G) {
oSymbol = mCLDRSymbols["G"];
oGroup = mCLDRSymbolGroups[oSymbol.group];
aTokens.splice(0, 0, {
symbol: "G",
group: oSymbol.group,
match: oSymbol.match,
index: oGroup.index,
field: oGroup.field,
length: 1
});
}
// Check if at least one token's group appears in the interval diff
// If not, a single date pattern is returned
for (i = aTokens.length - 1; i >= 0; i--){
oToken = aTokens[i];
if (vDiff[oToken.group]) {
bSingleDate = false;
break;
}
}
// select the greatest diff symbol
for (i = 0; i < aTokens.length; i++){
oToken = aTokens[i];
if (vDiff[oToken.group]) {
sDiffSymbol = oToken.symbol;
break;
}
}
// Special handling of "a" (Dayperiod)
// Find out whether dayperiod is different between the dates
// If yes, set the diff symbol with 'a' Dayperiod symbol
if ((sDiffSymbol == "h" || sDiffSymbol == "K") && vDiff.DayPeriod) {
sDiffSymbol = "a";
}
}
if (bSingleDate) {
return [this.getCustomDateTimePattern(sSkeleton, sCalendarType)];
}
// Only use best match, if there are no missing tokens, as there is no possibility
// to append items on interval formats
if (oBestMatch && oBestMatch.missingTokens.length === 0) {
sPattern = oBestMatch.pattern[sDiffSymbol];
// if there is no exact match, we need to do further processing
if (sPattern && oBestMatch.distance > 0) {
sPattern = this._expandFields(sPattern, oBestMatch.patternTokens, aTokens);
}
}
// If no pattern could be found, get the best availableFormat for the skeleton
// and use the fallbackIntervalFormat to create the pattern
if (!sPattern) {
oAvailableDateTimeFormats = this._get(getCLDRCalendarName(sCalendarType), "dateTimeFormats", "availableFormats");
// If it is a mixed skeleton and the greatest interval on time, create a mixed pattern
if (rMixedSkeleton.test(sSkeleton) && "ahHkKjJms".indexOf(sDiffSymbol) >= 0) {
sPattern = this._getMixedFormatPattern(sSkeleton, oAvailableDateTimeFormats, sCalendarType, vDiff);
} else {
sSinglePattern = this._getFormatPattern(sSkeleton, oAvailableDateTimeFormats, sCalendarType);
sPattern = this.getCombinedIntervalPattern(sSinglePattern, sCalendarType);
}
}
aPatterns = [sPattern];
} else if (!oBestMatch) {
sPattern = sSkeleton;
aPatterns = [sPattern];
} else {
if (typeof oBestMatch.pattern === "string") {
aPatterns = [oBestMatch.pattern];
} else if (typeof oBestMatch.pattern === "object") {
aPatterns = [];
for (var sKey in oBestMatch.pattern) {
sPattern = oBestMatch.pattern[sKey];
aPatterns.push(sPattern);
}
}
// if there is no exact match, we need to do further processing
if (oBestMatch.distance > 0) {
if (oBestMatch.missingTokens.length > 0) {
// if tokens are missing create a pattern containing all, otherwise just adjust pattern
if (rMixedSkeleton.test(sSkeleton)) {
aPatterns = [this._getMixedFormatPattern(sSkeleton, oAvailableFormats, sCalendarType)];
} else {
aPatterns = this._expandFields(aPatterns, oBestMatch.patternTokens, aTokens);
aPatterns = this._appendItems(aPatterns, oBestMatch.missingTokens, sCalendarType);
}
} else {
aPatterns = this._expandFields(aPatterns, oBestMatch.patternTokens, aTokens);
}
}
}
// If special input token "J" was used, remove dayperiod from pattern
if (sSkeleton.indexOf("J") >= 0) {
aPatterns.forEach(function(sPattern, iIndex) {
aPatterns[iIndex] = sPattern.replace(/ ?[abB](?=([^']*'[^']*')*[^']*)$/g, "");
});
}
return aPatterns;
},
_parseSkeletonFormat: function(sSkeleton) {
var aTokens = [],
oToken = {index: -1},
sSymbol,
oSymbol,
oGroup;
for (var i = 0; i < sSkeleton.length; i++) {
sSymbol = sSkeleton.charAt(i);
// Handle special input symbols
if (sSymbol == "j" || sSymbol == "J") {
sSymbol = this.getPreferredHourSymbol();
}
// if the symbol is the same as current token, increase the length
if (sSymbol == oToken.symbol) {
oToken.length++;
continue;
}
// get symbol group
oSymbol = mCLDRSymbols[sSymbol];
oGroup = mCLDRSymbolGroups[oSymbol.group];
// if group is other, the symbol is not allowed in skeleton tokens
if (oSymbol.group == "Other" || oGroup.diffOnly) {
throw new Error("Symbol '" + sSymbol + "' is not allowed in skeleton format '" + sSkeleton + "'");
}
// if group index the same or lower, format is invalid
if (oGroup.index <= oToken.index) {
throw new Error("Symbol '" + sSymbol + "' at wrong position or duplicate in skeleton format '" + sSkeleton + "'");
}
// create token and add it the token array
oToken = {
symbol: sSymbol,
group: oSymbol.group,
match: oSymbol.match,
index: oGroup.index,
field: oGroup.field,
length: 1
};
aTokens.push(oToken);
}
return aTokens;
},
_findBestMatch: function(aTokens, sSkeleton, oAvailableFormats) {
var aTestTokens,
aMissingTokens,
oToken,
oTestToken,
iTest,
iDistance,
bMatch,
iFirstDiffPos,
oTokenSymbol,
oTestTokenSymbol,
oBestMatch = {
distance: 10000,
firstDiffPos: -1
};
// Loop through all available tokens, find matches and calculate distance
for (var sTestSkeleton in oAvailableFormats) {
// Skip patterns with symbol "B" (which is introduced from CLDR v32.0.0) which isn't supported in DateFormat yet
if (sTestSkeleton === "intervalFormatFallback" || sTestSkeleton.indexOf("B") > -1) {
continue;
}
aTestTokens = this._parseSkeletonFormat(sTestSkeleton);
iDistance = 0;
aMissingTokens = [];
bMatch = true;
// if test format contains more tokens, it cannot be a best match
if (aTokens.length < aTestTokens.length) {
continue;
}
iTest = 0;
iFirstDiffPos = aTokens.length;
for (var i = 0; i < aTokens.length; i++) {
oToken = aTokens[i];
oTestToken = aTestTokens[iTest];
if (iFirstDiffPos === aTokens.length) {
iFirstDiffPos = i;
}
if (oTestToken) {
oTokenSymbol = mCLDRSymbols[oToken.symbol];
oTestTokenSymbol = mCLDRSymbols[oTestToken.symbol];
// if the symbol matches, just add the length difference to the distance
if (oToken.symbol === oTestToken.symbol) {
if (oToken.length === oTestToken.length) {
// both symbol and length match, check the next token
// clear the first difference position
if (iFirstDiffPos === i) {
iFirstDiffPos = aTokens.length;
}
} else {
if (oToken.length < oTokenSymbol.numericCeiling ? oTestToken.length < oTestTokenSymbol.numericCeiling : oTestToken.length >= oTestTokenSymbol.numericCeiling) {
// if the symbols are in the same category (either numeric or text representation), add the length diff
iDistance += Math.abs(oToken.length - oTestToken.length);
} else {
// otherwise add 5 which is bigger than any length difference
iDistance += 5;
}
}
iTest++;
continue;
} else {
// if only the group matches, add some more distance in addition to length difference
if (oToken.match == oTestToken.match) {
iDistance += Math.abs(oToken.length - oTestToken.length) + 10;
iTest++;
continue;
}
}
}
// if neither symbol nor group matched, add it to the missing tokens and add distance
aMissingTokens.push(oToken);
iDistance += 50 - i;
}
// if not all test tokens have been found, the format does not match
if (iTest < aTestTokens.length) {
bMatch = false;
}
// The current pattern is saved as the best pattern when there is a match and
// 1. the distance is smaller than the best distance or
// 2. the distance equals the best distance and the position of the token in the given skeleton which
// isn't the same between the given skeleton and the available skeleton is bigger than the best one's.
if (bMatch && (iDistance < oBestMatch.distance || (iDistance === oBestMatch.distance && iFirstDiffPos > oBestMatch.firstDiffPos))) {
oBestMatch.distance = iDistance;
oBestMatch.firstDiffPos = iFirstDiffPos;
oBestMatch.missingTokens = aMissingTokens;
oBestMatch.pattern = oAvailableFormats[sTestSkeleton];
oBestMatch.patternTokens = aTestTokens;
}
}
if (oBestMatch.pattern) {
return oBestMatch;
}
},
_expandFields: function(vPattern, aPatternTokens, aTokens) {
var bSinglePattern = (typeof vPattern === "string");
var aPatterns;
if (bSinglePattern) {
aPatterns = [vPattern];
} else {
aPatterns = vPattern;
}
var aResult = aPatterns.map(function(sPattern) {
var mGroups = {},
mPatternGroups = {},
sResultPatterm = "",
bQuoted = false,
i = 0,
iSkeletonLength,
iPatternLength,
iBestLength,
iNewLength,
oSkeletonToken,
oBestToken,
oSymbol,
sChar;
// Create a map of group names to token
aTokens.forEach(function(oToken) {
mGroups[oToken.group] = oToken;
});
// Create a map of group names to token in best pattern
aPatternTokens.forEach(function(oToken) {
mPatternGroups[oToken.group] = oToken;
});
// Loop through pattern and adjust symbol length
while (i < sPattern.length) {
sChar = sPattern.charAt(i);
if (bQuoted) {
sResultPatterm += sChar;
if (sChar == "'") {
bQuoted = false;
}
} else {
oSymbol = mCLDRSymbols[sChar];
// If symbol is a CLDR symbol and is contained in the group, expand length
if (oSymbol && mGroups[oSymbol.group] && mPatternGroups[oSymbol.group]) {
oSkeletonToken = mGroups[oSymbol.group];
oBestToken = mPatternGroups[oSymbol.group];
iSkeletonLength = oSkeletonToken.length;
iBestLength = oBestToken.length;
iPatternLength = 1;
while (sPattern.charAt(i + 1) == sChar) {
i++;
iPatternLength++;
}
// Prevent expanding the length of the field when:
// 1. The length in the best matching skeleton (iBestLength) matches the length of the application provided skeleton (iSkeletonLength) or
// 2. The length of the provided skeleton (iSkeletonLength) and the length of the result pattern (iPatternLength) are not in the same category (numeric or text)
// because switching between numeric to text representation is wrong in all cases
if (iSkeletonLength === iBestLength ||
((iSkeletonLength < oSymbol.numericCeiling) ?
(iPatternLength >= oSymbol.numericCeiling) : (iPatternLength < oSymbol.numericCeiling)
)) {
iNewLength = iPatternLength;
} else {
iNewLength = Math.max(iPatternLength, iSkeletonLength);
}
for (var j = 0; j < iNewLength; j++) {
sResultPatterm += sChar;
}
} else {
sResultPatterm += sChar;
if (sChar == "'") {
bQuoted = true;
}
}
}
i++;
}
return sResultPatterm;
});
return bSinglePattern ? aResult[0] : aResult;
},
_appendItems: function(aPatterns, aMissingTokens, sCalendarType) {
var oAppendItems = this._get(getCLDRCalendarName(sCalendarType), "dateTimeFormats", "appendItems");
aPatterns.forEach(function(sPattern, iIndex) {
var sDisplayName,
sAppendPattern,
sAppendField;
aMissingTokens.forEach(function(oToken) {
sAppendPattern = oAppendItems[oToken.group];
sDisplayName = "'" + this.getDisplayName(oToken.field) + "'";
sAppendField = "";
for (var i = 0; i < oToken.length; i++) {
sAppendField += oToken.symbol;
}
aPatterns[iIndex] = sAppendPattern.replace(/\{0\}/, sPattern).replace(/\{1\}/, sAppendField).replace(/\{2\}/, sDisplayName);
}.bind(this));
}.bind(this));
return aPatterns;
},
_getMixedFormatPattern: function(sSkeleton, oAvailableFormats, sCalendarType, vDiff) {
var rMixedSkeleton = /^([GyYqQMLwWEecdD]+)([hHkKjJmszZvVOXx]+)$/,
rWideMonth = /MMMM|LLLL/,
rAbbrevMonth = /MMM|LLL/,
rWeekDay = /E|e|c/,
oResult, sDateSkeleton, sTimeSkeleton, sStyle,
sDatePattern, sTimePattern, sDateTimePattern, sResultPattern;
// Split skeleton into date and time part
oResult = rMixedSkeleton.exec(sSkeleton);
sDateSkeleton = oResult[1];
sTimeSkeleton = oResult[2];
// Get patterns for date and time separately
sDatePattern = this._getFormatPattern(sDateSkeleton, oAvailableFormats, sCalendarType);
if (vDiff) {
sTimePattern = this.getCustomIntervalPattern(sTimeSkeleton, vDiff, sCalendarType);
} else {
sTimePattern = this._getFormatPattern(sTimeSkeleton, oAvailableFormats, sCalendarType);
}
// Combine patterns with datetime pattern, dependent on month and weekday
if (rWideMonth.test(sDateSkeleton)) {
sStyle = rWeekDay.test(sDateSkeleton) ? "full" : "long";
} else if (rAbbrevMonth.test(sDateSkeleton)) {
sStyle = "medium";
} else {
sStyle = "short";
}
sDateTimePattern = this.getDateTimePattern(sStyle, sCalendarType);
sResultPattern = sDateTimePattern.replace(/\{1\}/, sDatePattern).replace(/\{0\}/, sTimePattern);
return sResultPattern;
},
/**
* Get number symbol for the given type.
*
* @param {"decimal"|"group"|"minusSign"|"percentSign"|"plusSign"} sType the required type of symbol
* @returns {string} the selected number symbol
* @public
*/
getNumberSymbol: function(sType) {
assert(sType == "decimal" || sType == "group" || sType == "plusSign" || sType == "minusSign" || sType == "percentSign", "sType must be decimal, group, plusSign, minusSign or percentSign");
ret