@ui5/webcomponents-localization 0.0.0-47cc17a26 → 0.0.0-49bade48d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +293 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/DateFormat.d.ts +23 -0
  4. package/dist/DateFormat.js +16 -0
  5. package/dist/DateFormat.js.map +1 -1
  6. package/dist/Locale.d.ts +5 -0
  7. package/dist/Locale.js +7 -0
  8. package/dist/Locale.js.map +1 -0
  9. package/dist/NumberFormat.d.ts +5 -0
  10. package/dist/NumberFormat.js +7 -0
  11. package/dist/NumberFormat.js.map +1 -0
  12. package/dist/dates/convertMonthNumbersToMonthNames.d.ts +2 -2
  13. package/dist/dates/convertMonthNumbersToMonthNames.js.map +1 -1
  14. package/dist/generated/assets/cldr/Unicode-Data-Files-LICENSE.txt +36 -24
  15. package/dist/generated/assets/cldr/ar.json +624 -354
  16. package/dist/generated/assets/cldr/ar_EG.json +624 -354
  17. package/dist/generated/assets/cldr/ar_SA.json +625 -355
  18. package/dist/generated/assets/cldr/bg.json +339 -215
  19. package/dist/generated/assets/cldr/ca.json +633 -509
  20. package/dist/generated/assets/cldr/cnr.json +960 -653
  21. package/dist/generated/assets/cldr/cs.json +343 -531
  22. package/dist/generated/assets/cldr/cy.json +684 -1050
  23. package/dist/generated/assets/cldr/da.json +354 -231
  24. package/dist/generated/assets/cldr/de.json +458 -330
  25. package/dist/generated/assets/cldr/de_AT.json +459 -331
  26. package/dist/generated/assets/cldr/de_CH.json +522 -386
  27. package/dist/generated/assets/cldr/el.json +317 -152
  28. package/dist/generated/assets/cldr/el_CY.json +317 -152
  29. package/dist/generated/assets/cldr/en.json +606 -253
  30. package/dist/generated/assets/cldr/en_AU.json +830 -376
  31. package/dist/generated/assets/cldr/en_GB.json +696 -251
  32. package/dist/generated/assets/cldr/en_HK.json +544 -203
  33. package/dist/generated/assets/cldr/en_IE.json +533 -192
  34. package/dist/generated/assets/cldr/en_IN.json +820 -296
  35. package/dist/generated/assets/cldr/en_NZ.json +534 -193
  36. package/dist/generated/assets/cldr/en_PG.json +509 -168
  37. package/dist/generated/assets/cldr/en_SG.json +526 -185
  38. package/dist/generated/assets/cldr/en_ZA.json +529 -186
  39. package/dist/generated/assets/cldr/es.json +355 -230
  40. package/dist/generated/assets/cldr/es_AR.json +444 -288
  41. package/dist/generated/assets/cldr/es_BO.json +419 -264
  42. package/dist/generated/assets/cldr/es_CL.json +455 -300
  43. package/dist/generated/assets/cldr/es_CO.json +412 -257
  44. package/dist/generated/assets/cldr/es_MX.json +515 -352
  45. package/dist/generated/assets/cldr/es_PE.json +397 -234
  46. package/dist/generated/assets/cldr/es_UY.json +424 -269
  47. package/dist/generated/assets/cldr/es_VE.json +398 -243
  48. package/dist/generated/assets/cldr/et.json +390 -233
  49. package/dist/generated/assets/cldr/fa.json +510 -271
  50. package/dist/generated/assets/cldr/fi.json +497 -251
  51. package/dist/generated/assets/cldr/fr.json +339 -106
  52. package/dist/generated/assets/cldr/fr_BE.json +341 -108
  53. package/dist/generated/assets/cldr/fr_CA.json +652 -397
  54. package/dist/generated/assets/cldr/fr_CH.json +342 -109
  55. package/dist/generated/assets/cldr/fr_LU.json +339 -106
  56. package/dist/generated/assets/cldr/he.json +726 -904
  57. package/dist/generated/assets/cldr/hi.json +529 -288
  58. package/dist/generated/assets/cldr/hr.json +392 -383
  59. package/dist/generated/assets/cldr/hu.json +459 -335
  60. package/dist/generated/assets/cldr/id.json +379 -128
  61. package/dist/generated/assets/cldr/it.json +420 -301
  62. package/dist/generated/assets/cldr/it_CH.json +426 -307
  63. package/dist/generated/assets/cldr/ja.json +332 -74
  64. package/dist/generated/assets/cldr/kk.json +540 -240
  65. package/dist/generated/assets/cldr/ko.json +371 -115
  66. package/dist/generated/assets/cldr/lt.json +428 -545
  67. package/dist/generated/assets/cldr/lv.json +415 -413
  68. package/dist/generated/assets/cldr/mk.json +691 -559
  69. package/dist/generated/assets/cldr/ms.json +395 -136
  70. package/dist/generated/assets/cldr/nb.json +355 -234
  71. package/dist/generated/assets/cldr/nl.json +357 -251
  72. package/dist/generated/assets/cldr/nl_BE.json +428 -322
  73. package/dist/generated/assets/cldr/pl.json +333 -459
  74. package/dist/generated/assets/cldr/pt.json +419 -298
  75. package/dist/generated/assets/cldr/pt_PT.json +362 -231
  76. package/dist/generated/assets/cldr/ro.json +369 -397
  77. package/dist/generated/assets/cldr/ru.json +599 -368
  78. package/dist/generated/assets/cldr/ru_UA.json +598 -367
  79. package/dist/generated/assets/cldr/sk.json +343 -517
  80. package/dist/generated/assets/cldr/sl.json +477 -640
  81. package/dist/generated/assets/cldr/sr.json +478 -531
  82. package/dist/generated/assets/cldr/sr_Latn.json +631 -684
  83. package/dist/generated/assets/cldr/sv.json +395 -258
  84. package/dist/generated/assets/cldr/th.json +452 -199
  85. package/dist/generated/assets/cldr/tr.json +391 -238
  86. package/dist/generated/assets/cldr/uk.json +696 -342
  87. package/dist/generated/assets/cldr/vi.json +836 -487
  88. package/dist/generated/assets/cldr/zh_CN.json +358 -104
  89. package/dist/generated/assets/cldr/zh_HK.json +366 -113
  90. package/dist/generated/assets/cldr/zh_SG.json +371 -118
  91. package/dist/generated/assets/cldr/zh_TW.json +480 -238
  92. package/dist/getCachedLocaleDataInstance.js +2 -0
  93. package/dist/getCachedLocaleDataInstance.js.map +1 -1
  94. package/dist/locale/getLocaleData.js +2 -0
  95. package/dist/locale/getLocaleData.js.map +1 -1
  96. package/dist/sap/base/Event.js +2 -2
  97. package/dist/sap/base/Eventing.js +4 -3
  98. package/dist/sap/base/assert.js +1 -1
  99. package/dist/sap/base/config/MemoryConfigurationProvider.js +1 -1
  100. package/dist/sap/base/future.js +12 -0
  101. package/dist/sap/base/i18n/Formatting.d.ts +5 -0
  102. package/dist/sap/base/i18n/Formatting.js +7 -0
  103. package/dist/sap/base/i18n/Formatting.js.map +1 -1
  104. package/dist/sap/base/i18n/LanguageTag.js +18 -12
  105. package/dist/sap/base/i18n/Localization.d.ts +3 -0
  106. package/dist/sap/base/i18n/Localization.js +5 -0
  107. package/dist/sap/base/i18n/Localization.js.map +1 -1
  108. package/dist/sap/base/i18n/ResourceBundle.js +20 -0
  109. package/dist/sap/base/i18n/date/CalendarType.js +9 -8
  110. package/dist/sap/base/i18n/date/CalendarWeekNumbering.js +9 -8
  111. package/dist/sap/base/i18n/date/TimezoneUtils.js +170 -18
  112. package/dist/sap/base/i18n/date/_EnumHelper.js +37 -0
  113. package/dist/sap/base/strings/camelize.js +1 -1
  114. package/dist/sap/base/strings/formatMessage.js +6 -1
  115. package/dist/sap/base/util/Properties.js +12 -0
  116. package/dist/sap/base/util/Version.js +19 -13
  117. package/dist/sap/base/util/_merge.js +1 -1
  118. package/dist/sap/base/util/array/uniqueSort.js +1 -1
  119. package/dist/sap/base/util/deepClone.js +2 -2
  120. package/dist/sap/base/util/deepEqual.js +1 -1
  121. package/dist/sap/base/util/deepExtend.js +60 -0
  122. package/dist/sap/base/util/extend.js +1 -1
  123. package/dist/sap/base/util/fetch.js +8 -0
  124. package/dist/sap/base/util/isEmptyObject.js +1 -1
  125. package/dist/sap/base/util/isPlainObject.js +1 -1
  126. package/dist/sap/base/util/merge.js +57 -0
  127. package/dist/sap/base/util/mixedFetch.js +3 -0
  128. package/dist/sap/base/util/now.js +7 -13
  129. package/dist/sap/base/util/uid.js +1 -1
  130. package/dist/sap/ui/Device.js +27 -0
  131. package/dist/sap/ui/Global.js +1 -0
  132. package/dist/sap/ui/VersionInfo.js +14 -0
  133. package/dist/sap/ui/base/DataType.js +63 -12
  134. package/dist/sap/ui/base/DesignTime.js +11 -0
  135. package/dist/sap/ui/base/EventProvider.js +21 -0
  136. package/dist/sap/ui/base/Metadata.js +45 -10
  137. package/dist/sap/ui/base/Object.js +2 -2
  138. package/dist/sap/ui/base/OwnStatics.js +6 -0
  139. package/dist/sap/ui/base/SyncPromise.js +98 -0
  140. package/dist/sap/ui/core/AnimationMode.js +42 -0
  141. package/dist/sap/ui/core/CalendarType.js +2 -2
  142. package/dist/sap/ui/core/ControlBehavior.js +11 -0
  143. package/dist/sap/ui/core/Lib.js +41 -0
  144. package/dist/sap/ui/core/Locale.js +9 -9
  145. package/dist/sap/ui/core/LocaleData.js +498 -333
  146. package/dist/sap/ui/core/Theming.js +25 -525
  147. package/dist/sap/ui/core/_UrlResolver.js +11 -0
  148. package/dist/sap/ui/core/date/Buddhist.js +2 -2
  149. package/dist/sap/ui/core/date/CalendarUtils.js +14 -12
  150. package/dist/sap/ui/core/date/CalendarWeekNumbering.js +2 -2
  151. package/dist/sap/ui/core/date/Gregorian.js +2 -2
  152. package/dist/sap/ui/core/date/Islamic.js +2 -2
  153. package/dist/sap/ui/core/date/Japanese.js +2 -2
  154. package/dist/sap/ui/core/date/Persian.js +2 -2
  155. package/dist/sap/ui/core/date/UI5Date.js +80 -32
  156. package/dist/sap/ui/core/date/UniversalDate.js +83 -101
  157. package/dist/sap/ui/core/format/DateFormat.js +70 -73
  158. package/dist/sap/ui/core/format/FormatUtils.js +30 -0
  159. package/dist/sap/ui/core/format/NumberFormat.js +3164 -0
  160. package/dist/sap/ui/core/format/TimezoneUtil.js +80 -11
  161. package/dist/sap/ui/core/getCompatibilityVersion.js +7 -0
  162. package/dist/sap/ui/core/theming/ThemeHelper.js +14 -0
  163. package/dist/sap/ui/security/Security.js +12 -0
  164. package/dist/sap/ui/util/XMLHelper.js +11 -0
  165. package/dist/sap/ui/util/_URL.js +1 -0
  166. package/dist/ui5loader-autoconfig.js +1 -0
  167. package/package-scripts.cjs +1 -1
  168. package/package.json +9 -11
  169. package/used-modules.txt +7 -1
@@ -0,0 +1,3164 @@
1
+ /*!
2
+ * OpenUI5
3
+ * (c) Copyright 2026 SAP SE or an SAP affiliate company.
4
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
5
+ */
6
+ // Provides class sap.ui.core.format.NumberFormat
7
+ import Formatting from "../../../base/i18n/Formatting.js";
8
+ import Localization from "../../../base/i18n/Localization.js";
9
+ import BaseObject from "../../base/Object.js";
10
+ import Locale from "../Locale.js";
11
+ import LocaleData from "../LocaleData.js";
12
+ import Supportability from "../Supportability.js";
13
+ import FormatUtils from "./FormatUtils.js";
14
+ import Log from "../../../base/Log.js";
15
+ import assert from "../../../base/assert.js";
16
+ import extend from "../../../base/util/extend.js";
17
+ /**
18
+ * Format classes
19
+ *
20
+ * @namespace
21
+ * @name sap.ui.core.format
22
+ * @public
23
+ */
24
+
25
+ /**
26
+ * Constructor for NumberFormat - must not be used: To get a NumberFormat instance, please use getInstance, getFloatInstance or getIntegerInstance.
27
+ *
28
+ * @class
29
+ * The NumberFormat is a static class for formatting and parsing numeric values according
30
+ * to a set of format options.
31
+ *
32
+ * @public
33
+ * @hideconstructor
34
+ * @alias sap.ui.core.format.NumberFormat
35
+ * @extends sap.ui.base.Object
36
+ */
37
+ var NumberFormat = BaseObject.extend("sap.ui.core.format.NumberFormat", /** @lends sap.ui.core.format.NumberFormat.prototype */{
38
+ constructor: function (oFormatOptions) {
39
+ // Do not use the constructor
40
+ throw new Error();
41
+ }
42
+ });
43
+
44
+ // Regex for replacing the number part of a decimal or currency pattern
45
+ const rNumberPattern = /[0#.,]+/;
46
+ const rAllWhiteSpaces = /\s/g;
47
+ // Regex for checking whether the last character belongs to the Unicode General Category L (letter)
48
+ const rEndsWithLetter = /\p{L}$/u;
49
+ // Regex for checking whether the first character belongs to the Unicode General Category L (letter)
50
+ const rStartsWithLetter = /^\p{L}/u;
51
+ // splits a currency pattern into following matching groups:
52
+ // 0: complete match
53
+ // 1: the optional number pattern in front of the currency placeholder
54
+ // 2: the characters between the number pattern in front of the currency placeholder and the currency placeholder
55
+ // 3: the currency placeholder
56
+ // 4: the characters between the currency placeholder and the number pattern after the currency placeholder
57
+ // 5: the optional number pattern after the currency placeholder
58
+ const rSplitCurrencyPattern = /([0#.,]*)([^0#.,]*)(¤)([^0#.,]*)([0#.,]*)/;
59
+ // Regex for checking if a number has leading zeros
60
+ const rLeadingZeros = /^(-?)0+(\d)/;
61
+ // Regex for matching the number placeholder in pattern
62
+ const rNumPlaceHolder = /0+(\.0+)?/;
63
+ // Regex for checking that the given string only consists of '0' characters
64
+ const rOnlyZeros = /^0+$/;
65
+ // A regular expresssion that can be used to remove a leading "-" from a number representing zero,
66
+ // e.g. "-0", or "-0.00"; $1 contains the number without the leading "-"
67
+ const rRemoveMinusFromZero = /^-(0(?:.0+)?)$/;
68
+ // A regular expression that can be used to remove trailing zeros from a number
69
+ const rTrailingZeros = /0+$/;
70
+ // A regular expression that can be used to remove all RTL characters
71
+ // see: https://www.unicode.org/reports/tr44/#Bidi_Class_Values (Explicit Formatting Types)
72
+ const rAllRTLCharacters = /[\u061c\u200e\u200f\u202a\u202b\u202c]/g;
73
+ // A regular expression that can be used to remove the left-to-right mark character
74
+ const rLeftToRightMark = /\u200e/;
75
+ // Array of all available power of tens for short formats, max: 100 000 000 000 000
76
+ const aPowerOfTens = [10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000];
77
+
78
+ /*
79
+ * Is used to validate existing grouping separators.
80
+ * e.g. yyy.yyy.yyy -> /^\d+(?:\.?\d{3})*\.?\d{3}$/
81
+ */
82
+ var getGroupingRegExp = function (groupingSeparator, groupingSize, groupingBaseSize) {
83
+ var sGroupingEscaped = quote(groupingSeparator);
84
+ return new RegExp("^\\d+" + "(?:" + sGroupingEscaped + "?" + "\\d{" + groupingSize + "}" + ")*" + "" + sGroupingEscaped + "?" + "\\d{" + groupingBaseSize + "}" + "$");
85
+ };
86
+
87
+ /**
88
+ * Internal enumeration to differentiate number types
89
+ */
90
+ var mNumberType = {
91
+ INTEGER: "integer",
92
+ FLOAT: "float",
93
+ CURRENCY: "currency",
94
+ UNIT: "unit",
95
+ PERCENT: "percent"
96
+ };
97
+
98
+ /**
99
+ * Specifies a rounding behavior for numerical operations capable of discarding precision. Each rounding mode in
100
+ * this object indicates how the least significant returned digits of rounded result are to be calculated.
101
+ *
102
+ * @public
103
+ * @enum {string}
104
+ * @alias sap.ui.core.format.NumberFormat.RoundingMode
105
+ */
106
+ var mRoundingMode = {
107
+ /**
108
+ * Rounding mode to round towards negative infinity; examples of rounding results to one fractional digit: 0.51
109
+ * is rounded to 0.5, and -0.51 is rounded to -0.6.
110
+ * @public
111
+ * @type {string}
112
+ */
113
+ FLOOR: "FLOOR",
114
+ /**
115
+ * Rounding mode to round towards positive infinity; examples of rounding results to one fractional digit: 0.51
116
+ * is rounded to 0.6, and -0.51 is rounded to -0.5.
117
+ * @public
118
+ * @type {string}
119
+ */
120
+ CEILING: "CEILING",
121
+ /**
122
+ * Rounding mode to round towards zero; examples of rounding results to one fractional digit: 0.59 is rounded to
123
+ * 0.5, and -0.59 is rounded to -0.5.
124
+ * @public
125
+ * @type {string}
126
+ */
127
+ TOWARDS_ZERO: "TOWARDS_ZERO",
128
+ /**
129
+ * Rounding mode to round away from zero; examples of rounding results to one fractional digit: 0.51 is rounded
130
+ * to 0.6, and -0.51 is rounded to -0.6.
131
+ * @public
132
+ * @type {string}
133
+ */
134
+ AWAY_FROM_ZERO: "AWAY_FROM_ZERO",
135
+ /**
136
+ * Rounding mode to round towards the nearest neighbor, unless both neighbors are equidistant, in which case
137
+ * round towards negative infinity; examples of rounding results to one fractional digit: 0.54 or 0.46 are
138
+ * rounded to 0.5, -0.54 or -0.46 are rounded to -0.5, 0.55 is rounded to 0.5, and -0.55 is rounded to -0.6.
139
+ * @public
140
+ * @type {string}
141
+ */
142
+ HALF_FLOOR: "HALF_FLOOR",
143
+ /**
144
+ * Rounding mode to round towards the nearest neighbor, unless both neighbors are equidistant, in which case
145
+ * round towards positive infinity; examples of rounding results to one fractional digit: 0.54 or 0.46 are
146
+ * rounded to 0.5, -0.54 or -0.46 are rounded to -0.5, 0.55 is rounded to 0.6, and -0.55 is rounded to -0.5.
147
+ * @public
148
+ * @type {string}
149
+ */
150
+ HALF_CEILING: "HALF_CEILING",
151
+ /**
152
+ * Rounding mode to round towards the nearest neighbor, unless both neighbors are equidistant, in which case
153
+ * round towards zero; examples of rounding results to one fractional digit: 0.54 or 0.46 are rounded to 0.5,
154
+ * -0.54 or -0.46 are rounded to -0.5, 0.55 is rounded to 0.5, and -0.55 is rounded to -0.5.
155
+ * @public
156
+ * @type {string}
157
+ */
158
+ HALF_TOWARDS_ZERO: "HALF_TOWARDS_ZERO",
159
+ /**
160
+ * Rounding mode to round towards the nearest neighbor unless, both neighbors are equidistant, in which case
161
+ * round away from zero; examples of rounding results to one fractional digit: 0.54 or 0.46 are rounded to 0.5,
162
+ * -0.54 or -0.46 are rounded to -0.5, 0.55 is rounded to 0.6, and -0.55 is rounded to -0.6.
163
+ * @public
164
+ * @type {string}
165
+ */
166
+ HALF_AWAY_FROM_ZERO: "HALF_AWAY_FROM_ZERO"
167
+ };
168
+
169
+ /**
170
+ * Adds the summand given as a number to an decimal given as a string.
171
+ *
172
+ * @param {string} sDecimal A positive or negative decimal number as string
173
+ * @param {int} iSummand An integer between -9 and 9 to be added to the given decimal number
174
+ * @returns {string} The sum of the two numbers as a string
175
+ *
176
+ * @private
177
+ */
178
+ NumberFormat.add = function (sDecimal, iSummand) {
179
+ const aParts = sDecimal.split(".");
180
+ let sInteger = aParts[0];
181
+ const sFractionPart = aParts[1];
182
+ const bNegative = sInteger[0] === "-";
183
+ if (bNegative) {
184
+ sInteger = sInteger.slice(1);
185
+ iSummand = -iSummand;
186
+ }
187
+ const aDigits = sInteger.split("").map(Number);
188
+ const iLastIndex = aDigits.length - 1;
189
+ aDigits[iLastIndex] += iSummand;
190
+ for (let i = iLastIndex; i >= 0; i -= 1) {
191
+ if (aDigits[i] >= 10) {
192
+ aDigits[i] = aDigits[i] % 10;
193
+ if (i === 0) {
194
+ aDigits.unshift(1);
195
+ break;
196
+ }
197
+ aDigits[i - 1] += 1;
198
+ } else if (aDigits[i] < 0 && i > 0) {
199
+ aDigits[i] = 10 + aDigits[i];
200
+ aDigits[i - 1] -= 1;
201
+ if (i === 1 && aDigits[0] === 0) {
202
+ aDigits.shift();
203
+ break;
204
+ }
205
+ } else {
206
+ break;
207
+ }
208
+ }
209
+ if (bNegative) {
210
+ aDigits[0] = -aDigits[0];
211
+ }
212
+ let sResult = aDigits.join("");
213
+ if (!sFractionPart) {
214
+ return sResult;
215
+ }
216
+
217
+ // If sResult is 0, the sign may be lost and has to be restored, e.g. "-5.123" + 5 => -5 + 5 = 0 => "-0.123"
218
+ sResult = sResult === "0" && bNegative ? "-0" : sResult;
219
+ const sResultSign = sResult[0] === "-" ? "-" : "";
220
+ // If both signs are equal, the fraction part can simply be appended
221
+ if (bNegative === !!sResultSign) {
222
+ return sResult + "." + sFractionPart;
223
+ }
224
+
225
+ // If the signs are different, aDigits contains only one digit which is different from zero; to compute the
226
+ // result, the result sign has to be kept, the integer part is the absolute sResult reduced by one, and the
227
+ // fractional part is (1 - fractional part), e.g. "2.123" - 5 => 2 - 5 = -3 => sign = "-", integer part is
228
+ // |-3| - 1 = 2 and fractional part is 1 - 0.123 = 0.877 without the leading "0." => "-2.877"
229
+ const aFractionDigits = sFractionPart.split("").map(Number);
230
+ for (let i = aFractionDigits.length - 1; i >= 0; i -= 1) {
231
+ aFractionDigits[i] = 10 - aFractionDigits[i];
232
+ if (i > 0) {
233
+ aFractionDigits[i - 1] += 1;
234
+ }
235
+ }
236
+ return sResultSign + (Math.abs(aDigits[0]) - 1) + "." + aFractionDigits.join("");
237
+ };
238
+
239
+ /**
240
+ * Derives the maximum number of possible decimals from the given format option's <code>maxFractionDigits</code>
241
+ * and <code>decimals</code> properties.
242
+ *
243
+ * If <code>decimals</code> and <code>maxFractionDigits</code> are >= 0, then the minimum of
244
+ * <code>maxFractionDigits</code> and <code>decimals</code> is returned, - otherwise
245
+ * <code>decimals</code> is returned.
246
+ *
247
+ * @param {object} oFormatOptions
248
+ * @param {int} [oFormatOptions.decimals]
249
+ * The number of decimal digits
250
+ * @param {int} [oFormatOptions.maxFractionDigits]
251
+ * The maximum number of decimal digits
252
+ * @returns {int}
253
+ * The maximum decimals to be used
254
+ *
255
+ * @private
256
+ * @static
257
+ */
258
+ NumberFormat.getMaximumDecimals = function ({
259
+ decimals,
260
+ maxFractionDigits
261
+ }) {
262
+ if (maxFractionDigits >= 0 && decimals > 0 && maxFractionDigits < decimals) {
263
+ return maxFractionDigits;
264
+ }
265
+ return decimals;
266
+ };
267
+
268
+ /**
269
+ * Rounds the given number up to the smallest integer greater than or equal to the given number.
270
+ *
271
+ * @param {number|string} vNumber
272
+ * The number to be rounded up; it has at least one digit in front of the decimal point in case of type "string"
273
+ * @returns {number|string}
274
+ * The smallest integer greater than or equal to the given number; the returned type is the same as the type of
275
+ * the given number
276
+ */
277
+ function ceil(vNumber) {
278
+ if (typeof vNumber === "number") {
279
+ return Math.ceil(vNumber);
280
+ }
281
+ const [sIntegerPart, sFractionPart = "0"] = vNumber.split(".");
282
+ return rOnlyZeros.test(sFractionPart) || sIntegerPart[0] === "-" ? sIntegerPart : NumberFormat.add(sIntegerPart, 1);
283
+ }
284
+
285
+ /**
286
+ * Rounds the given number down to the largest integer less than or equal to the given number.
287
+ *
288
+ * @param {number|string} vNumber
289
+ * The number to be rounded down; it has at least one digit in front of the decimal point in case of type "string"
290
+ * @returns {number|string}
291
+ * The largest integer less than or equal to the given number; the returned type is the same as the type of the
292
+ * given number
293
+ */
294
+ function floor(vNumber) {
295
+ if (typeof vNumber === "number") {
296
+ return Math.floor(vNumber);
297
+ }
298
+ const [sIntegerPart, sFractionPart = "0"] = vNumber.split(".");
299
+ return rOnlyZeros.test(sFractionPart) || sIntegerPart[0] !== "-" ? sIntegerPart : NumberFormat.add(sIntegerPart, -1);
300
+ }
301
+
302
+ /**
303
+ * Adds 0.5 to or subtracts 0.5 from the given number.
304
+ *
305
+ * @param {number|string} vNumber
306
+ * The number to be increased or decreased by 0.5
307
+ * @param {boolean} bIncrease
308
+ * Whether to increase the number by 0.5; otherwise the number is decreased by 0.5
309
+ * @returns {number|string}
310
+ * The number increased or decreased by 0.5; the returned type is the same as the type of the given number
311
+ */
312
+ function increaseOrDecreaseByHalf(vNumber, bIncrease) {
313
+ if (typeof vNumber === "number") {
314
+ return bIncrease ? vNumber + 0.5 : vNumber - 0.5;
315
+ }
316
+ vNumber = NumberFormat._shiftDecimalPoint(vNumber, 1);
317
+ vNumber = NumberFormat.add(vNumber, bIncrease ? 5 : -5);
318
+ return NumberFormat._shiftDecimalPoint(vNumber, -1);
319
+ }
320
+ const mRoundingFunction = {
321
+ [mRoundingMode.FLOOR]: floor,
322
+ [mRoundingMode.CEILING]: ceil,
323
+ [mRoundingMode.TOWARDS_ZERO]: vNumber => vNumber > 0 ? floor(vNumber) : ceil(vNumber),
324
+ [mRoundingMode.AWAY_FROM_ZERO]: vNumber => vNumber > 0 ? ceil(vNumber) : floor(vNumber),
325
+ [mRoundingMode.HALF_TOWARDS_ZERO]: vNumber => {
326
+ const bPositive = vNumber > 0;
327
+ vNumber = increaseOrDecreaseByHalf(vNumber, !bPositive);
328
+ return bPositive ? ceil(vNumber) : floor(vNumber);
329
+ },
330
+ [mRoundingMode.HALF_AWAY_FROM_ZERO]: vNumber => {
331
+ const bPositive = vNumber > 0;
332
+ vNumber = increaseOrDecreaseByHalf(vNumber, bPositive);
333
+ return bPositive ? floor(vNumber) : ceil(vNumber);
334
+ },
335
+ [mRoundingMode.HALF_FLOOR]: vNumber => ceil(increaseOrDecreaseByHalf(vNumber, false)),
336
+ [mRoundingMode.HALF_CEILING]: vNumber => floor(increaseOrDecreaseByHalf(vNumber, true))
337
+ };
338
+ NumberFormat.RoundingMode = mRoundingMode;
339
+
340
+ /*
341
+ * Default format options for Integer
342
+ */
343
+ NumberFormat.oDefaultIntegerFormat = {
344
+ minIntegerDigits: 1,
345
+ maxIntegerDigits: 99,
346
+ minFractionDigits: 0,
347
+ maxFractionDigits: 0,
348
+ strictGroupingValidation: false,
349
+ groupingEnabled: false,
350
+ groupingSize: 3,
351
+ groupingSeparator: ",",
352
+ decimalSeparator: ".",
353
+ plusSign: "+",
354
+ minusSign: "-",
355
+ isInteger: true,
356
+ type: mNumberType.INTEGER,
357
+ showMeasure: false,
358
+ style: "standard",
359
+ showNumber: true,
360
+ parseAsString: false,
361
+ preserveDecimals: false,
362
+ roundingMode: NumberFormat.RoundingMode.TOWARDS_ZERO,
363
+ emptyString: NaN,
364
+ showScale: true
365
+ };
366
+
367
+ /*
368
+ * Default format options for Float
369
+ */
370
+ NumberFormat.oDefaultFloatFormat = {
371
+ minIntegerDigits: 1,
372
+ maxIntegerDigits: 99,
373
+ minFractionDigits: 0,
374
+ maxFractionDigits: 99,
375
+ strictGroupingValidation: false,
376
+ groupingEnabled: true,
377
+ groupingSize: 3,
378
+ groupingSeparator: ",",
379
+ decimalSeparator: ".",
380
+ plusSign: "+",
381
+ minusSign: "-",
382
+ isInteger: false,
383
+ type: mNumberType.FLOAT,
384
+ showMeasure: false,
385
+ style: "standard",
386
+ showNumber: true,
387
+ parseAsString: false,
388
+ preserveDecimals: false,
389
+ roundingMode: NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO,
390
+ emptyString: NaN,
391
+ showScale: true
392
+ };
393
+
394
+ /*
395
+ * Default format options for Percent
396
+ */
397
+ NumberFormat.oDefaultPercentFormat = {
398
+ minIntegerDigits: 1,
399
+ maxIntegerDigits: 99,
400
+ minFractionDigits: 0,
401
+ maxFractionDigits: 99,
402
+ strictGroupingValidation: false,
403
+ groupingEnabled: true,
404
+ groupingSize: 3,
405
+ groupingSeparator: ",",
406
+ decimalSeparator: ".",
407
+ plusSign: "+",
408
+ minusSign: "-",
409
+ percentSign: "%",
410
+ isInteger: false,
411
+ type: mNumberType.PERCENT,
412
+ showMeasure: false,
413
+ style: "standard",
414
+ showNumber: true,
415
+ parseAsString: false,
416
+ preserveDecimals: false,
417
+ roundingMode: NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO,
418
+ emptyString: NaN,
419
+ showScale: true
420
+ };
421
+
422
+ /*
423
+ * Default format options for Currency
424
+ * @name sap.ui.core.format.NumberFormat.oDefaultCurrencyFormat
425
+ */
426
+ NumberFormat.oDefaultCurrencyFormat = {
427
+ minIntegerDigits: 1,
428
+ maxIntegerDigits: 99,
429
+ // the default value for min/maxFractionDigits is defined in oLocaleData.getCurrencyDigits
430
+ // they need to be left undefined here in order to detect whether they are set from outside
431
+ strictGroupingValidation: false,
432
+ groupingEnabled: true,
433
+ groupingSize: 3,
434
+ groupingSeparator: ",",
435
+ decimalSeparator: ".",
436
+ plusSign: "+",
437
+ minusSign: "-",
438
+ isInteger: false,
439
+ type: mNumberType.CURRENCY,
440
+ showMeasure: true,
441
+ currencyCode: true,
442
+ currencyContext: 'standard',
443
+ style: "standard",
444
+ showNumber: true,
445
+ customCurrencies: undefined,
446
+ parseAsString: false,
447
+ preserveDecimals: false,
448
+ roundingMode: NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO,
449
+ emptyString: NaN,
450
+ showScale: true,
451
+ // The 'precision' format option is ignored because the number of decimals shouldn't
452
+ // depend on the number of integer part of a number
453
+ ignorePrecision: true
454
+ };
455
+
456
+ /*
457
+ * Default format options for Unit (type is CLDR)
458
+ * @name sap.ui.core.format.NumberFormat.oDefaultUnitFormat
459
+ */
460
+ NumberFormat.oDefaultUnitFormat = {
461
+ minIntegerDigits: 1,
462
+ maxIntegerDigits: 99,
463
+ strictGroupingValidation: false,
464
+ groupingEnabled: true,
465
+ groupingSize: 3,
466
+ groupingSeparator: ",",
467
+ decimalSeparator: ".",
468
+ plusSign: "+",
469
+ minusSign: "-",
470
+ isInteger: false,
471
+ type: mNumberType.UNIT,
472
+ showMeasure: true,
473
+ style: "standard",
474
+ showNumber: true,
475
+ customUnits: undefined,
476
+ allowedUnits: undefined,
477
+ parseAsString: false,
478
+ preserveDecimals: false,
479
+ roundingMode: NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO,
480
+ emptyString: NaN,
481
+ showScale: true
482
+ };
483
+
484
+ /**
485
+ * Checks the given format options if decimal padding is allowed and if the decimal padding value is supported.
486
+ *
487
+ * @param {object} [oFormatOptions]
488
+ * The format options to be checked
489
+ * @param {int} [oFormatOptions.decimalPadding]
490
+ * The format option decimal padding
491
+ * @param {boolean} [oFormatOptions.showMeasure]
492
+ * The format option show measure
493
+ * @param {string} [oFormatOptions.style]
494
+ * The format option style
495
+ * @param {boolean} [bDecimalPaddingSupported=true]
496
+ * Whether decimal padding is supported
497
+ * @param {boolean} [bShowMeasureMustBeFalse=false]
498
+ * Whether the format option <code>showMeasure</code> must be set to false
499
+ * @throws {Error} If decimal padding cannot be used or the given decimal padding value is not supported
500
+ *
501
+ * @private
502
+ * @static
503
+ */
504
+ NumberFormat.checkDecimalPadding = function (oFormatOptions, bDecimalPaddingSupported = true, bShowMeasureMustBeFalse = false) {
505
+ const iDecimalPadding = oFormatOptions?.decimalPadding;
506
+ if (!bDecimalPaddingSupported && iDecimalPadding !== undefined) {
507
+ throw new Error("Unsupported format option: 'decimalPadding' cannot be used with an integer or percent" + " instance of sap.ui.core.format.NumberFormat");
508
+ }
509
+ if (bShowMeasureMustBeFalse && iDecimalPadding && oFormatOptions?.showMeasure !== false) {
510
+ throw new Error("The format option 'decimalPadding' can only be used if the format option" + " 'showMeasure' is set to false");
511
+ }
512
+ if (iDecimalPadding < 1) {
513
+ throw new Error("The format option 'decimalPadding' must have a value greater than 0");
514
+ }
515
+ if (iDecimalPadding > 0 && (oFormatOptions?.style === "short" || oFormatOptions?.style === "long")) {
516
+ throw new Error("The format option 'decimalPadding' can only be used if the format option" + " 'style' is not set to 'short' or 'long'");
517
+ }
518
+ };
519
+
520
+ /**
521
+ * An alias for {@link #getFloatInstance}.
522
+ *
523
+ * @param {object} [oFormatOptions] Object which defines the format options. See the documentation of
524
+ * {@link #getFloatInstance} for the parameters
525
+ * @param {sap.ui.core.Locale} [oLocale] Locale to get the formatter for
526
+ * @return {sap.ui.core.format.NumberFormat} float instance of the NumberFormat
527
+ *
528
+ */
529
+ NumberFormat.getInstance = function (oFormatOptions, oLocale) {
530
+ return this.getFloatInstance(oFormatOptions, oLocale);
531
+ };
532
+
533
+ /**
534
+ * Get a float instance of the NumberFormat, which can be used for formatting.
535
+ *
536
+ * <p>
537
+ * This instance has HALF_AWAY_FROM_ZERO set as default rounding mode.
538
+ * Please set the roundingMode property in oFormatOptions to change the
539
+ * default value.
540
+ * </p>
541
+ *
542
+ * The following example shows how grouping is done:
543
+ * <pre>
544
+ * var oFormat = NumberFormat.getFloatInstance({
545
+ * "groupingEnabled": true, // grouping is enabled
546
+ * "groupingSeparator": '.', // grouping separator is '.'
547
+ * "groupingSize": 3, // the amount of digits to be grouped (here: thousand)
548
+ * "decimalSeparator": "," // the decimal separator must be different from the grouping separator
549
+ * });
550
+ *
551
+ * oFormat.format(1234.56); // "1.234,56"
552
+ * </pre>
553
+ *
554
+ * @param {object} [oFormatOptions] The option object, which supports the following parameters.
555
+ * If no options are given, default values according to the type and locale settings are used.
556
+ * @param {int} [oFormatOptions.decimalPadding]
557
+ * The target length of places after the decimal separator; if the number has fewer decimal places than given in
558
+ * this option, it is padded with whitespaces at the end up to the target length. An additional whitespace
559
+ * character for the decimal separator is added for a number without any decimals.
560
+ * <b>Note:</b> This format option is only allowed if the following conditions apply:
561
+ * <ul>
562
+ * <li>It has a value greater than 0.</li>
563
+ * <li>The <code>oFormatOptions.style</code> format option is <b>not</b> set to <code>"short"</code> or
564
+ * <code>"long"</code>.</li>
565
+ * </ul>
566
+ * @param {int} [oFormatOptions.decimals] defines the number of decimal digits
567
+ * @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
568
+ * Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
569
+ * @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
570
+ * is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string),
571
+ * NaN, <code>null</code>, or 0.
572
+ * The 'format' and 'parse' functions are done in a symmetric way. For example, when this
573
+ * parameter is set to NaN, an empty string is parsed as NaN, and NaN is formatted as an empty
574
+ * string.
575
+ * @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
576
+ * it is different from the grouping size (e.g. Indian grouping)
577
+ * @param {boolean} [oFormatOptions.groupingEnabled=true] defines whether grouping is enabled
578
+ * (grouping separators are shown).
579
+ * <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
580
+ * a non-positive value.
581
+ * @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
582
+ * Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
583
+ * @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
584
+ * is <code>3</code>.
585
+ * <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
586
+ * @param {int} [oFormatOptions.maxFractionDigits=99] defines the maximum number of decimal digits
587
+ * @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
588
+ * If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
589
+ * @param {int} [oFormatOptions.minFractionDigits=0] defines the minimal number of decimal digits
590
+ * @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
591
+ * @param {string} [oFormatOptions.minusSign] defines the used minus symbol
592
+ * @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
593
+ * the string from the parse function in order to keep the precision for big numbers. Numbers
594
+ * in scientific notation are parsed back to standard notation. For example, "5e-3" is parsed
595
+ * to "0.005".
596
+ * @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
597
+ * @param {string} [oFormatOptions.plusSign] defines the used plus symbol
598
+ * @param {int} [oFormatOptions.precision] The maximum number of digits in the formatted representation of a number;
599
+ * if the <code>precision</code> is less than the overall length of the number, its fractional part is truncated
600
+ * through rounding. As the <code>precision</code> only affects the rounding of a number, its integer part can
601
+ * retain more digits than defined by this parameter.
602
+ * <b>Example:</b> With a <code>precision</code> of 2, <code>234.567</code> is formatted to <code>235</code>.
603
+ * <b>Note:</b> The formatted output may differ depending on locale.
604
+ * @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
605
+ * decimal digits except trailing zeros in case there are more decimals than the
606
+ * <code>maxFractionDigits</code> format option allows.
607
+ * If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
608
+ * @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=HALF_AWAY_FROM_ZERO]
609
+ * Specifies the rounding behavior for discarding the digits after the maximum fraction digits
610
+ * defined by <code>maxFractionDigits</code>.
611
+ * This can be assigned
612
+ * <ul>
613
+ * <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
614
+ * <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
615
+ * number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
616
+ * string based numbers are not rounded via this custom function.</li>
617
+ * </ul>
618
+ * @param {int} [oFormatOptions.shortDecimals] defines the number of decimal in the shortened format string. If this isn't specified, the 'decimals' options is used
619
+ * @param {int} [oFormatOptions.shortLimit] only use short number formatting for values above this limit
620
+ * @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the scale factor for 'short' or 'long' style format is generated. The generated scale factor is
621
+ * used for all numbers which are formatted with this format instance. This option has effect only when the option 'style' is set to 'short' or 'long'. This option is by default set
622
+ * with <code>undefined</code> which means the scale factor is selected automatically for each number being formatted.
623
+ * @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number. This option takes effect only when the 'style' options is set to either 'short' or 'long'.
624
+ * @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
625
+ * @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
626
+ * 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
627
+ * numbers are formatted into compact forms. When this option is set, the default value of the
628
+ * 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
629
+ * decimals, shortDecimals, or the 'precision' option itself.
630
+ * @param {sap.ui.core.Locale} [oLocale]
631
+ * The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
632
+ * used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
633
+ * @return {sap.ui.core.format.NumberFormat} float instance of the NumberFormat
634
+ * @throws {Error} If the <code>oFormatOptions.decimalPadding</code> is set but is not allowed
635
+ * @static
636
+ * @public
637
+ */
638
+ NumberFormat.getFloatInstance = function (oFormatOptions, oLocale) {
639
+ NumberFormat.checkDecimalPadding(oFormatOptions);
640
+ const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
641
+ const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.FLOAT);
642
+ oFormat.oFormatOptions = extend({}, this.oDefaultFloatFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
643
+ oFormat.checkGroupingFormatOptions();
644
+ return oFormat;
645
+ };
646
+
647
+ /**
648
+ * Get an integer instance of the NumberFormat, which can be used for formatting.
649
+ *
650
+ * <p>
651
+ * This instance has TOWARDS_ZERO set as default rounding mode.
652
+ * Please set the roundingMode property in oFormatOptions to change the
653
+ * default value.
654
+ * </p>
655
+ *
656
+ * The following example shows how grouping is done:
657
+ * <pre>
658
+ * var oFormat = NumberFormat.getIntegerInstance({
659
+ * "groupingEnabled": true, // grouping is enabled
660
+ * "groupingSeparator": '.', // grouping separator is '.'
661
+ * "groupingSize": 3 // the amount of digits to be grouped (here: thousand)
662
+ * });
663
+ *
664
+ * oFormat.format(1234); // "1.234"
665
+ * </pre>
666
+ *
667
+ * @param {object} [oFormatOptions] The option object, which supports the following parameters.
668
+ * If no options are given, default values according to the type and locale settings are used.
669
+ * @param {int} [oFormatOptions.decimalPadding] Not supported.
670
+ * @param {int} [oFormatOptions.decimals] defines the number of decimal digits
671
+ * @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
672
+ * Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
673
+ * @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
674
+ * is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string)
675
+ * NaN, <code>null</code>, or 0.
676
+ * The 'format' and 'parse' functions are done in a symmetric way. For example, when this
677
+ * parameter is set to NaN, an empty string is parsed as NaN, and NaN is formatted as an empty
678
+ * string.
679
+ * @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
680
+ * it is different from the grouping size (e.g. Indian grouping)
681
+ * @param {boolean} [oFormatOptions.groupingEnabled=false] defines whether grouping is enabled
682
+ * (grouping separators are shown).
683
+ * <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
684
+ * a non-positive value.
685
+ * @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
686
+ * Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
687
+ * @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
688
+ * is <code>3</code>.
689
+ * <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
690
+ * @param {int} [oFormatOptions.maxFractionDigits=0] defines the maximum number of decimal digits
691
+ * @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
692
+ * If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
693
+ * @param {int} [oFormatOptions.minFractionDigits=0] defines the minimal number of decimal digits
694
+ * @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
695
+ * @param {string} [oFormatOptions.minusSign] defines the used minus symbol
696
+ * @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
697
+ * the string from the parse function in order to keep the precision for big numbers. Numbers
698
+ * in scientific notation are parsed back to standard notation. For example, "5e+3" is parsed
699
+ * to "5000".
700
+ * @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
701
+ * @param {string} [oFormatOptions.plusSign] defines the used plus symbol
702
+ * @param {int} [oFormatOptions.precision] <b>Note:</b> Only considered if the number format leads to a
703
+ * representation with decimal places, e.g. if the option <code>style: "short"</code> is set.
704
+ * The maximum number of digits in the formatted representation of a number; if the <code>precision</code> is
705
+ * less than the overall length of the number, its fractional part is truncated through rounding.
706
+ * As the <code>precision</code> only affects the rounding of a number, its integer part can retain more digits
707
+ * than defined by this parameter.
708
+ * <b>Example:</b> With a <code>precision</code> of 2 and <code>style: "short"</code>,
709
+ * <code>234567</code> is formatted to <code>"235K"</code>.
710
+ * <b>Note:</b> The formatted output may differ depending on locale.
711
+ * @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
712
+ * decimal digits except trailing zeros in case there are more decimals than the
713
+ * <code>maxFractionDigits</code> format option allows.
714
+ * If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
715
+ * @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=TOWARDS_ZERO]
716
+ * Specifies the rounding behavior for discarding the digits after the maximum fraction digits
717
+ * defined by <code>maxFractionDigits</code>.
718
+ * This can be assigned
719
+ * <ul>
720
+ * <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
721
+ * <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
722
+ * number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
723
+ * string based numbers are not rounded via this custom function.</li>
724
+ * </ul>
725
+ * @param {int} [oFormatOptions.shortDecimals] defines the number of decimal in the shortened format string. If this isn't specified, the 'decimals' options is used
726
+ * @param {int} [oFormatOptions.shortLimit] only use short number formatting for values above this limit
727
+ * @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the scale factor for 'short' or 'long' style format is generated. The generated scale factor is
728
+ * used for all numbers which are formatted with this format instance. This option has effect only when the option 'style' is set to 'short' or 'long'. This option is by default set
729
+ * with <code>undefined</code> which means the scale factor is selected automatically for each number being formatted.
730
+ * @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number. This option takes effect only when the 'style' options is set to either 'short' or 'long'.
731
+ * @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
732
+ * @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
733
+ * 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
734
+ * numbers are formatted into compact forms. When this option is set, the default value of the
735
+ * 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
736
+ * decimals, shortDecimals, or the 'precision' option itself.
737
+ * @param {sap.ui.core.Locale} [oLocale]
738
+ * The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
739
+ * used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
740
+ * @return {sap.ui.core.format.NumberFormat} integer instance of the NumberFormat
741
+ * @throws {Error} If the <code>oFormatOptions.decimalPadding</code> format option is provided
742
+ * @static
743
+ * @public
744
+ */
745
+ NumberFormat.getIntegerInstance = function (oFormatOptions, oLocale) {
746
+ NumberFormat.checkDecimalPadding(oFormatOptions, false);
747
+ const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
748
+ const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.INTEGER);
749
+ oFormat.oFormatOptions = extend({}, NumberFormat.oDefaultIntegerFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
750
+ oFormat.checkGroupingFormatOptions();
751
+ return oFormat;
752
+ };
753
+
754
+ /**
755
+ * Get a currency instance of the NumberFormat, which can be used for formatting.
756
+ *
757
+ * <p>
758
+ * This instance has HALF_AWAY_FROM_ZERO set as default rounding mode.
759
+ * Please set the roundingMode property in oFormatOptions to change the
760
+ * default value.
761
+ * </p>
762
+ *
763
+ * The currency instance supports locally defined custom currency exclusive to the created instance.
764
+ * The following example shows how to use custom currencies (e.g. for Bitcoins):
765
+ * <pre>
766
+ * var oFormat = NumberFormat.getCurrencyInstance({
767
+ * "currencyCode": false,
768
+ * "customCurrencies": {
769
+ * "BTC": {
770
+ * "symbol": "\u0243",
771
+ * "decimals": 3
772
+ * }
773
+ * }
774
+ * });
775
+ *
776
+ * oFormat.format(123.4567, "BTC"); // "Ƀ 123.457"
777
+ * </pre>
778
+ *
779
+ * As an alternative to using a fixed <code>symbol</code> for your custom currencies, you can also provide an ISO-Code.
780
+ * The provided ISO-Code will be used to look up the currency symbol in the global configuration,
781
+ * either defined in the CLDR or custom defined on the Format Settings (see
782
+ * {@link module:sap/base/i18n/Formatting.setCustomCurrencies Formatting.setCustomCurrencies},
783
+ * {@link module:sap/base/i18n/Formatting.addCustomCurrencies Formatting.addCustomCurrencies}).
784
+ *
785
+ * If no symbol is given at all, the custom currency key is used for formatting.
786
+ *
787
+ * <pre>
788
+ * var oFormat = NumberFormat.getCurrencyInstance({
789
+ * "currencyCode": false,
790
+ * "customCurrencies": {
791
+ * "MyDollar": {
792
+ * "isoCode": "USD",
793
+ * "decimals": 3
794
+ * },
795
+ * "Bitcoin": {
796
+ * "decimals": 2
797
+ * }
798
+ * }
799
+ * });
800
+ *
801
+ * // symbol looked up from global configuration
802
+ * oFormat.format(123.4567, "MyDollar"); // "$123.457"
803
+ *
804
+ * // no symbol available, custom currency key is rendered
805
+ * oFormat.format(777.888, "Bitcoin"); // "Bitcoin 777.89"
806
+ * </pre>
807
+ *
808
+ * @param {object} [oFormatOptions] The option object, which supports the following parameters.
809
+ * If no options are given, default values according to the type and locale settings are used.
810
+ * @param {boolean} [oFormatOptions.currencyCode=true] defines whether the currency is shown as
811
+ * a code in currency format. The currency symbol is displayed when this option is set to
812
+ * <code>false</code> and a symbol has been defined for the given currency code.
813
+ * @param {string} [oFormatOptions.currencyContext=standard] can be set either to 'standard'
814
+ * (the default value) or to 'accounting' for an accounting-specific currency display
815
+ * @param {Object<string,object>} [oFormatOptions.customCurrencies] defines a set of custom currencies exclusive to this NumberFormat instance.
816
+ * Custom currencies must not only consist of digits.
817
+ * If custom currencies are defined on the instance, no other currencies can be formatted and parsed by this instance.
818
+ * Globally available custom currencies can be added via the global configuration.
819
+ * See the above examples.
820
+ * See also {@link module:sap/base/i18n/Formatting.setCustomCurrencies Formatting.setCustomCurrencies} and
821
+ * {@link module:sap/base/i18n/Formatting.addCustomCurrencies Formatting.addCustomCurrencies}.
822
+ * @param {int} [oFormatOptions.decimalPadding]
823
+ * The target length of places after the decimal separator; if the number has fewer decimal places than given in
824
+ * this option, it is padded with whitespaces at the end up to the target length. An additional whitespace
825
+ * character for the decimal separator is added for a number without any decimals.
826
+ * <b>Note:</b> This format option is only allowed if the following conditions apply:
827
+ * <ul>
828
+ * <li>It has a value greater than 0.</li>
829
+ * <li>The <code>FormatOptions.showMeasure</code> format option is set to <code>false</code>.</li>
830
+ * <li>The <code>oFormatOptions.style</code> format option is <b>not</b> set to <code>"short"</code> or
831
+ * <code>"long"</code>.</li>
832
+ * </ul>
833
+ * @param {int} [oFormatOptions.decimals] defines the number of decimal digits
834
+ * @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
835
+ * Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
836
+ * @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
837
+ * is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string),
838
+ * NaN, <code>null</code>, or 0.
839
+ * The 'format' and 'parse' functions are done in a symmetric way. For example, when this
840
+ * parameter is set to NaN, an empty string is parsed as [NaN, undefined], and NaN is
841
+ * formatted as an empty string.
842
+ * @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
843
+ * it is different from the grouping size (e.g. Indian grouping)
844
+ * @param {boolean} [oFormatOptions.groupingEnabled=true] defines whether grouping is enabled
845
+ * (grouping separators are shown).
846
+ * <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
847
+ * a non-positive value.
848
+ * @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
849
+ * Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
850
+ * @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
851
+ * is <code>3</code>.
852
+ * <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
853
+ * @param {int} [oFormatOptions.maxFractionDigits=99] defines the maximum number of decimal digits
854
+ * @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
855
+ * If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
856
+ * @param {int} [oFormatOptions.minFractionDigits=0] Deprecated as of 1.130; this format option does not have
857
+ * an effect on currency formats since decimals can always be determined, either through the given format options,
858
+ * custom currencies or the CLDR
859
+ * @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
860
+ * @param {string} [oFormatOptions.minusSign] defines the used minus symbol
861
+ * @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
862
+ * the string from the parse function in order to keep the precision for big numbers. Numbers
863
+ * in scientific notation are parsed back to standard notation. For example, "5e-3" is parsed
864
+ * to "0.005".
865
+ * @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
866
+ * @param {string} [oFormatOptions.plusSign] defines the used plus symbol
867
+ * @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
868
+ * decimal digits except trailing zeros in case there are more decimals than the
869
+ * <code>maxFractionDigits</code> format option allows.
870
+ * If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
871
+ * @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=HALF_AWAY_FROM_ZERO]
872
+ * Specifies the rounding behavior for discarding the digits after the maximum fraction digits
873
+ * defined by <code>maxFractionDigits</code>.
874
+ * This can be assigned
875
+ * <ul>
876
+ * <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
877
+ * <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
878
+ * number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
879
+ * string based numbers are not rounded via this custom function.</li>
880
+ * </ul>
881
+ * @param {int} [oFormatOptions.shortDecimals] defines the number of decimal in the shortened format string. If this isn't specified, the 'decimals' options is used
882
+ * @param {int} [oFormatOptions.shortLimit] only use short number formatting for values above this limit
883
+ * @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the scale factor for 'short' or 'long' style format is generated. The generated scale factor is
884
+ * used for all numbers which are formatted with this format instance. This option has effect only when the option 'style' is set to 'short' or 'long'. This option is by default set
885
+ * with <code>undefined</code> which means the scale factor is selected automatically for each number being formatted.
886
+ * @param {boolean} [oFormatOptions.showMeasure=true] defines whether the currency code/symbol is shown in the formatted string,
887
+ * e.g. true: "1.00 EUR", false: "1.00" for locale "en"
888
+ * If both <code>showMeasure</code> and <code>showNumber</code> are false, an empty string is returned
889
+ * @param {boolean} [oFormatOptions.showNumber=true] defines whether the number is shown as part of the result string,
890
+ * e.g. 1 EUR for locale "en"
891
+ * <code>NumberFormat.getCurrencyInstance({showNumber:true}).format(1, "EUR"); // "1.00 EUR"</code>
892
+ * <code>NumberFormat.getCurrencyInstance({showNumber:false}).format(1, "EUR"); // "EUR"</code>
893
+ * If both <code>showMeasure</code> and <code>showNumber</code> are false, an empty string is returned
894
+ * @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number.
895
+ * This option takes effect only when the 'style' options is set to either 'short' or 'long'.
896
+ * @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
897
+ * @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
898
+ * 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
899
+ * numbers are formatted into the 'short' form only. When this option is set, the default value of the
900
+ * 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
901
+ * decimals, shortDecimals, or the 'precision' option itself.
902
+ * @param {boolean} [oFormatOptions.trailingCurrencyCode] overrides the global configuration
903
+ * value {@link module:sap/base/i18n/Formatting.getTrailingCurrencyCode Formatting.getTrailingCurrencyCode},
904
+ * which has a default value of <code>true</>.
905
+ * This is ignored if <code>oFormatOptions.currencyCode</code> is set to <code>false</code>,
906
+ * or if <code>oFormatOptions.pattern</code> is supplied.
907
+ * @param {sap.ui.core.Locale} [oLocale]
908
+ * The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
909
+ * used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
910
+ * @return {sap.ui.core.format.NumberFormat} currency instance of the NumberFormat
911
+ * @throws {Error} If the <code>oFormatOptions.decimalPadding</code> is set but is not allowed
912
+ * @static
913
+ * @public
914
+ */
915
+ NumberFormat.getCurrencyInstance = function (oFormatOptions, oLocale) {
916
+ NumberFormat.checkDecimalPadding(oFormatOptions, true, true);
917
+ const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
918
+ const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.CURRENCY);
919
+ oFormat.oFormatOptions = extend({}, NumberFormat.oDefaultCurrencyFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
920
+
921
+ // The format option "trailingCurrencyCode" is influenced by other options, such as pattern, currencyCode,
922
+ // global config
923
+ // Therefore set it manually without modifying the original oFormatOptions.
924
+ // E.g. the "pattern" option would overwrite this option, even if the "trailingCurrencyCode" option is set
925
+ // oFormatOptions.pattern = "###"
926
+ // oFormatOptions.trailingCurrencyCode = true
927
+ // ->
928
+ // oFormatOptions.trailingCurrencyCode = false
929
+ oFormat.oFormatOptions.trailingCurrencyCode = oFormat.showTrailingCurrencyCode();
930
+ oFormat._defineCustomCurrencySymbols();
931
+ if (oFormat.oFormatOptions.style === "long") {
932
+ oFormat.oFormatOptions.style = "short";
933
+ }
934
+ oFormat.checkGroupingFormatOptions();
935
+ return oFormat;
936
+ };
937
+
938
+ /**
939
+ * Get a unit instance of the NumberFormat, which can be used for formatting units.
940
+ *
941
+ * <p>
942
+ * This instance has HALF_AWAY_FROM_ZERO set as default rounding mode.
943
+ * Please set the roundingMode property in oFormatOptions to change the
944
+ * default value.
945
+ * </p>
946
+ *
947
+ * @param {object} [oFormatOptions] The option object, which supports the following parameters.
948
+ * If no options are given, default values according to the type and locale settings are used.
949
+ * @param {array} [oFormatOptions.allowedUnits] defines the allowed units for formatting and parsing, e.g. ["size-meter", "volume-liter", ...]
950
+ * @param {Object<string,object>} [oFormatOptions.customUnits] defines a set of custom units, e.g.
951
+ * {"electric-inductance": {
952
+ * "displayName": "henry",
953
+ * "unitPattern-count-one": "{0} H",
954
+ * "unitPattern-count-other": "{0} H",
955
+ * "perUnitPattern": "{0}/H",
956
+ * "decimals": 2,
957
+ * "precision": 4
958
+ * }}
959
+ * @param {int} [oFormatOptions.decimalPadding]
960
+ * The target length of places after the decimal separator; if the number has fewer decimal places than given in
961
+ * this option, it is padded with whitespaces at the end up to the target length. An additional whitespace
962
+ * character for the decimal separator is added for a number without any decimals.
963
+ * <b>Note:</b> This format option is only allowed if the following conditions apply:
964
+ * <ul>
965
+ * <li>It has a value greater than 0.</li>
966
+ * <li>The <code>FormatOptions.showMeasure</code> format option is set to <code>false</code>.</li>
967
+ * <li>The <code>oFormatOptions.style</code> format option is <b>not</b> set to <code>"short"</code> or
968
+ * <code>"long"</code>.</li>
969
+ * </ul>
970
+ * @param {int} [oFormatOptions.decimals] defines the number of decimal digits
971
+ * @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
972
+ * Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
973
+ * @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
974
+ * is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string),
975
+ * NaN, <code>null</code>, or 0.
976
+ * The 'format' and 'parse' functions are done in a symmetric way. For example, when this
977
+ * parameter is set to NaN, an empty string is parsed as [NaN, undefined], and NaN is
978
+ * formatted as an empty string.
979
+ * @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
980
+ * it is different from the grouping size (e.g. Indian grouping)
981
+ * @param {boolean} [oFormatOptions.groupingEnabled=true] defines whether grouping is enabled
982
+ * (grouping separators are shown).
983
+ * <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
984
+ * a non-positive value.
985
+ * @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
986
+ * Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
987
+ * @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
988
+ * is <code>3</code>.
989
+ * <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
990
+ * @param {int} [oFormatOptions.maxFractionDigits=99] defines the maximum number of decimal digits
991
+ * @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
992
+ * If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
993
+ * @param {int} [oFormatOptions.minFractionDigits=0] defines the minimal number of decimal digits
994
+ * @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
995
+ * @param {string} [oFormatOptions.minusSign] defines the used minus symbol
996
+ * @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
997
+ * the string from the parse function in order to keep the precision for big numbers. Numbers
998
+ * in scientific notation are parsed back to standard notation. For example, "5e-3" is parsed
999
+ * to "0.005".
1000
+ * @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
1001
+ * @param {string} [oFormatOptions.plusSign] defines the used plus symbol
1002
+ * @param {int} [oFormatOptions.precision] The maximum number of digits in the formatted representation of a number;
1003
+ * if the <code>precision</code> is less than the overall length of the number, its fractional part is truncated
1004
+ * through rounding. As the <code>precision</code> only affects the rounding of a number, its integer part can
1005
+ * retain more digits than defined by this parameter.
1006
+ * <b>Example:</b> With a <code>precision</code> of 2, the parameters <code>"234.567", "mass-kilogram"</code> are
1007
+ * formatted to <code>"235 kg"</code>.
1008
+ * <b>Note:</b> The formatted output may differ depending on locale.
1009
+ * @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
1010
+ * decimal digits except trailing zeros in case there are more decimals than the
1011
+ * <code>maxFractionDigits</code> format option allows.
1012
+ * If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
1013
+ * @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=HALF_AWAY_FROM_ZERO]
1014
+ * Specifies the rounding behavior for discarding the digits after the maximum fraction digits
1015
+ * defined by <code>maxFractionDigits</code>.
1016
+ * This can be assigned
1017
+ * <ul>
1018
+ * <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
1019
+ * <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
1020
+ * number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
1021
+ * string based numbers are not rounded via this custom function.</li>
1022
+ * </ul>
1023
+ * @param {int} [oFormatOptions.shortDecimals] defines the number of decimals in the shortened
1024
+ * format string. If this option isn't specified, the 'decimals' option is used instead.
1025
+ * @param {int} [oFormatOptions.shortLimit] defines a limit above which only short number formatting is used
1026
+ * @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the
1027
+ * scale factor for the 'short' or 'long' style format is generated. The generated scale
1028
+ * factor is used for all numbers which are formatted with this format instance. This option
1029
+ * only takes effect when the 'style' option is set to 'short' or 'long'. This option is
1030
+ * set to <code>undefined</code> by default, which means that the scale factor is selected
1031
+ * automatically for each number being formatted.
1032
+ * @param {boolean} [oFormatOptions.showMeasure=true] defines whether the unit of measure is shown in the formatted string,
1033
+ * e.g. for input 1 and "duration-day" true: "1 day", false: "1".
1034
+ * If both <code>showMeasure</code> and <code>showNumber</code> are false, an empty string is returned
1035
+ * @param {boolean} [oFormatOptions.showNumber=true] defines whether the number is shown as part of the result string,
1036
+ * e.g. 1 day for locale "en"
1037
+ * <code>NumberFormat.getUnitInstance({showNumber:true}).format(1, "duration-day"); // "1 day"</code>
1038
+ * <code>NumberFormat.getUnitInstance({showNumber:false}).format(1, "duration-day"); // "day"</code>
1039
+ * e.g. 2 days for locale "en"
1040
+ * <code>NumberFormat.getUnitInstance({showNumber:true}).format(2, "duration-day"); // "2 days"</code>
1041
+ * <code>NumberFormat.getUnitInstance({showNumber:false}).format(2, "duration-day"); // "days"</code>
1042
+ * If both <code>showMeasure</code> and <code>showNumber</code> are false, an empty string is returned
1043
+ * @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number. This option takes effect only when the 'style' options is set to either 'short' or 'long'.
1044
+ * @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
1045
+ * @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
1046
+ * 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
1047
+ * numbers are formatted into compact forms. When this option is set, the default value of the
1048
+ * 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
1049
+ * decimals, shortDecimals, or the 'precision' option itself.
1050
+ * @param {sap.ui.core.Locale} [oLocale]
1051
+ * The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
1052
+ * used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
1053
+ * @return {sap.ui.core.format.NumberFormat} unit instance of the NumberFormat
1054
+ * @throws {Error} If the <code>oFormatOptions.decimalPadding</code> is set but is not allowed
1055
+ * @static
1056
+ * @public
1057
+ */
1058
+ NumberFormat.getUnitInstance = function (oFormatOptions, oLocale) {
1059
+ NumberFormat.checkDecimalPadding(oFormatOptions, true, true);
1060
+ const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
1061
+ const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.UNIT);
1062
+ oFormat.oFormatOptions = extend({}, NumberFormat.oDefaultUnitFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
1063
+ oFormat.checkGroupingFormatOptions();
1064
+ return oFormat;
1065
+ };
1066
+
1067
+ /**
1068
+ * Get a percent instance of the NumberFormat, which can be used for formatting.
1069
+ *
1070
+ * <p>
1071
+ * This instance has HALF_AWAY_FROM_ZERO set as default rounding mode.
1072
+ * Please set the roundingMode property in oFormatOptions to change the
1073
+ * default value.
1074
+ * </p>
1075
+ *
1076
+ * @param {object} [oFormatOptions] The option object, which supports the following parameters.
1077
+ * If no options are given, default values according to the type and locale settings are used.
1078
+ * @param {int} [oFormatOptions.decimalPadding] Not supported.
1079
+ * @param {int} [oFormatOptions.decimals] defines the number of decimal digits
1080
+ * @param {string} [oFormatOptions.decimalSeparator] defines the character used as decimal separator.
1081
+ * Note: <code>decimalSeparator</code> must always be different from <code>groupingSeparator</code>.
1082
+ * @param {null|number|string} [oFormatOptions.emptyString=NaN] since 1.30.0 defines what an empty string
1083
+ * is parsed as, and what is formatted as an empty string. The allowed values are "" (empty string),
1084
+ * NaN, <code>null</code>, or 0.
1085
+ * The 'format' and 'parse' functions are done in a symmetric way. For example, when this
1086
+ * parameter is set to NaN, an empty string is parsed as NaN, and NaN is formatted as an empty
1087
+ * string.
1088
+ * @param {int} [oFormatOptions.groupingBaseSize=3] defines the grouping base size in digits if
1089
+ * it is different from the grouping size (e.g. Indian grouping)
1090
+ * @param {boolean} [oFormatOptions.groupingEnabled=true] defines whether grouping is enabled
1091
+ * (grouping separators are shown).
1092
+ * <b>Note:</b> Grouping is disabled if the <code>groupingSize</code> format option is set to
1093
+ * a non-positive value.
1094
+ * @param {string} [oFormatOptions.groupingSeparator] defines the character used as grouping separator.
1095
+ * Note: <code>groupingSeparator</code> must always be different from <code>decimalSeparator</code>.
1096
+ * @param {int} [oFormatOptions.groupingSize=3] defines the grouping size in digits; the default
1097
+ * is <code>3</code>.
1098
+ * <b>Note:</b> If this format option is set to a non-positive value, grouping will be disabled entirely.
1099
+ * @param {int} [oFormatOptions.maxFractionDigits=99] defines the maximum number of decimal digits
1100
+ * @param {int} [oFormatOptions.maxIntegerDigits=99] defines the maximum number of non-decimal digits.
1101
+ * If the number exceeds this maximum, e.g. 1e+120, "?" characters are shown instead of digits.
1102
+ * @param {int} [oFormatOptions.minFractionDigits=0] defines the minimal number of decimal digits
1103
+ * @param {int} [oFormatOptions.minIntegerDigits=1] defines the minimal number of non-decimal digits
1104
+ * @param {string} [oFormatOptions.minusSign] defines the used minus symbol
1105
+ * @param {boolean} [oFormatOptions.parseAsString=false] since 1.28.2 defines whether to output
1106
+ * the string from the parse function in order to keep the precision for big numbers. Numbers
1107
+ * in scientific notation are parsed back to standard notation. For example, "5e-3" is parsed
1108
+ * to "0.005".
1109
+ * @param {string} [oFormatOptions.pattern] CLDR number pattern which is used to format the number
1110
+ * @param {string} [oFormatOptions.percentSign] defines the used percent symbol
1111
+ * @param {string} [oFormatOptions.plusSign] defines the used plus symbol
1112
+ * @param {int} [oFormatOptions.precision] The maximum number of digits in the formatted representation of a number;
1113
+ * if the <code>precision</code> is less than the overall length of the number, its fractional part is truncated
1114
+ * through rounding. As the <code>precision</code> only affects the rounding of a number, its integer part
1115
+ * can retain more digits than defined by this parameter.
1116
+ * <b>Example:</b> With a <code>precision</code> of 2, <code>234.567</code> is formatted to
1117
+ * <code>"23,457%"</code>.
1118
+ * <b>Note:</b> The formatted output may differ depending on locale.
1119
+ * @param {boolean} [oFormatOptions.preserveDecimals=false] Whether {@link #format} preserves
1120
+ * decimal digits except trailing zeros in case there are more decimals than the
1121
+ * <code>maxFractionDigits</code> format option allows.
1122
+ * If decimals are not preserved, the formatted number is rounded to <code>maxFractionDigits</code>.
1123
+ * @param {sap.ui.core.format.NumberFormat.RoundingMode} [oFormatOptions.roundingMode=HALF_AWAY_FROM_ZERO]
1124
+ * Specifies the rounding behavior for discarding the digits after the maximum fraction digits
1125
+ * defined by <code>maxFractionDigits</code>.
1126
+ * This can be assigned
1127
+ * <ul>
1128
+ * <li>by value in {@link sap.ui.core.format.NumberFormat.RoundingMode RoundingMode},</li>
1129
+ * <li>via a function that is used for rounding the number and takes two parameters: the number itself, and the
1130
+ * number of decimal digits that should be reserved. <b>Using a function is deprecated since 1.121.0</b>;
1131
+ * string based numbers are not rounded via this custom function.</li>
1132
+ * </ul>
1133
+ * @param {int} [oFormatOptions.shortDecimals] defines the number of decimal in the shortened format string. If this isn't specified, the 'decimals' options is used
1134
+ * @param {int} [oFormatOptions.shortLimit] only use short number formatting for values above this limit
1135
+ * @param {int} [oFormatOptions.shortRefNumber] since 1.40 specifies a number from which the scale factor for 'short' or 'long' style format is generated. The generated scale factor is
1136
+ * used for all numbers which are formatted with this format instance. This option has effect only when the option 'style' is set to 'short' or 'long'. This option is by default set
1137
+ * with <code>undefined</code> which means the scale factor is selected automatically for each number being formatted.
1138
+ * @param {boolean} [oFormatOptions.showScale=true] since 1.40 specifies whether the scale factor is shown in the formatted number. This option takes effect only when the 'style' options is set to either 'short' or 'long'.
1139
+ * @param {boolean} [oFormatOptions.strictGroupingValidation=false] whether the positions of grouping separators are validated. Space characters used as grouping separators are not validated.
1140
+ * @param {string} [oFormatOptions.style=standard] defines the style of format. Valid values are
1141
+ * 'short, 'long' or 'standard' (based on the CLDR decimalFormat). When set to 'short' or 'long',
1142
+ * numbers are formatted into compact forms. When this option is set, the default value of the
1143
+ * 'precision' option is set to 2. This can be changed by setting either min/maxFractionDigits,
1144
+ * decimals, shortDecimals, or the 'precision' option itself.
1145
+ * @param {sap.ui.core.Locale} [oLocale]
1146
+ * The locale to get the formatter for; if no locale is given, a locale for the currently configured language is
1147
+ * used; see {@link module:sap/base/i18n/Formatting.getLanguageTag Formatting.getLanguageTag}
1148
+ * @return {sap.ui.core.format.NumberFormat} percentage instance of the NumberFormat
1149
+ * @throws {Error} If the <code>oFormatOptions.decimalPadding</code> format option is provided
1150
+ * @static
1151
+ * @public
1152
+ */
1153
+ NumberFormat.getPercentInstance = function (oFormatOptions, oLocale) {
1154
+ NumberFormat.checkDecimalPadding(oFormatOptions, false);
1155
+ const oFormat = NumberFormat.createInstance(oFormatOptions, oLocale);
1156
+ const oLocaleFormatOptions = oFormat.getLocaleFormatOptions(mNumberType.PERCENT);
1157
+ oFormat.oFormatOptions = extend({}, NumberFormat.oDefaultPercentFormat, oLocaleFormatOptions, oFormat.oOriginalFormatOptions);
1158
+ oFormat.checkGroupingFormatOptions();
1159
+ return oFormat;
1160
+ };
1161
+
1162
+ /**
1163
+ * Create an instance of the NumberFormat.
1164
+ *
1165
+ * @param {object} [oFormatOptions] Object which defines the format options
1166
+ * @return {sap.ui.core.format.NumberFormat} integer instance of the NumberFormat
1167
+ * @static
1168
+ * @private
1169
+ */
1170
+ NumberFormat.createInstance = function (oFormatOptions, oLocale) {
1171
+ var oFormat = Object.create(this.prototype),
1172
+ oPatternOptions;
1173
+ if (oFormatOptions instanceof Locale) {
1174
+ oLocale = oFormatOptions;
1175
+ oFormatOptions = undefined;
1176
+ }
1177
+ if (!oLocale) {
1178
+ oLocale = new Locale(Formatting.getLanguageTag());
1179
+ }
1180
+ oFormat.oLocale = oLocale;
1181
+ oFormat.oLocaleData = LocaleData.getInstance(oLocale);
1182
+ oFormat.oOriginalFormatOptions = oFormatOptions;
1183
+
1184
+ // If a pattern is defined in the format option, parse it and add options
1185
+ if (oFormatOptions) {
1186
+ if (oFormatOptions.pattern) {
1187
+ oPatternOptions = this.parseNumberPattern(oFormatOptions.pattern);
1188
+ Object.keys(oPatternOptions).forEach(function (sName) {
1189
+ oFormatOptions[sName] = oPatternOptions[sName];
1190
+ });
1191
+ }
1192
+ if (oFormatOptions.emptyString !== undefined) {
1193
+ assert(oFormatOptions.emptyString === "" || oFormatOptions.emptyString === 0 || oFormatOptions.emptyString === null
1194
+ // eslint-disable-next-line no-self-compare -- check if it's NaN (only NaN doesn't equal to itself)
1195
+ || oFormatOptions.emptyString !== oFormatOptions.emptyString, "The format option 'emptyString' must be either '', 0, null, or NaN");
1196
+ }
1197
+ }
1198
+ return oFormat;
1199
+ };
1200
+
1201
+ /**
1202
+ * Returns a default unit format/parse pattern for the given unit short name.
1203
+ * The returned pattern can then be used for custom units, for example as a <code>unitPattern-count-other</code> pattern.
1204
+ * The <code>unitPattern-count-other</code> pattern is then used by NumberFormat instances as a fallback in case
1205
+ * no other patterns are defined, see the below example:
1206
+ *
1207
+ * <pre>
1208
+ * var oFormat = NumberFormat.getUnitInstance({
1209
+ * "customUnits": {
1210
+ * "myUnit": {
1211
+ * "unitPattern-count-other": NumberFormat.getDefaultUnitPattern("Bottles"); // returns "{0} Bottles"
1212
+ * }
1213
+ * }
1214
+ * });
1215
+ * oFormat.format(1234, "myUnit"); // returns "1.234,00 Bottles"
1216
+ * </pre>
1217
+ *
1218
+ * @param {string} sShortName the short name of the unit used in the created pattern
1219
+ * @returns {string} a pattern, which can be used for formatting and parsing a custom unit of measure
1220
+ * @private
1221
+ * @ui5-restricted sap.ui.model.odata.type
1222
+ */
1223
+ NumberFormat.getDefaultUnitPattern = function (sShortName) {
1224
+ return "{0}\u00a0" + sShortName;
1225
+ };
1226
+
1227
+ /**
1228
+ * Gets the default locale-dependent format options for the given number format type.
1229
+ *
1230
+ * @param {"integer"|"float"|"currency"|"unit"|"percent"} sType
1231
+ * The number format type
1232
+ * @returns {object} The default locale-dependent format options for the given number format type
1233
+ *
1234
+ * @private
1235
+ */
1236
+ NumberFormat.prototype.getLocaleFormatOptions = function (sType) {
1237
+ const oLocaleData = this.oLocaleData;
1238
+ let sNumberPattern;
1239
+ let sContext;
1240
+ switch (sType) {
1241
+ case mNumberType.PERCENT:
1242
+ sNumberPattern = oLocaleData.getPercentPattern();
1243
+ break;
1244
+ case mNumberType.CURRENCY:
1245
+ sContext = this.oOriginalFormatOptions?.currencyContext || NumberFormat.oDefaultCurrencyFormat.style;
1246
+ // prepend "sap-" to pattern params to load (context and short)
1247
+ if (this.showTrailingCurrencyCode()) {
1248
+ sContext = "sap-" + sContext;
1249
+ }
1250
+ sNumberPattern = oLocaleData.getCurrencyPattern(sContext);
1251
+ break;
1252
+ default:
1253
+ sNumberPattern = oLocaleData.getDecimalPattern();
1254
+ }
1255
+ const oLocaleFormatOptions = NumberFormat.parseNumberPattern(sNumberPattern);
1256
+ oLocaleFormatOptions.plusSign = oLocaleData.getNumberSymbol("plusSign");
1257
+ oLocaleFormatOptions.minusSign = oLocaleData.getNumberSymbol("minusSign");
1258
+ oLocaleFormatOptions.decimalSeparator = oLocaleData.getNumberSymbol("decimal");
1259
+ oLocaleFormatOptions.groupingSeparator = oLocaleData.getNumberSymbol("group");
1260
+ oLocaleFormatOptions.percentSign = oLocaleData.getNumberSymbol("percentSign");
1261
+ oLocaleFormatOptions.pattern = sNumberPattern;
1262
+
1263
+ // Some options need to be overridden to stay compatible with the formatting defaults
1264
+ // before pattern parsing was added to the NumberFormat
1265
+ switch (sType) {
1266
+ case mNumberType.INTEGER:
1267
+ // No fraction digits and no grouping for integer values
1268
+ oLocaleFormatOptions.minFractionDigits = 0;
1269
+ oLocaleFormatOptions.maxFractionDigits = 0;
1270
+ oLocaleFormatOptions.groupingEnabled = false;
1271
+ break;
1272
+ case mNumberType.CURRENCY:
1273
+ // reset the iMin/MaxFractionDigits because the extracted info from the pattern doesn't contain the currency specific info.
1274
+ oLocaleFormatOptions.minFractionDigits = undefined;
1275
+ oLocaleFormatOptions.maxFractionDigits = undefined;
1276
+ break;
1277
+ default:
1278
+ // cases: mNumberType.UNIT, mNumberType.FLOAT and mNumberType.PERCENT
1279
+ // Unlimited fraction digits
1280
+ oLocaleFormatOptions.minFractionDigits = 0;
1281
+ oLocaleFormatOptions.maxFractionDigits = 99;
1282
+ }
1283
+ return oLocaleFormatOptions;
1284
+ };
1285
+
1286
+ /**
1287
+ * Get digit information from number format.
1288
+ *
1289
+ * @static
1290
+ */
1291
+ NumberFormat.parseNumberPattern = function (sFormatString) {
1292
+ var iMinIntegerDigits = 0,
1293
+ iMinFractionDigits = 0,
1294
+ iMaxFractionDigits = 0,
1295
+ bGroupingEnabled = false,
1296
+ iGroupSize = 0,
1297
+ iBaseGroupSize = 0,
1298
+ iSeparatorPos = sFormatString.indexOf(";"),
1299
+ mSection = {
1300
+ Integer: 0,
1301
+ Fraction: 1
1302
+ },
1303
+ iSection = mSection.Integer;
1304
+
1305
+ // The sFormatString can be ¤#,##0.00;(¤#,##0.00). If the whole string is parsed, the wrong
1306
+ // iMinFractionDigits and iMaxFractionDigits are wrong.
1307
+ // Only the sub string before ';' is taken into consideration.
1308
+ if (iSeparatorPos !== -1) {
1309
+ sFormatString = sFormatString.substring(0, iSeparatorPos);
1310
+ }
1311
+ for (var i = 0; i < sFormatString.length; i++) {
1312
+ var sCharacter = sFormatString[i];
1313
+ switch (sCharacter) {
1314
+ case ",":
1315
+ if (bGroupingEnabled) {
1316
+ iGroupSize = iBaseGroupSize;
1317
+ iBaseGroupSize = 0;
1318
+ }
1319
+ bGroupingEnabled = true;
1320
+ break;
1321
+ case ".":
1322
+ iSection = mSection.Fraction;
1323
+ break;
1324
+ case "0":
1325
+ if (iSection === mSection.Integer) {
1326
+ iMinIntegerDigits++;
1327
+ if (bGroupingEnabled) {
1328
+ iBaseGroupSize++;
1329
+ }
1330
+ } else {
1331
+ iMinFractionDigits++;
1332
+ iMaxFractionDigits++;
1333
+ }
1334
+ break;
1335
+ case "#":
1336
+ if (iSection === mSection.Integer) {
1337
+ if (bGroupingEnabled) {
1338
+ iBaseGroupSize++;
1339
+ }
1340
+ } else {
1341
+ iMaxFractionDigits++;
1342
+ }
1343
+ break;
1344
+ }
1345
+ }
1346
+ if (!iGroupSize) {
1347
+ iGroupSize = iBaseGroupSize;
1348
+ iBaseGroupSize = 0;
1349
+ }
1350
+ return {
1351
+ minIntegerDigits: iMinIntegerDigits,
1352
+ minFractionDigits: iMinFractionDigits,
1353
+ maxFractionDigits: iMaxFractionDigits,
1354
+ groupingEnabled: bGroupingEnabled,
1355
+ groupingSize: iGroupSize,
1356
+ groupingBaseSize: iBaseGroupSize
1357
+ };
1358
+ };
1359
+
1360
+ /**
1361
+ * Compiles a map <code>this.mKnownCurrencySymbols</code>
1362
+ * of all custom currency symbols. Symbols are either defined in
1363
+ * the custom currency object itself, or are looked up on the
1364
+ * LocaleData in case an ISO Code is given.
1365
+ *
1366
+ * It also checks if there are duplicated symbols defined,
1367
+ * which lead to an ambiguous parse result.
1368
+ *
1369
+ * In case there are custom currencies defined on instance level,
1370
+ * it also compiles a map <code>this.mKnownCurrencyCodes</code>
1371
+ * of custom currency codes.
1372
+ *
1373
+ * The function is only used by the Currency formatting.
1374
+ * @private
1375
+ */
1376
+ NumberFormat.prototype._defineCustomCurrencySymbols = function () {
1377
+ var oOptions = this.oFormatOptions;
1378
+ var mCurrencySymbols = this.oLocaleData.getCurrencySymbols();
1379
+ var fnFindDuplicates = function (mSymbols, mResult) {
1380
+ var aUniqueSymbols = [];
1381
+ var sSymbol;
1382
+ for (var sKey in mSymbols) {
1383
+ sSymbol = mSymbols[sKey];
1384
+ if (aUniqueSymbols.indexOf(sSymbol) === -1) {
1385
+ aUniqueSymbols.push(sSymbol);
1386
+ } else if (sSymbol !== undefined) {
1387
+ // Duplicated symbol found
1388
+ mResult[sSymbol] = true;
1389
+ Log.error("Symbol '" + sSymbol + "' is defined multiple times in custom currencies.", undefined, "NumberFormat");
1390
+ }
1391
+ }
1392
+ };
1393
+
1394
+ // process custom currencies on instance-level
1395
+ if (oOptions.customCurrencies && typeof oOptions.customCurrencies === "object") {
1396
+ this.mKnownCurrencySymbols = {};
1397
+ this.mKnownCurrencyCodes = {};
1398
+
1399
+ // get all relevant symbols for custom currencies
1400
+ Object.keys(oOptions.customCurrencies).forEach(function (sKey) {
1401
+ if (oOptions.customCurrencies[sKey].symbol) {
1402
+ this.mKnownCurrencySymbols[sKey] = oOptions.customCurrencies[sKey].symbol;
1403
+ } else {
1404
+ // if no symbol is defined, we make a look up into the locale data with the given isoCode
1405
+ var sIsoCode = oOptions.customCurrencies[sKey].isoCode;
1406
+ if (sIsoCode) {
1407
+ this.mKnownCurrencySymbols[sKey] = mCurrencySymbols[sIsoCode];
1408
+ }
1409
+ }
1410
+
1411
+ // In case no symbol is found during parsing,
1412
+ // we take the custom currency key itself
1413
+ this.mKnownCurrencyCodes[sKey] = sKey;
1414
+ }.bind(this));
1415
+ } else {
1416
+ // find duplicated symbols in global config/CLDR
1417
+ // mCurrencySymbols
1418
+ this.mKnownCurrencySymbols = mCurrencySymbols;
1419
+ this.mKnownCurrencyCodes = this.oLocaleData.getCustomCurrencyCodes();
1420
+ }
1421
+
1422
+ // Find duplicated symbols defined in custom currencies
1423
+ this.mDuplicatedSymbols = {};
1424
+ fnFindDuplicates(this.mKnownCurrencySymbols, this.mDuplicatedSymbols);
1425
+ };
1426
+
1427
+ /**
1428
+ * Removes trailing zero decimals
1429
+ * @param {string} sNumber the number, e.g. "1.23000"
1430
+ * @param {number} minDecimalsPreserved the minimum decimals preserved, e.g. 3
1431
+ * @returns {string} the number with stripped trailing zero decimals, e.g. "1.230"
1432
+ */
1433
+ function stripTrailingZeroDecimals(sNumber, minDecimalsPreserved) {
1434
+ if (sNumber.indexOf(".") >= 0 && sNumber.endsWith("0")) {
1435
+ var iFractionDigitsLength = sNumber.length - sNumber.lastIndexOf(".") - 1;
1436
+ var iFractionsToRemove = iFractionDigitsLength - minDecimalsPreserved;
1437
+ if (iFractionsToRemove > 0) {
1438
+ while (sNumber.endsWith("0") && iFractionsToRemove-- > 0) {
1439
+ sNumber = sNumber.substring(0, sNumber.length - 1);
1440
+ }
1441
+ if (sNumber.endsWith(".")) {
1442
+ sNumber = sNumber.substring(0, sNumber.length - 1);
1443
+ }
1444
+ }
1445
+ }
1446
+ return sNumber;
1447
+ }
1448
+
1449
+ /**
1450
+ * Applies the grouping to the given integer part and returns it.
1451
+ *
1452
+ * @param {string} sIntegerPart
1453
+ * A string with the integer value, e.g. "1234567"
1454
+ * @param {object} oOptions
1455
+ * The format options
1456
+ * @param {int} oOptions.groupingBaseSize
1457
+ * The grouping base size in digits if it is different from the grouping size (e.g. Indian grouping)
1458
+ * @param {string} oOptions.groupingSeparator
1459
+ * The character used as grouping separator
1460
+ * @param {int} oOptions.groupingSize
1461
+ * The grouping size in digits
1462
+ * @returns {string}
1463
+ * The integer part with grouping, e.g. "1.234.567" for locale de-DE
1464
+ * @private
1465
+ */
1466
+ function applyGrouping(sIntegerPart, oOptions) {
1467
+ var iGroupSize = oOptions.groupingSize,
1468
+ iBaseGroupSize = oOptions.groupingBaseSize || iGroupSize,
1469
+ iLength = sIntegerPart.length,
1470
+ iPosition = Math.max(iLength - iBaseGroupSize, 0) % iGroupSize || iGroupSize,
1471
+ sGroupedIntegerPart = sIntegerPart.slice(0, iPosition);
1472
+ while (iLength - iPosition >= iBaseGroupSize) {
1473
+ sGroupedIntegerPart += oOptions.groupingSeparator;
1474
+ sGroupedIntegerPart += sIntegerPart.slice(iPosition, iPosition + iGroupSize);
1475
+ iPosition += iGroupSize;
1476
+ }
1477
+ sGroupedIntegerPart += sIntegerPart.slice(iPosition, iLength);
1478
+ return sGroupedIntegerPart;
1479
+ }
1480
+
1481
+ /**
1482
+ * Format a number according to the given format options.
1483
+ *
1484
+ * @param {number|string|array} vValue
1485
+ * The number to format as a number or a string, such as <code>1234.45</code> or <code>"-1234.45"</code>, or an
1486
+ * array which contains both the number to format as a number or a string and the <code>sMeasure</code> parameter
1487
+ * @param {string} [sMeasure]
1488
+ * An optional unit which has an impact on formatting currencies and units
1489
+ * @returns {string}
1490
+ * The formatted value
1491
+ * @public
1492
+ */
1493
+ NumberFormat.prototype.format = function (vValue, sMeasure) {
1494
+ if (Array.isArray(vValue)) {
1495
+ sMeasure = vValue[1];
1496
+ vValue = vValue[0];
1497
+ }
1498
+ var sIntegerPart = "",
1499
+ sFractionPart = "",
1500
+ sGroupedIntegerPart = "",
1501
+ sResult = "",
1502
+ sNumber = "",
1503
+ sPattern = "",
1504
+ bNegative = vValue < 0,
1505
+ iDotPos = -1,
1506
+ oOptions = Object.assign({}, this.oFormatOptions),
1507
+ oOrigOptions = this.oOriginalFormatOptions,
1508
+ bIndianCurrency = oOptions.type === mNumberType.CURRENCY && sMeasure === "INR" && this.oLocale.getLanguage() === "en" && this.oLocale.getRegion() === "IN",
1509
+ oShortFormat,
1510
+ nShortRefNumber,
1511
+ sPluralCategory,
1512
+ mUnitPatterns,
1513
+ sLookupMeasure,
1514
+ bValueIsNullOrUndefined = vValue === undefined || vValue === null;
1515
+
1516
+ // emptyString is only relevant for the number part (vValue)
1517
+ if (oOptions.showNumber && (vValue === oOptions.emptyString || isNaN(vValue) && isNaN(oOptions.emptyString))) {
1518
+ // if the value equals the 'emptyString' format option, return empty string.
1519
+ // the NaN case has to be checked by using isNaN because NaN !== NaN
1520
+ return "";
1521
+ }
1522
+
1523
+ // sMeasure must be a string if defined
1524
+ if (sMeasure !== undefined && sMeasure !== null && typeof sMeasure !== "string" && !(sMeasure instanceof String)) {
1525
+ return "";
1526
+ }
1527
+ if (!oOptions.showNumber && !sMeasure) {
1528
+ return "";
1529
+ }
1530
+
1531
+ // cannot create number from null or undefined
1532
+ if (bValueIsNullOrUndefined && (!sMeasure || !oOptions.showMeasure || oOptions.showNumber)) {
1533
+ return "";
1534
+ }
1535
+
1536
+ // If custom currencies are defined, we exclusively accept the defined ones,
1537
+ // other currencies are ignored
1538
+ if (sMeasure && oOptions.customCurrencies && !oOptions.customCurrencies[sMeasure]) {
1539
+ Log.error("Currency '" + sMeasure + "' is unknown.");
1540
+ return "";
1541
+ }
1542
+ if (!oOptions.showNumber && !oOptions.showMeasure) {
1543
+ return "";
1544
+ }
1545
+
1546
+ // Recognize the correct unit definition (either custom unit or CLDR unit)
1547
+ if (sMeasure && oOptions.type === mNumberType.UNIT) {
1548
+ if (oOptions.customUnits && typeof oOptions.customUnits === "object") {
1549
+ //custom units are exclusive (no fallback to LocaleData)
1550
+ mUnitPatterns = oOptions.customUnits[sMeasure];
1551
+ } else {
1552
+ //check if there is a unit mapping for the given unit
1553
+ sLookupMeasure = this.oLocaleData.getUnitFromMapping(sMeasure) || sMeasure;
1554
+ mUnitPatterns = this.oLocaleData.getUnitFormat(sLookupMeasure);
1555
+ }
1556
+ if (oOptions.showMeasure) {
1557
+ // a list of allowed unit types is given, so we check if the given measure is ok
1558
+ var bUnitTypeAllowed = !oOptions.allowedUnits || oOptions.allowedUnits.indexOf(sMeasure) >= 0;
1559
+ if (!bUnitTypeAllowed) {
1560
+ return "";
1561
+ }
1562
+ }
1563
+ if (!mUnitPatterns && !oOptions.showNumber) {
1564
+ return this._addOriginInfo(sMeasure);
1565
+ }
1566
+ }
1567
+ if (oOptions.type === mNumberType.UNIT) {
1568
+ // either take the decimals/precision on the custom units or fallback to the given format-options
1569
+ oOptions.decimals = mUnitPatterns && typeof mUnitPatterns.decimals === "number" && mUnitPatterns.decimals >= 0 ? mUnitPatterns.decimals : oOptions.decimals;
1570
+ oOptions.decimals = NumberFormat.getMaximumDecimals(oOptions);
1571
+ oOptions.precision = mUnitPatterns && typeof mUnitPatterns.precision === "number" && mUnitPatterns.precision >= 0 ? mUnitPatterns.precision : oOptions.precision;
1572
+ }
1573
+ let sCurrencySymbolOrCode;
1574
+ if (oOptions.type === mNumberType.CURRENCY) {
1575
+ sCurrencySymbolOrCode = this.getCurrencySymbolOrCode(sMeasure, oOptions.currencyCode);
1576
+ // Make sure the "trailingCurrencyCode" mode is only used on currency codes:
1577
+ // The "customCurrencies" format option takes precedence over CLDR and global configuration. If the given measure isn't found
1578
+ // there, we already return an empty string in the check above (look for error log 'Currency "xy" is unknown').
1579
+ // "mKnownCurrencyCodes" either contains the keys of the "customCurrencies" format option or the accumulated currency codes
1580
+ // from CLDR and global configuration. If the given measure isn't found there and does not have the three letter ISO code format,
1581
+ // it shouldn't be formatted with the "trailingCurrencyCode" pattern.
1582
+ if (sMeasure && oOptions.trailingCurrencyCode) {
1583
+ if (!this.mKnownCurrencyCodes[sMeasure] && !/(^[A-Z]{3}$)/.test(sMeasure)) {
1584
+ // Revert to non-"sap-" prefixed (trailing-currency-code) pattern. Also see code in getCurrencyInstance()
1585
+ oOptions.trailingCurrencyCode = false;
1586
+ }
1587
+ }
1588
+ if (!oOptions.showNumber) {
1589
+ // if the number should not be shown, return the sMeasure part standalone, without anything number specific
1590
+ return sCurrencySymbolOrCode;
1591
+ }
1592
+ if (oOptions.style === "long" || oOptions.style === "short") {
1593
+ oOptions.maxFractionDigits ??= 0;
1594
+ } else {
1595
+ if (oOptions.customCurrencies?.[sMeasure]?.decimals !== undefined) {
1596
+ oOptions.decimals = oOptions.customCurrencies[sMeasure].decimals;
1597
+ } else {
1598
+ oOptions.decimals ??= this.oLocaleData.getCurrencyDigits(sMeasure);
1599
+ }
1600
+ oOptions.decimals = NumberFormat.getMaximumDecimals(oOptions);
1601
+ }
1602
+ }
1603
+
1604
+ // set fraction digits based on the given or derived decimals
1605
+ if (oOptions.decimals !== undefined) {
1606
+ oOptions.minFractionDigits = oOptions.decimals;
1607
+ oOptions.maxFractionDigits = oOptions.decimals;
1608
+ }
1609
+ if (oOptions.shortLimit === undefined || Math.abs(vValue) >= oOptions.shortLimit) {
1610
+ nShortRefNumber = oOptions.shortRefNumber === undefined ? vValue : oOptions.shortRefNumber;
1611
+ oShortFormat = this.getShortenedFormat(nShortRefNumber, oOptions, bIndianCurrency);
1612
+ if (oShortFormat && oShortFormat.formatString != "0") {
1613
+ vValue = vValue / oShortFormat.magnitude;
1614
+ // If shortDecimals is defined, override the fractionDigits
1615
+ if (oOptions.shortDecimals !== undefined) {
1616
+ oOptions.minFractionDigits = oOptions.shortDecimals;
1617
+ oOptions.maxFractionDigits = oOptions.shortDecimals;
1618
+ } else {
1619
+ if (oOrigOptions.minFractionDigits === undefined && oOrigOptions.maxFractionDigits === undefined && oOrigOptions.decimals === undefined && oOrigOptions.precision === undefined && oOrigOptions.pattern === undefined) {
1620
+ // if none of the options which can affect the decimal digits is set, the default precision is set to 2
1621
+ oOptions.precision = 2;
1622
+ // set the default min/maxFractionDigits after setting the default precision
1623
+ oOptions.minFractionDigits = 0;
1624
+ oOptions.maxFractionDigits = 99;
1625
+ }
1626
+ if (oOrigOptions.maxFractionDigits === undefined && oOrigOptions.decimals === undefined) {
1627
+ // overwrite the default setting of Integer instance because
1628
+ // Integer with short format could have fraction part
1629
+ oOptions.maxFractionDigits = 99;
1630
+ }
1631
+ }
1632
+
1633
+ // Always use HALF_AWAY_FROM_ZERO for short formats
1634
+ oOptions.roundingMode = NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO;
1635
+ }
1636
+ }
1637
+
1638
+ // Must be done after calculating the short value, as it depends on the value
1639
+ // If short format is enabled or the precision isn't ignored, take the precision
1640
+ // option into consideration
1641
+ if ((oShortFormat || !oOptions.ignorePrecision) && oOptions.precision !== undefined) {
1642
+ // the number of decimal digits is calculated using (precision - number of integer digits)
1643
+ // the maxFractionDigits is adapted if the calculated value is smaller than the maxFractionDigits
1644
+ oOptions.maxFractionDigits = Math.min(oOptions.maxFractionDigits, getDecimals(vValue, oOptions.precision));
1645
+
1646
+ // if the minFractionDigits is greater than the maxFractionDigits, adapt the minFractionDigits with
1647
+ // the same value of the maxFractionDigits
1648
+ oOptions.minFractionDigits = Math.min(oOptions.minFractionDigits, oOptions.maxFractionDigits);
1649
+ }
1650
+ if (oOptions.type == mNumberType.PERCENT) {
1651
+ vValue = NumberFormat._shiftDecimalPoint(vValue, 2);
1652
+ }
1653
+
1654
+ // Rounding the value with oOptions.maxFractionDigits and oOptions.roundingMode.
1655
+ //
1656
+ // If the number of fraction digits are equal or less than oOptions.maxFractionDigits, the
1657
+ // number isn't changed. After this operation, the number of fraction digits is
1658
+ // equal or less than oOptions.maxFractionDigits.
1659
+ if ((typeof vValue === "number" || typeof vValue === "string" && typeof oOptions.roundingMode !== "function") && !oOptions.preserveDecimals) {
1660
+ vValue = rounding(vValue, oOptions.maxFractionDigits, oOptions.roundingMode);
1661
+ }
1662
+
1663
+ // No sign on zero values
1664
+ if (vValue == 0) {
1665
+ bNegative = false;
1666
+ }
1667
+ if (!bValueIsNullOrUndefined) {
1668
+ sNumber = LocaleData.convertToDecimal(vValue);
1669
+ }
1670
+ if (sNumber === "NaN") {
1671
+ return sNumber;
1672
+ }
1673
+
1674
+ // strip of trailing zeros in decimals
1675
+ // "1000.00" -> "1000" (maxFractionDigits: 0)
1676
+ // "1000.0" -> "1000.0" (maxFractionDigits: 1)
1677
+ // the intention behind preserveDecimals is to keep the precision in the number.
1678
+ // Trailing zero decimals are not required for the precision (e.g. 1,23000000 EUR).
1679
+ // These zeros are cut off until maxFractionDigits is reached to be backward compatible.
1680
+ // If more trailing decimal zeros are required the option maxFractionDigits can be increased.
1681
+ // Note: default maxFractionDigits for Unit and Float is 99.
1682
+ if (oOptions.preserveDecimals) {
1683
+ sNumber = stripTrailingZeroDecimals(sNumber, oOptions.maxFractionDigits);
1684
+ }
1685
+
1686
+ // if number is negative remove minus
1687
+ if (bNegative) {
1688
+ sNumber = sNumber.substr(1);
1689
+ }
1690
+
1691
+ // if number contains fraction, extract it
1692
+ iDotPos = sNumber.indexOf(".");
1693
+ if (iDotPos > -1) {
1694
+ sIntegerPart = sNumber.substr(0, iDotPos);
1695
+ sFractionPart = sNumber.substr(iDotPos + 1);
1696
+ } else {
1697
+ sIntegerPart = sNumber;
1698
+ }
1699
+
1700
+ // integer part length
1701
+ if (sIntegerPart.length < oOptions.minIntegerDigits) {
1702
+ sIntegerPart = sIntegerPart.padStart(oOptions.minIntegerDigits, "0");
1703
+ } else if (sIntegerPart.length > oOptions.maxIntegerDigits) {
1704
+ sIntegerPart = "".padStart(oOptions.maxIntegerDigits, "?");
1705
+ }
1706
+
1707
+ // fraction part length
1708
+ if (sFractionPart.length < oOptions.minFractionDigits) {
1709
+ sFractionPart = sFractionPart.padEnd(oOptions.minFractionDigits, "0");
1710
+ } else if (sFractionPart.length > oOptions.maxFractionDigits && !oOptions.preserveDecimals) {
1711
+ sFractionPart = sFractionPart.substr(0, oOptions.maxFractionDigits);
1712
+ }
1713
+ if (oOptions.type === mNumberType.UNIT && !oOptions.showNumber) {
1714
+ if (mUnitPatterns) {
1715
+ // the plural category of a unit pattern is determined for the complete number, maybe as compact
1716
+ // notation, e.g. "1.2M" must check "1.2c6"
1717
+ sPluralCategory = this._getPluralCategory(sIntegerPart, sFractionPart, oShortFormat);
1718
+ sPattern = mUnitPatterns["unitPattern-count-" + sPluralCategory];
1719
+ if (!sPattern) {
1720
+ sPattern = mUnitPatterns["unitPattern-count-other"];
1721
+ }
1722
+ if (!sPattern) {
1723
+ return this._addOriginInfo(sMeasure);
1724
+ }
1725
+ // fallback to "other" pattern if pattern does not include the number placeholder
1726
+ if (sPluralCategory !== "other" && sPattern.indexOf("{0}") === -1) {
1727
+ sPattern = mUnitPatterns["unitPattern-count-other"];
1728
+ if (!sPattern) {
1729
+ return this._addOriginInfo(sMeasure);
1730
+ }
1731
+ }
1732
+
1733
+ // with the current CLDR data this is not possible
1734
+ // but if there is the case when there is no number placeholder, the number cannot be separated from the unit
1735
+ // therefore it does not make sense to return a pattern which contains the number part in any other form as part of the pattern
1736
+ if (sPattern.indexOf("{0}") === -1) {
1737
+ Log.warning("Cannot separate the number from the unit because unitPattern-count-other '" + sPattern + "' does not include the number placeholder '{0}' for unit '" + sMeasure + "'");
1738
+ } else {
1739
+ return this._addOriginInfo(sPattern.replace("{0}", "").trim());
1740
+ }
1741
+ }
1742
+ }
1743
+
1744
+ // grouping
1745
+ if (oOptions.groupingEnabled) {
1746
+ sGroupedIntegerPart = applyGrouping(sIntegerPart, oOptions);
1747
+ } else {
1748
+ sGroupedIntegerPart = sIntegerPart;
1749
+ }
1750
+ const iDecimalPadding = oOptions.decimalPadding || 0;
1751
+ if (iDecimalPadding) {
1752
+ oOptions.minusSign = oOptions.minusSign.replace(rLeftToRightMark, "");
1753
+ }
1754
+ // combine
1755
+ if (bNegative) {
1756
+ sResult = oOptions.minusSign;
1757
+ }
1758
+ sResult += sGroupedIntegerPart;
1759
+ if (sFractionPart) {
1760
+ sResult += oOptions.decimalSeparator + sFractionPart;
1761
+ }
1762
+ const bUseCompactPattern = oShortFormat && oShortFormat.formatString && oOptions.showScale;
1763
+ let sCompactPattern;
1764
+ if (bUseCompactPattern) {
1765
+ // Get correct format string based on actual decimal/fraction digits
1766
+ // the plural category of a compact number/currency is determined for the reduced short number without
1767
+ // compact notation, e.g. "1.2M" must check "1.2"
1768
+ // (see CLDR "decimalFormat-short" and "decimalFormat-long" or "currencyFormat-short")
1769
+ sPluralCategory = this._getPluralCategory(sIntegerPart, sFractionPart);
1770
+ sCompactPattern = this.getCompactPattern(oOptions.type, oOptions.style, oShortFormat.key, sPluralCategory, oOptions.trailingCurrencyCode, bIndianCurrency, sMeasure && oOptions.showMeasure, sCurrencySymbolOrCode, bNegative);
1771
+ if (oOptions.type !== mNumberType.CURRENCY) {
1772
+ // inject formatted shortValue in the formatString
1773
+ sResult = sCompactPattern.replace(oShortFormat.valueSubString, sResult);
1774
+ }
1775
+ }
1776
+ if (oOptions.type === mNumberType.CURRENCY) {
1777
+ sPattern = bUseCompactPattern ? sCompactPattern : this.getCurrencyPattern(oOptions.currencyContext, oOptions.trailingCurrencyCode, sMeasure && oOptions.showMeasure, sCurrencySymbolOrCode, bNegative);
1778
+ sResult = NumberFormat._composeCurrencyResult(sPattern, sResult, sCurrencySymbolOrCode, oOptions.minusSign, bNegative);
1779
+ }
1780
+
1781
+ // format percent values:
1782
+ if (oOptions.type === mNumberType.PERCENT) {
1783
+ sPattern = oOptions.pattern;
1784
+ sResult = sPattern.replace(rNumberPattern, sResult);
1785
+ sResult = sResult.replace(/%/, oOptions.percentSign);
1786
+ }
1787
+ if (oOptions.showMeasure && sMeasure && oOptions.type === mNumberType.UNIT) {
1788
+ // the plural category of a unit pattern is determined for the complete number, maybe as compact
1789
+ // notation, e.g. "1.2M" must check "1.2c6"
1790
+ sPluralCategory = this._getPluralCategory(sIntegerPart, sFractionPart, oShortFormat);
1791
+ if (mUnitPatterns) {
1792
+ sPattern = mUnitPatterns["unitPattern-count-" + sPluralCategory];
1793
+ // some units do not have a pattern for each plural and therefore "other" is used as fallback
1794
+ if (!sPattern) {
1795
+ sPattern = mUnitPatterns["unitPattern-count-other"];
1796
+ }
1797
+ if (!sPattern) {
1798
+ sPattern = NumberFormat.getDefaultUnitPattern(sMeasure);
1799
+ }
1800
+ } else {
1801
+ sPattern = NumberFormat.getDefaultUnitPattern(sMeasure);
1802
+ }
1803
+ sResult = sPattern.replace("{0}", sResult);
1804
+ }
1805
+ let iDecimalPaddingLength = iDecimalPadding - sFractionPart.length;
1806
+ if (iDecimalPaddingLength > 0) {
1807
+ const bNegativeAccounting = sResult[sResult.length - 1] === ")";
1808
+ const sCharPunctuationSpace = "\u2008";
1809
+ if (sFractionPart) {
1810
+ if (bNegativeAccounting) {
1811
+ // the ")" and the CHAR_PUNCTUATION_SPACE u2008 have a combined width close to the width of a
1812
+ // CHAR_FIGURE_SPACE u2007
1813
+ sResult += sCharPunctuationSpace;
1814
+ iDecimalPaddingLength = iDecimalPaddingLength - 1;
1815
+ }
1816
+ } else if (!bNegativeAccounting) {
1817
+ // only add CHAR_PUNCTUATION_SPACE u2008 if there is no ")" at the end
1818
+ sResult += sCharPunctuationSpace;
1819
+ }
1820
+ sResult += "\u2007".repeat(iDecimalPaddingLength); // CHAR_FIGURE_SPACE u2007
1821
+ }
1822
+ return this._addOriginInfo(sResult);
1823
+ };
1824
+
1825
+ /**
1826
+ * Gets the currency symbol or the currency code for the given currency code depending on the given format options.
1827
+ *
1828
+ * @param {string} sCurrencyCode
1829
+ * The currency code
1830
+ * @param {boolean} bCurrencyCode
1831
+ * Whether to show the currency code instead of the currency symbol, see {@link Numberformat.getCurrencyInstance}
1832
+ * @returns {string}
1833
+ * The currency symbol or the currency code
1834
+ *
1835
+ * @private
1836
+ */
1837
+ NumberFormat.prototype.getCurrencySymbolOrCode = function (sCurrencyCode, bCurrencyCode) {
1838
+ if (bCurrencyCode) {
1839
+ return sCurrencyCode;
1840
+ }
1841
+ // the custom currency symbol map was preprocessed on instance creation
1842
+ return (typeof this.oFormatOptions.customCurrencies === "object" ? this.mKnownCurrencySymbols[sCurrencyCode] : this.oLocaleData.getCurrencySymbol(sCurrencyCode)) || sCurrencyCode;
1843
+ };
1844
+
1845
+ /**
1846
+ * Gets the plural category for the given number information. With a given <code>oShortFormat</code>
1847
+ * the category is determined based on the compact notation.
1848
+ *
1849
+ * @param {int} sIntegerPart
1850
+ * The integer part
1851
+ * @param {int} [sFractionPart]
1852
+ * The fraction part
1853
+ * @param {{magnitude: int}} [oShortFormat]
1854
+ * An object containing the <code>magnitude</code> information describing the factor of a compact number
1855
+ * @returns {string}
1856
+ * The plural category
1857
+ *
1858
+ * @private
1859
+ */
1860
+ NumberFormat.prototype._getPluralCategory = function (sIntegerPart, sFractionPart, oShortFormat) {
1861
+ var sNumber = sIntegerPart;
1862
+ if (sFractionPart) {
1863
+ sNumber += "." + sFractionPart;
1864
+ }
1865
+ if (oShortFormat) {
1866
+ sNumber += "c" + oShortFormat.magnitude.toExponential().slice(2);
1867
+ }
1868
+ return this.oLocaleData.getPluralCategory(sNumber);
1869
+ };
1870
+ NumberFormat.prototype._addOriginInfo = function (sResult) {
1871
+ if (Supportability.collectOriginInfo()) {
1872
+ // String object is created on purpose and must not be a string literal
1873
+ // eslint-disable-next-line no-new-wrappers
1874
+ sResult = new String(sResult);
1875
+ sResult.originInfo = {
1876
+ source: "Common Locale Data Repository",
1877
+ locale: this.oLocale.toString()
1878
+ };
1879
+ }
1880
+ return sResult;
1881
+ };
1882
+
1883
+ /**
1884
+ * Replaces the amount, measure, and minus sign parts in the given pattern with the given values and returns the
1885
+ * result.
1886
+ *
1887
+ * @param {string} sPattern
1888
+ * The currency pattern, e.g. "¤#,##0.00;(¤#,##0.00)", "¤#,##0.00;¤-#,##0.00", "#,##0.00", or "¤ 000K"
1889
+ * @param {string} sAmount
1890
+ * The formatted amount, e.g. "1,234.56"
1891
+ * @param {string} sMeasure
1892
+ * The currency symbol or code
1893
+ * @param {string} sMinusSign
1894
+ * The locale specific minus sign
1895
+ * @param {boolean} bNegative
1896
+ * Whether the amount is negative
1897
+ * @returns {string}
1898
+ * The resulting string after replacing the amount, measure, and minus sign parts in the given pattern with the
1899
+ * given values
1900
+ *
1901
+ * @private
1902
+ */
1903
+ NumberFormat._composeCurrencyResult = function (sPattern, sAmount, sMeasure, sMinusSign, bNegative) {
1904
+ const aPatternParts = sPattern.split(";");
1905
+ if (aPatternParts.length === 2) {
1906
+ sPattern = aPatternParts[bNegative ? 1 : 0];
1907
+ if (bNegative) {
1908
+ sAmount = sAmount.slice(sMinusSign.length);
1909
+ }
1910
+ }
1911
+ let sResult = sPattern.replace("-", sMinusSign).replace(rNumberPattern, sAmount).replace("\u00a4", sMeasure);
1912
+ if (bNegative) {
1913
+ // when no space is inserted between measure and number
1914
+ // and when the number is negative and the measure is shown before the number
1915
+ // a zero-width non-breakable space ("\ufeff") is inserted before the minus sign
1916
+ // in order to prevent the formatted currency number from being wrapped after the
1917
+ // minus sign when the space isn't enough for displaying the currency number within
1918
+ // one line
1919
+ sResult = sResult.replace(sMeasure + sMinusSign, sMeasure + "\ufeff" + sMinusSign);
1920
+ }
1921
+ return sResult;
1922
+ };
1923
+
1924
+ /**
1925
+ * Parse a string which is formatted according to the given format options.
1926
+ *
1927
+ * @param {string} sValue the string containing a formatted numeric value
1928
+ * @return {number|array|string|null} the parsed value as:
1929
+ * <ul>
1930
+ * <li>number</li>
1931
+ * <li>array which contains the parsed value and the currency code (symbol) or unit for currency and unit instances</li>
1932
+ * <li>string when option "parseAsString" is <code>true</code></li>
1933
+ * <li><code>NaN</code> if value cannot be parsed</li>
1934
+ * <li><code>null</code> if value is invalid</li>
1935
+ * </ul>
1936
+ * @public
1937
+ */
1938
+ NumberFormat.prototype.parse = function (sValue) {
1939
+ var oOptions = this.oFormatOptions,
1940
+ sPlusSigns = oOptions.plusSign + this.oLocaleData.getLenientNumberSymbols("plusSign"),
1941
+ sMinusSigns = oOptions.minusSign + this.oLocaleData.getLenientNumberSymbols("minusSign"),
1942
+ // Note: the minus sign ('-') needs to be quoted as well such that it is not confused with the range operator, e.g. in [A-Z]
1943
+ sPlusMinusSigns = quote(sPlusSigns + sMinusSigns),
1944
+ sGroupingSeparator = quote(oOptions.groupingSeparator),
1945
+ sDecimalSeparator = quote(oOptions.decimalSeparator),
1946
+ sRegExpFloat = "^\\s*([" + sPlusMinusSigns + "]?(?:[0-9" + sGroupingSeparator + "]+|[0-9" + sGroupingSeparator + "]*" + sDecimalSeparator + "[0-9]*)(?:[eE][+-]?[0-9]+)?)\\s*$",
1947
+ sRegExpInt = "^\\s*([" + sPlusMinusSigns + "]?[0-9" + sGroupingSeparator + "]+)\\s*$",
1948
+ oGroupingRegExp = new RegExp(sGroupingSeparator, "g"),
1949
+ oDecimalRegExp = new RegExp(sDecimalSeparator, "g"),
1950
+ sPercentSign = this.oLocaleData.getNumberSymbol("percentSign"),
1951
+ bIndianCurrency = oOptions.type === mNumberType.CURRENCY && this.oLocale.getLanguage() === "en" && this.oLocale.getRegion() === "IN",
1952
+ oRegExp,
1953
+ bPercent,
1954
+ sMeasure,
1955
+ sPercentPattern,
1956
+ vResult = 0,
1957
+ oShort,
1958
+ vEmptyParseValue;
1959
+ if (typeof sValue !== "string" && !(sValue instanceof String)) {
1960
+ return null;
1961
+ }
1962
+ sValue = FormatUtils.normalize(sValue).trim();
1963
+ if (sValue === "") {
1964
+ const bUnitOrCurrency = oOptions.type === mNumberType.CURRENCY || oOptions.type === mNumberType.UNIT;
1965
+ if (!oOptions.showNumber && !bUnitOrCurrency) {
1966
+ return null;
1967
+ }
1968
+ vEmptyParseValue = oOptions.emptyString;
1969
+ // If the 'emptyString' option is set to 0 or NaN and parseAsString is set to true, the return value should be converted to a string.
1970
+ // Because null is a valid value for string type, therefore null is not converted to a string.
1971
+ if (oOptions.parseAsString && (oOptions.emptyString === 0 || isNaN(oOptions.emptyString))) {
1972
+ vEmptyParseValue = oOptions.emptyString + "";
1973
+ }
1974
+ if (bUnitOrCurrency) {
1975
+ if (!oOptions.showNumber) {
1976
+ return [undefined, vEmptyParseValue];
1977
+ }
1978
+ return [vEmptyParseValue, undefined];
1979
+ } else {
1980
+ return vEmptyParseValue;
1981
+ }
1982
+ }
1983
+ if (oOptions.groupingSeparator === oOptions.decimalSeparator) {
1984
+ Log.error("The grouping and decimal separator both have the same value '" + oOptions.groupingSeparator + "'. " + "They must be different from each other such that values can be parsed correctly.");
1985
+ }
1986
+ sPercentPattern = oOptions.type === mNumberType.PERCENT ? oOptions.pattern : this.oLocaleData.getPercentPattern();
1987
+ if (sPercentPattern.charAt(0) === "%") {
1988
+ sRegExpFloat = sRegExpFloat.slice(0, 1) + "%?" + sRegExpFloat.slice(1);
1989
+ } else if (sPercentPattern.charAt(sPercentPattern.length - 1) === "%") {
1990
+ sRegExpFloat = sRegExpFloat.slice(0, sRegExpFloat.length - 1) + "%?" + sRegExpFloat.slice(sRegExpFloat.length - 1);
1991
+ }
1992
+ var aUnitCode;
1993
+ if (oOptions.type === mNumberType.UNIT && oOptions.showMeasure) {
1994
+ var mUnitPatterns;
1995
+ if (oOptions.customUnits && typeof oOptions.customUnits === "object") {
1996
+ //custom units are exclusive (no fallback to LocaleData)
1997
+ mUnitPatterns = oOptions.customUnits;
1998
+ } else {
1999
+ mUnitPatterns = this.oLocaleData.getUnitFormats();
2000
+ }
2001
+ assert(mUnitPatterns, "Unit patterns cannot be loaded");
2002
+
2003
+ // filter using allowedUnits option
2004
+ if (oOptions.allowedUnits) {
2005
+ var mFilteredUnits = {};
2006
+ for (var i = 0; i < oOptions.allowedUnits.length; i++) {
2007
+ var sUnitType = oOptions.allowedUnits[i];
2008
+ mFilteredUnits[sUnitType] = mUnitPatterns[sUnitType];
2009
+ }
2010
+ mUnitPatterns = mFilteredUnits;
2011
+ }
2012
+ var oPatternAndResult = parseNumberAndUnit(mUnitPatterns, sValue, oOptions.showNumber, this.oLocaleData.sCLDRLocaleId);
2013
+ var bUnitIsAmbiguous = false;
2014
+ aUnitCode = oPatternAndResult.cldrCode;
2015
+ if (aUnitCode.length === 1) {
2016
+ sMeasure = aUnitCode[0];
2017
+ if (!oOptions.showNumber) {
2018
+ return [undefined, sMeasure];
2019
+ }
2020
+ } else if (aUnitCode.length === 0) {
2021
+ // in case showMeasure is set to false or unitOptional is set to true
2022
+ // we only try to parse the numberValue
2023
+ // the currency format behaves the same
2024
+ if (oOptions.unitOptional) {
2025
+ oPatternAndResult.numberValue = sValue;
2026
+ } else {
2027
+ //unit not found
2028
+ return null;
2029
+ }
2030
+ } else {
2031
+ //ambiguous unit
2032
+ assert(aUnitCode.length === 1, "Ambiguous unit [" + aUnitCode.join(", ") + "] for input: '" + sValue + "'");
2033
+ sMeasure = undefined;
2034
+ bUnitIsAmbiguous = true;
2035
+ }
2036
+
2037
+ // TODO: better error handling in strict mode
2038
+ // Next steps will be to implement a more helpful error message for these cases.
2039
+ // Right now we simply return null. For now this will force the types to throw
2040
+ // a default ParseException with a non-descriptive error.
2041
+ if (oOptions.strictParsing) {
2042
+ // two cases:
2043
+ // 1. showMeasure is set to false, but still a unit was parsed
2044
+ // 2. no unit (either none could be found OR the unit is ambiguous, should be separate error logs later on)
2045
+ if (bUnitIsAmbiguous) {
2046
+ return null;
2047
+ }
2048
+ }
2049
+ sValue = oPatternAndResult.numberValue || sValue;
2050
+ }
2051
+ var oResult;
2052
+ if (oOptions.type === mNumberType.CURRENCY && oOptions.showMeasure) {
2053
+ oResult = parseNumberAndCurrency({
2054
+ value: sValue,
2055
+ currencySymbols: this.mKnownCurrencySymbols,
2056
+ customCurrencyCodes: this.mKnownCurrencyCodes,
2057
+ duplicatedSymbols: this.mDuplicatedSymbols,
2058
+ customCurrenciesAvailable: !!oOptions.customCurrencies
2059
+ });
2060
+ if (!oResult) {
2061
+ return null;
2062
+ }
2063
+
2064
+ // TODO: better error handling in strict mode
2065
+ // Next steps will be to implement a more helpful error message for these cases.
2066
+ // Right now we simply return null. For now this will force the types to throw
2067
+ // a default ParseException with a non-descriptive error.
2068
+ if (oOptions.strictParsing) {
2069
+ if (!oResult.currencyCode || oResult.duplicatedSymbolFound) {
2070
+ // here we need an error log for:
2071
+ // 1. missing currency code/symbol (CLDR & custom)
2072
+ // 2. duplicated symbol was found (only custom, CLDR has no duplicates)
2073
+ return null;
2074
+ }
2075
+ }
2076
+ sValue = oResult.numberValue;
2077
+ sMeasure = oResult.currencyCode;
2078
+ if (oOptions.customCurrencies && sMeasure === null) {
2079
+ return null;
2080
+ }
2081
+ if (!oOptions.showNumber) {
2082
+ if (sValue) {
2083
+ return null;
2084
+ }
2085
+ return [undefined, sMeasure];
2086
+ }
2087
+ }
2088
+
2089
+ // remove all white spaces because when grouping separator is a non-breaking space (russian and french for example)
2090
+ // user will not input it this way. Also white spaces or grouping separator can be ignored by determining the value
2091
+ sValue = sValue.replace(rAllWhiteSpaces, "");
2092
+ oShort = this.getNumberFromShortened(sValue, bIndianCurrency);
2093
+ if (oShort) {
2094
+ sValue = oShort.number;
2095
+ }
2096
+ var bScientificNotation = isScientificNotation(sValue);
2097
+
2098
+ // Check for valid syntax
2099
+ // integer might be expressed in scientific format, e.g. 1.23e+5
2100
+ // for this case it must be parsed as float
2101
+ if (oOptions.isInteger && !oShort && !bScientificNotation) {
2102
+ oRegExp = new RegExp(sRegExpInt);
2103
+ } else {
2104
+ oRegExp = new RegExp(sRegExpFloat);
2105
+ }
2106
+ if (!oRegExp.test(sValue)) {
2107
+ return oOptions.type === mNumberType.CURRENCY || oOptions.type === mNumberType.UNIT ? null : NaN;
2108
+ }
2109
+
2110
+ // Replace "minus/plus" sign with a parsable symbol
2111
+ // e.g. "➖47" ("➖" or "\u2796" cannot be parsed using parseInt) --> "-47" (can be parsed using parseInt)
2112
+ var iValueLength = sValue.length;
2113
+ for (var iValuePos = 0; iValuePos < iValueLength; iValuePos++) {
2114
+ var sCurrentValueChar = sValue[iValuePos];
2115
+
2116
+ // it can either be a minus or a plus
2117
+ // if one was found break because there can only be one in a value
2118
+ if (sPlusSigns.includes(sCurrentValueChar)) {
2119
+ sValue = sValue.replace(sCurrentValueChar, "+");
2120
+ break;
2121
+ } else if (sMinusSigns.includes(sCurrentValueChar)) {
2122
+ sValue = sValue.replace(sCurrentValueChar, "-");
2123
+ break;
2124
+ }
2125
+ }
2126
+
2127
+ // Remove the leading "+" sign because when "parseAsString" is set to true the "parseInt" or "parseFloat" isn't called and the leading "+" has to be moved manually
2128
+ sValue = sValue.replace(/^\+/, "");
2129
+
2130
+ // remove the percentage sign
2131
+ if (!oOptions.isInteger && sValue.indexOf(sPercentSign) !== -1) {
2132
+ bPercent = true;
2133
+ sValue = sValue.replace(sPercentSign, "");
2134
+ }
2135
+ var sValueWithGrouping = sValue;
2136
+
2137
+ // Remove grouping separator and replace locale dependant decimal separator,
2138
+ // before calling parseInt/parseFloat
2139
+ sValue = sValue.replace(oGroupingRegExp, "");
2140
+
2141
+ // Expanding short value before using parseInt/parseFloat
2142
+ if (oShort) {
2143
+ sValue = sValue.replace(oDecimalRegExp, ".");
2144
+ sValue = NumberFormat._shiftDecimalPoint(sValue, Math.round(Math.log(oShort.factor) / Math.LN10));
2145
+ }
2146
+ if (oOptions.isInteger) {
2147
+ var iInt;
2148
+ // check if it is a valid integer
2149
+ // 1.234567e+5 is 123456.7 is not an integer
2150
+ // 1.234567e+6 is 1234567 is an integer
2151
+ if (bScientificNotation) {
2152
+ sValue = sValue.replace(oDecimalRegExp, ".");
2153
+ iInt = getInteger(sValue);
2154
+ if (iInt === undefined) {
2155
+ return NaN;
2156
+ }
2157
+ } else {
2158
+ iInt = parseInt(sValue);
2159
+ }
2160
+ vResult = oOptions.parseAsString ? sValue : iInt;
2161
+ } else {
2162
+ sValue = sValue.replace(oDecimalRegExp, ".");
2163
+ vResult = oOptions.parseAsString ? sValue : parseFloat(sValue);
2164
+ if (bPercent) {
2165
+ vResult = NumberFormat._shiftDecimalPoint(vResult, -2);
2166
+ }
2167
+ }
2168
+
2169
+ // strict grouping validation
2170
+ var bIsGroupingValid = this._checkGrouping(sValueWithGrouping, oOptions, bScientificNotation);
2171
+ if (!bIsGroupingValid) {
2172
+ // treat invalid grouping the same way as if the value cannot be parsed
2173
+ return oOptions.type === mNumberType.CURRENCY || oOptions.type === mNumberType.UNIT ? null : NaN;
2174
+ }
2175
+
2176
+ // Get rid of leading zeros (percent was already shifted)
2177
+ if (oOptions.parseAsString && !bPercent) {
2178
+ vResult = NumberFormat._shiftDecimalPoint(sValue, 0);
2179
+ }
2180
+ if (oOptions.type === mNumberType.CURRENCY || oOptions.type === mNumberType.UNIT) {
2181
+ return [vResult, sMeasure];
2182
+ }
2183
+ return vResult;
2184
+ };
2185
+
2186
+ /**
2187
+ * Returns the scaling factor which is calculated based on the format options and the current locale being used.
2188
+ *
2189
+ * This function only returns a meaningful scaling factor when the 'style' formatting option is set
2190
+ * to 'short' or 'long', and the 'shortRefNumber' option for calculating the scale factor is set.
2191
+ *
2192
+ * Consider using this function when the 'showScale' option is set to <code>false</code>, which
2193
+ * causes the scale factor not to appear in every formatted number but in a shared place.
2194
+ *
2195
+ * @example thousand (locale "en")
2196
+ *
2197
+ * NumberFormat.getFloatInstance({style: "long", shortRefNumber: 1000}).getScale();
2198
+ * // "thousand"
2199
+ *
2200
+ * @returns {string|undefined} The scale string if it exists based on the given 'shortRefNumber' option. Otherwise it returns <code>undefined</code>.
2201
+ * @since 1.100
2202
+ * @public
2203
+ */
2204
+ NumberFormat.prototype.getScale = function () {
2205
+ if (this.oFormatOptions.style !== "short" && this.oFormatOptions.style !== "long" || this.oFormatOptions.shortRefNumber === undefined) {
2206
+ return;
2207
+ }
2208
+ var oShortFormat = this.getShortenedFormat(this.oFormatOptions.shortRefNumber, this.oFormatOptions),
2209
+ sScale;
2210
+ if (oShortFormat && oShortFormat.formatString) {
2211
+ // remove the placeholder of number
2212
+ // replace the "'.'" with "."
2213
+ // trim to remove the space and non-breakable space
2214
+ sScale = oShortFormat.formatString.replace(rNumPlaceHolder, "").replace(/'.'/g, ".").trim();
2215
+ if (sScale) {
2216
+ // sScale could be an empty string and undefined should be returned in this case
2217
+ return sScale;
2218
+ }
2219
+ }
2220
+ };
2221
+
2222
+ /**
2223
+ * Moves the decimal seperator of the given number by the given steps to the right or left.
2224
+ *
2225
+ * @param {number|string} vValue
2226
+ * The number
2227
+ * @param {int} iStep
2228
+ * The number of decimal places to shift the "."; positive values shift to the right, negative values shift to the
2229
+ * left
2230
+ * @param {boolean} bNormalize
2231
+ * Whether the result is normalized if <code>vValue</code> is of type "string"; that means whether trailing zeros
2232
+ * are removed and whether scientific notation is resolved to a decimal string without exponent
2233
+ * @returns {number|string|null}
2234
+ * The number with shifted decimal point; or <code>null</code> if the given value is neither of type "number", nor
2235
+ * of type "string"
2236
+ */
2237
+ NumberFormat._shiftDecimalPoint = function (vValue, iStep, bNormalize) {
2238
+ var sMinus = "";
2239
+ var aExpParts = vValue.toString().toLowerCase().split("e");
2240
+ if (typeof vValue === "number") {
2241
+ // Exponential operation is used instead of simply multiply the number by
2242
+ // Math.pow(10, maxFractionDigits) because Exponential operation returns exact float
2243
+ // result but multiply doesn't. For example 1.005*100 = 100.49999999999999.
2244
+
2245
+ iStep = aExpParts[1] ? +aExpParts[1] + iStep : iStep;
2246
+ return +(aExpParts[0] + "e" + iStep);
2247
+ } else if (typeof vValue === "string") {
2248
+ if (!bNormalize && parseFloat(vValue) === 0 && iStep >= 0) {
2249
+ // input "00000" should become "0"
2250
+ // input "000.000" should become "0.000" to keep precision of decimals
2251
+ // input "1e-1337" should remain "1e-1337" in order to keep the precision
2252
+ return vValue.replace(rLeadingZeros, "$1$2");
2253
+ }
2254
+ // In case of a negative value the leading minus needs to be cut off before shifting the decimal point.
2255
+ // Otherwise the minus will affect the positioning by index 1.
2256
+ // The minus sign will be added to the final result again.
2257
+ var sFirstChar = aExpParts[0].charAt(0);
2258
+ sMinus = sFirstChar === "-" ? sFirstChar : "";
2259
+ if (sMinus || sFirstChar === "+") {
2260
+ aExpParts[0] = aExpParts[0].slice(1);
2261
+ }
2262
+ vValue = aExpParts[0];
2263
+ var iDecimalPos = vValue.indexOf("."),
2264
+ // the expected position after move
2265
+ iAfterMovePos,
2266
+ // the integer part in the final result
2267
+ sInt,
2268
+ // the decimal part in the final result
2269
+ sDecimal;
2270
+ if (iDecimalPos === -1) {
2271
+ // when there's no decimal point, add one to the end
2272
+ vValue = vValue + ".";
2273
+ iDecimalPos = vValue.length - 1;
2274
+ }
2275
+ if (aExpParts[1]) {
2276
+ iDecimalPos += +aExpParts[1];
2277
+ }
2278
+ iAfterMovePos = iDecimalPos + iStep;
2279
+ if (iAfterMovePos <= 0) {
2280
+ // pad 0 to the left when decimal point should be shifted far left
2281
+ vValue = vValue.padStart(vValue.length - iAfterMovePos + 1, '0');
2282
+ iAfterMovePos = 1;
2283
+ } else if (iAfterMovePos >= vValue.length - 1) {
2284
+ // pad 0 to the right
2285
+ vValue = vValue.padEnd(iAfterMovePos + 1, '0');
2286
+ iAfterMovePos = vValue.length - 1;
2287
+ }
2288
+ vValue = vValue.replace(".", "");
2289
+ sInt = vValue.substring(0, iAfterMovePos);
2290
+ sDecimal = vValue.substring(iAfterMovePos);
2291
+ // remove unnecessary leading zeros
2292
+ sInt = sInt.replace(rLeadingZeros, "$1$2");
2293
+ if (bNormalize) {
2294
+ sDecimal = sDecimal.replace(rTrailingZeros, "");
2295
+ }
2296
+ return sMinus + sInt + (sDecimal ? "." + sDecimal : "");
2297
+ } else {
2298
+ // can't shift decimal point in this case
2299
+ return null;
2300
+ }
2301
+ };
2302
+
2303
+ /**
2304
+ * Checks whether there is a letter next to the number using the given currency pattern.
2305
+ *
2306
+ * @param {string} sPattern
2307
+ * The currency pattern, e.g. "¤#,##0.00;(¤#,##0.00)" or "¤ 000K"
2308
+ * @param {string} sCurrency
2309
+ * The currency code or the currency symbol to check, e.g. "USD" or "$"
2310
+ * @param {boolean} bNegative
2311
+ * Whether the value to be formatted is negative
2312
+ * @returns {boolean}
2313
+ * Whether there is a letter next to the number for the given pattern and currency
2314
+ *
2315
+ * @private
2316
+ */
2317
+ NumberFormat.isAlphaNextToNumber = function (sPattern, sCurrency, bNegative) {
2318
+ if (!sPattern || !sCurrency) {
2319
+ return false;
2320
+ }
2321
+ const aPatterns = sPattern.split(";");
2322
+ sPattern = (aPatterns[bNegative ? 1 : 0] || aPatterns[0]).replace(rAllRTLCharacters, "");
2323
+ const aMatches = rSplitCurrencyPattern.exec(sPattern);
2324
+
2325
+ // number in front of the currency placeholder
2326
+ if (aMatches[1]) {
2327
+ return rStartsWithLetter.test(sCurrency) && !aMatches[2];
2328
+ }
2329
+
2330
+ // currency placeholder in front of the number
2331
+ return !aMatches[4] // no characters between the placeholder and the number
2332
+ && rEndsWithLetter.test(sCurrency) // currency ends with a letter
2333
+ // if there is no separate negative pattern and the value is negative, there is a minus sign between
2334
+ // the currency and the number, so there is no need for the alphaNextToNumber pattern
2335
+ && (!bNegative || aPatterns.length !== 1);
2336
+ };
2337
+
2338
+ /**
2339
+ * Gets the compact decimal or currency pattern for the given power of ten and plural category.
2340
+ *
2341
+ * @param {"integer"|"float"|"currency"|"unit"|"percent"} sType
2342
+ * The number format type
2343
+ * @param {"long"|"short"} sStyle
2344
+ * The style of the compact format
2345
+ * @param {string} sPowerOfTen
2346
+ * The power of ten
2347
+ * @param {"few"|"many"|"one"|"other"|"two"|"zero"} sPluralCategory
2348
+ * The plural category
2349
+ * @param {boolean} [bTrailingCurrencyCode]
2350
+ * Whether the currency code is formatted after the amount; only relevant if type "currency" is used
2351
+ * @param {boolean} [bIndianCurrency]
2352
+ * Whether to use the Indian currency format; only relevant if type "currency" is used
2353
+ * @param {boolean} [bShowMeasure]
2354
+ * Whether to show the measure
2355
+ * @param {string} [sCurrency]
2356
+ * The currency code or symbol
2357
+ * @param {boolean} [bNegative]
2358
+ * Whether the number is negative
2359
+ * @returns {string|undefined}
2360
+ * The compact decimal or currency pattern for the given power of ten and plural category; or
2361
+ * <code>undefined</code> if there is no pattern for the given parameters
2362
+ *
2363
+ * @private
2364
+ */
2365
+ NumberFormat.prototype.getCompactPattern = function (sType, sStyle, sPowerOfTen, sPluralCategory, bTrailingCurrencyCode, bIndianCurrency, bShowMeasure, sCurrency, bNegative) {
2366
+ let sPattern;
2367
+ if (sType === mNumberType.CURRENCY) {
2368
+ if (bTrailingCurrencyCode) {
2369
+ sStyle = "sap-short";
2370
+ }
2371
+ if (bIndianCurrency) {
2372
+ sStyle += "-indian";
2373
+ }
2374
+ if (bShowMeasure) {
2375
+ // Use currency specific format because for some languages there is a difference between the
2376
+ // decimalFormat and the currencyFormat
2377
+ sPattern = this.oLocaleData.getCompactCurrencyPattern(sStyle, sPowerOfTen, sPluralCategory);
2378
+ if (NumberFormat.isAlphaNextToNumber(sPattern, sCurrency, bNegative)) {
2379
+ sPattern = this.oLocaleData.getCompactCurrencyPattern(sStyle, sPowerOfTen, sPluralCategory, "alphaNextToNumber") || sPattern;
2380
+ }
2381
+ } else {
2382
+ sPattern = this.oLocaleData.getCompactCurrencyPattern(sStyle, sPowerOfTen, sPluralCategory, "noCurrency");
2383
+ if (!sPattern) {
2384
+ if (sStyle.startsWith("sap-")) {
2385
+ sStyle = sStyle.slice(4);
2386
+ }
2387
+ sPattern = this.oLocaleData.getCompactDecimalPattern(sStyle, sPowerOfTen, sPluralCategory);
2388
+ }
2389
+ }
2390
+ } else {
2391
+ sPattern = this.oLocaleData.getCompactDecimalPattern(sStyle, sPowerOfTen, sPluralCategory);
2392
+ }
2393
+
2394
+ // pattern may contain a single quoted dot ('.') to differentiate them from decimal separator; replace it
2395
+ // with an unquoted dot (.)
2396
+ sPattern = sPattern?.replace(/'.'/g, ".");
2397
+ return sPattern;
2398
+ };
2399
+
2400
+ /**
2401
+ * Gets the locale specific currency pattern for the given parameters.
2402
+ *
2403
+ * @param {"accounting"|"standard"} sContext The context of the currency pattern
2404
+ * @param {boolean} [bShowTrailingCurrencyCode] Whether the currency code shall be shown after the amount
2405
+ * @param {boolean} [bShowMeasure] Whether to include the measure (currency code or currency symbol) in the pattern
2406
+ * @param {string} [sCurrency] The currency code or symbol to use
2407
+ * @param {boolean} [bNegative] Whether the current value is negative
2408
+ * @returns {string} The currency pattern
2409
+ *
2410
+ * @private
2411
+ */
2412
+ NumberFormat.prototype.getCurrencyPattern = function (sContext, bShowTrailingCurrencyCode, bShowMeasure, sCurrency, bNegative) {
2413
+ if (bShowTrailingCurrencyCode) {
2414
+ sContext = "sap-" + sContext;
2415
+ }
2416
+ let sPattern = this.oLocaleData.getCurrencyPattern(sContext, bShowMeasure ? undefined : "noCurrency");
2417
+ if (bShowMeasure && NumberFormat.isAlphaNextToNumber(sPattern, sCurrency, bNegative)) {
2418
+ sPattern = this.oLocaleData.getCurrencyPattern(sContext, "alphaNextToNumber") || sPattern;
2419
+ }
2420
+ return sPattern;
2421
+ };
2422
+
2423
+ /**
2424
+ * Gets the compact decimal or currency format for the given value and parameters.
2425
+ *
2426
+ * @param {number|string} vValue
2427
+ * The value for which the shortened format is determined
2428
+ * @param {object} oOptions
2429
+ * The options used for getting the compact pattern
2430
+ * @param {int} [oOptions.precision = 2]
2431
+ * The maximum number of digits in the formatted representation of the number
2432
+ * @param {"long"|"short"} oOptions.style
2433
+ * The style of the compact format
2434
+ * @param {boolean} [oOptions.trailingCurrencyCode]
2435
+ * Whether the currency code is formatted after the amount; only relevant if type "currency" is used
2436
+ * @param {"integer"|"float"|"currency"|"unit"|"percent"} oOptions.type
2437
+ * The number format type
2438
+ * @param {boolean} [bIndianCurrency]
2439
+ * Whether to use the Indian currency format; only relevant if type "currency" is used
2440
+ *
2441
+ * @returns {object|undefined}
2442
+ * The compact decimal or currency format for the given value; or <code>undefined</code> if neither the "short"
2443
+ * or the "long" style is used, or if there is no compact format for the given parameters; the returned object
2444
+ * contains the following properties:
2445
+ * <ul>
2446
+ * <li><code>decimals</code>: The number of decimals used in the compact format pattern</li>
2447
+ * <li><code>formatString</code>: The compact format pattern to use</li>
2448
+ * <li><code>key</code>: The power of ten matching the given value</li>
2449
+ * <li><code>magnitude</code>: The divisor to get the compact number to show from the given value</li>
2450
+ * <li><code>valueSubString</code>: The number part of the format pattern</li>
2451
+ * </ul>
2452
+ *
2453
+ * @private
2454
+ */
2455
+ NumberFormat.prototype.getShortenedFormat = function (vValue, oOptions, bIndianCurrency) {
2456
+ var oShortFormat,
2457
+ iKey,
2458
+ sKey,
2459
+ sStyle = oOptions.style,
2460
+ iPrecision = oOptions.precision !== undefined ? oOptions.precision : 2;
2461
+ if (sStyle != "short" && sStyle != "long") {
2462
+ return undefined;
2463
+ }
2464
+ for (var i = 0; i < 15; i++) {
2465
+ iKey = Math.pow(10, i);
2466
+ if (rounding(Math.abs(vValue) / iKey, iPrecision - 1) < 10) {
2467
+ break;
2468
+ }
2469
+ }
2470
+ sKey = iKey.toString();
2471
+
2472
+ // Use "other" format to find the right magnitude, the actual format will be retrieved later
2473
+ // after the value has been calculated
2474
+ const sCldrFormat = this.getCompactPattern(oOptions.type, oOptions.style, sKey, "other", oOptions.trailingCurrencyCode, bIndianCurrency, oOptions.showMeasure);
2475
+ if (!sCldrFormat || sCldrFormat == "0") {
2476
+ //no format or special "0" format => number doesn't need to be shortened
2477
+ return undefined;
2478
+ } else {
2479
+ oShortFormat = {};
2480
+ oShortFormat.key = sKey;
2481
+ oShortFormat.formatString = sCldrFormat;
2482
+ var match = sCldrFormat.match(rNumPlaceHolder);
2483
+ if (match) {
2484
+ //to get magnitude, we need to remove from key the number of zeros
2485
+ //contained in valueSubString before decimal separator minus 1
2486
+ // "0.0" => magnitude = key
2487
+ // "00" => magnitude = key / 10
2488
+ // "000" => magnitude = key / 100
2489
+ oShortFormat.valueSubString = match[0];
2490
+ var decimalSeparatorPosition = oShortFormat.valueSubString.indexOf(".");
2491
+ if (decimalSeparatorPosition == -1) {
2492
+ oShortFormat.decimals = 0;
2493
+ oShortFormat.magnitude = iKey * Math.pow(10, 1 - oShortFormat.valueSubString.length);
2494
+ } else {
2495
+ oShortFormat.decimals = oShortFormat.valueSubString.length - decimalSeparatorPosition - 1;
2496
+ oShortFormat.magnitude = iKey * Math.pow(10, 1 - decimalSeparatorPosition);
2497
+ }
2498
+ } else {
2499
+ //value pattern has not be recognized
2500
+ //we cannot shorten
2501
+ return undefined;
2502
+ }
2503
+ }
2504
+ return oShortFormat;
2505
+ };
2506
+
2507
+ /**
2508
+ * Returns an object with two properties. The first is the number substring of the given <code>sValue</code>.
2509
+ * The second property is the factor with which the determined number must be multiplied to resolve
2510
+ * the short format.
2511
+ * If the number of the given <code>sValue</code> is larger than 100 000 000 000 000 no short format will be found
2512
+ * and <code>undefined</code> is returned.
2513
+ *
2514
+ * @example
2515
+ * Formatted value to be parsed: "123K"
2516
+ * Chosen short format: "100000-other": "000K",
2517
+ * Number Substring: "123"
2518
+ * Factor: 1000
2519
+ * Parsed number: 123 * 1000 = 123000
2520
+ *
2521
+ * @param {string} sValue The value for which the short format shall be determined
2522
+ * @param {boolean} bIndianCurrency Whether the the value has to be treated as Indian currency
2523
+ *
2524
+ * @returns {{number: string, factor: number}|undefined}
2525
+ * An object containing the number substring of the given <code>sValue</code>, e.g. <code>"123"</code> and
2526
+ * the factor with which the determined number must be multiplied to resolve the short format;
2527
+ * <code>undefined</code> if no short format is found for the given <code>sValue</code>
2528
+ *
2529
+ * @private
2530
+ */
2531
+ NumberFormat.prototype.getNumberFromShortened = function (sValue, bIndianCurrency) {
2532
+ const aPluralCategories = this.oLocaleData.getPluralCategories();
2533
+ const oBestResult = {
2534
+ number: undefined,
2535
+ factor: 1
2536
+ };
2537
+ const oIndianBestResult = {
2538
+ number: undefined,
2539
+ factor: 1
2540
+ };
2541
+ for (const iPowerOfTen of aPowerOfTens) {
2542
+ for (const sPluralCategory of aPluralCategories) {
2543
+ if (bIndianCurrency) {
2544
+ this.updateBestResult(oIndianBestResult, sPluralCategory, iPowerOfTen, "short-indian", sValue);
2545
+ }
2546
+ this.updateBestResult(oBestResult, sPluralCategory, iPowerOfTen, "long", sValue);
2547
+ this.updateBestResult(oBestResult, sPluralCategory, iPowerOfTen, "short", sValue);
2548
+ }
2549
+ }
2550
+ if (oIndianBestResult.number) {
2551
+ return oIndianBestResult;
2552
+ } else if (oBestResult.number) {
2553
+ return oBestResult;
2554
+ }
2555
+ return undefined;
2556
+ };
2557
+
2558
+ /**
2559
+ * Updates the number and factor in the given reference object based on the plural category,
2560
+ * power of ten, and style with the best match if one can be found for a given value.
2561
+ *
2562
+ * @param {{number: string, factor: number}} oBestResult
2563
+ * A reference in which the number and factor determined by this function will be stored
2564
+ * @param {"zero"|"one"|"two"|"few"|"many"} sPluralCategory
2565
+ * The plural category
2566
+ * @param {int} iPowerOfTen
2567
+ * The power of ten, max: 100 000 000 000 000
2568
+ * @param {"long"|"short"|"short-indian"} sStyle
2569
+ * The style
2570
+ * @param {string} sValue
2571
+ * The value for which the number and factor has to be determined
2572
+ *
2573
+ * @private
2574
+ */
2575
+ NumberFormat.prototype.updateBestResult = function (oBestResult, sPluralCategory, iPowerOfTen, sStyle, sValue) {
2576
+ let sCldrFormat = this.oLocaleData.getCompactDecimalPattern(sStyle, iPowerOfTen.toString(), sPluralCategory);
2577
+ if (sCldrFormat) {
2578
+ // Note: CLDR uses a non-breaking space and right-to-left mark u+200f in the format string
2579
+ sCldrFormat = FormatUtils.normalize(sCldrFormat, true);
2580
+ //formatString may contain '.' (quoted to differentiate them from decimal separator)
2581
+ //which must be replaced with .
2582
+ sCldrFormat = sCldrFormat.replace(/'.'/g, ".");
2583
+ const aMatch = sCldrFormat.match(rNumPlaceHolder);
2584
+ if (aMatch) {
2585
+ // determine unit -> may be on the beginning e.g. for he
2586
+ const sValueSubString = aMatch[0];
2587
+ const sScalingFactor = sCldrFormat.replace(sValueSubString, "");
2588
+ if (sScalingFactor && sValue.includes(sScalingFactor)) {
2589
+ // parse the number part like every other number and then use the factor to get the real number
2590
+ const sNumber = sValue.replace(sScalingFactor, "");
2591
+ // spanish numbers e.g. for MRD in format for "one" is "00 MRD" therefore factor needs to be
2592
+ // adjusted
2593
+ // german numbers e.g. for Mrd. in format for "one" is "0 Mrd." therefore number does not need to
2594
+ // be adjusted
2595
+ // "0" => magnitude = key
2596
+ // "00" => magnitude = key / 10
2597
+ // "000" => magnitude = key / 100
2598
+ const iFactor = iPowerOfTen * Math.pow(10, 1 - sValueSubString.length);
2599
+ if (oBestResult.number === undefined || sNumber.length < oBestResult.number.length) {
2600
+ oBestResult.number = sNumber;
2601
+ oBestResult.factor = iFactor;
2602
+ }
2603
+ }
2604
+ }
2605
+ }
2606
+ };
2607
+
2608
+ /**
2609
+ * Whether to show the currency code at the end based on the original format options and the global configuration.
2610
+ *
2611
+ * @returns {boolean} Whether to show trailing currency code
2612
+ */
2613
+ NumberFormat.prototype.showTrailingCurrencyCode = function () {
2614
+ const oFormatOptions = this.oOriginalFormatOptions;
2615
+ // use default currency mode if custom pattern is given or currency code shall not be shown
2616
+ if (oFormatOptions?.pattern || oFormatOptions?.currencyCode === false) {
2617
+ return false;
2618
+ }
2619
+ return oFormatOptions?.trailingCurrencyCode !== undefined ? oFormatOptions.trailingCurrencyCode // overwritten by instance configuration
2620
+ : Formatting.getTrailingCurrencyCode();
2621
+ };
2622
+
2623
+ /**
2624
+ * Checks if grouping is performed correctly (decimal separator is not confused with grouping separator).
2625
+ * The examples use the German locale.
2626
+ *
2627
+ * Validity:
2628
+ * * The grouping is valid if there are at least 2 grouping separators present.
2629
+ * Because there can only be one decimal separator, and by writing 2 grouping separators there is no confusion.
2630
+ * E.g. 1.2.3
2631
+ * * The grouping is valid if there is a decimal separator and one grouping separator present.
2632
+ * Because the user wrote both, there cannot be a confusion.
2633
+ * (If it was confused, it has already been taken care by the syntax check.)
2634
+ * E.g. 1.2,3
2635
+ *
2636
+ * Invalidity:
2637
+ * * If there is exactly one grouping separator present, no decimal separator, and the grouping
2638
+ * separator at the most right grouping position is wrong.
2639
+ * E.g. 1.2
2640
+ * E.g. 1.234567
2641
+ *
2642
+ * The grouping is checked even if the groupingEnabled format is set to <code>false</code>, because the
2643
+ * input could be copied from external sources which might have wrong grouping separators.
2644
+ *
2645
+ * The empty grouping separator is ignored and <code>true</code> is returned, because it cannot be validated.
2646
+ *
2647
+ * An additional check is performed which invalidates a wrong number syntax
2648
+ * E.g. 0.123
2649
+ * E.g. -.123
2650
+ *
2651
+ * @param {string} sValueWithGrouping the normalized value which only contains the grouping (e.g. "1.000"),
2652
+ * i.e. the following modifications were already applied:
2653
+ * <ul>
2654
+ * <li>remove percent symbol</li>
2655
+ * <li>remove leading plus</li>
2656
+ * <li>remove whitespaces</li>
2657
+ * <li>remove RTL characters</li>
2658
+ * <li>remove short/long format (e.g. "Mio"/"Million")</li>
2659
+ * <li>resolve lenient symbols</li>
2660
+ * </ul>
2661
+ * This means grouping separators which are space characters or RTL characters are not validated.
2662
+ * @param {object} oOptions the format options, relevant are: groupingSeparator, groupingSize, groupingBaseSize and decimalSeparator
2663
+ * @param {boolean} bScientificNotation is scientific notation, e.g. "1.234e+1"
2664
+ * @returns {boolean} true if the grouping is done correctly, e.g. "1.23" is not grouped correctly for grouping separator "." and groupingSize 3
2665
+ * @private
2666
+ */
2667
+ NumberFormat.prototype._checkGrouping = function (sValueWithGrouping, oOptions, bScientificNotation) {
2668
+ if (oOptions.groupingSeparator && sValueWithGrouping.includes(oOptions.groupingSeparator)) {
2669
+ // All following checks are only done, if the value contains at least one (non-falsy) grouping separator.
2670
+ // The examples below use the German locale:
2671
+ // groupingSeparator: '.'
2672
+ // decimalSeparator: ','
2673
+ // groupingSize: 3
2674
+
2675
+ // remove leading minus sign, it is irrelevant for grouping check
2676
+ // "-123.456" -> "123.456"
2677
+ sValueWithGrouping = sValueWithGrouping.replace(/^-/, "");
2678
+
2679
+ // remove leading zeros before non-zero digits
2680
+ // "001.234" -> "1.234"
2681
+ // "0.234" -> "0.234"
2682
+ sValueWithGrouping = sValueWithGrouping.replace(/^0+(\d)/, "$1");
2683
+
2684
+ // if value still starts with 0, or it starts with a grouping separator, it is invalid
2685
+ // e.g. "0.123", ".123" (invalid)
2686
+ if (sValueWithGrouping.startsWith("0") || sValueWithGrouping.startsWith(oOptions.groupingSeparator)) {
2687
+ return false;
2688
+ }
2689
+
2690
+ // remove scientific notation
2691
+ // "1.234e+1" -> "1.234"
2692
+ if (bScientificNotation) {
2693
+ sValueWithGrouping = sValueWithGrouping.replace(/[eE].*/, "");
2694
+ }
2695
+ var bHasDecimalSeparator = sValueWithGrouping.includes(oOptions.decimalSeparator);
2696
+ // Integer types often have identical decimal and grouping separators configured,
2697
+ // therefore we do not remove the decimals part and validate them as if they would not
2698
+ // have decimals
2699
+ if (oOptions.decimalSeparator === oOptions.groupingSeparator) {
2700
+ bHasDecimalSeparator = false;
2701
+ } else if (bHasDecimalSeparator) {
2702
+ // remove decimals part to be able to validate grouping
2703
+ sValueWithGrouping = sValueWithGrouping.split(oOptions.decimalSeparator)[0];
2704
+ }
2705
+
2706
+ // check if decimal and grouping separator were confused.
2707
+ // This check is performed in addition to stricter grouping validation (strictGroupingValidation)
2708
+ // to reduce the confusion between decimal and grouping separator.
2709
+ // e.g. for "de": 1.234567 (is invalid)
2710
+ // Pre-requisites (examples for "de")
2711
+ // * number has exactly one grouping separator, e.g. "1.23"
2712
+ // since there can be only one decimal separator, if there is exactly one grouping
2713
+ // separator they could have been confused
2714
+ // * number has no decimal separator, e.g. 1.23
2715
+ // if there is a decimal separator and a grouping separator present,
2716
+ // there cannot be a confusion
2717
+ var bHasExactlyOneGroupingSeparator = sValueWithGrouping.split(oOptions.groupingSeparator).length === 2;
2718
+ if (bHasExactlyOneGroupingSeparator && !bHasDecimalSeparator) {
2719
+ // find least-significant ("lowest") grouping separator
2720
+ var iLowestGroupingIndex = sValueWithGrouping.length - sValueWithGrouping.lastIndexOf(oOptions.groupingSeparator);
2721
+ var iBaseGroupSize = oOptions.groupingBaseSize || oOptions.groupingSize;
2722
+ // if least-significant grouping size doesn't match grouping base size, the value is invalid
2723
+ // e.g. 12.34 (invalid)
2724
+ if (iLowestGroupingIndex !== iBaseGroupSize + oOptions.groupingSeparator.length) {
2725
+ return false;
2726
+ }
2727
+ }
2728
+
2729
+ /**
2730
+ * With strictGroupingValidation enabled the behaviour is closer to ABAP, the position
2731
+ * of the grouping separators are validated as well.
2732
+ * e.g. for "de" <code>1.2.3</code> becomes invalid
2733
+ */
2734
+ if (oOptions.strictGroupingValidation) {
2735
+ if (!this._rGrouping) {
2736
+ this._rGrouping = getGroupingRegExp(oOptions.groupingSeparator, oOptions.groupingSize, oOptions.groupingBaseSize || oOptions.groupingSize);
2737
+ }
2738
+
2739
+ // e.g. for "de" with valid grouping separators at the correct position
2740
+ // rGrouping: /^\d+(?:\.?\d{3})*\.?\d{3}$/
2741
+ // sValueWithGrouping: 123 456.789
2742
+ // 123 456 789
2743
+ // 123.456.789
2744
+ // Note: spaces are just there for visual aid.
2745
+ if (!this._rGrouping.test(sValueWithGrouping)) {
2746
+ return false;
2747
+ }
2748
+ }
2749
+ }
2750
+ return true;
2751
+ };
2752
+
2753
+ /**
2754
+ * Checks whether grouping will be enabled for this instance.
2755
+ *
2756
+ * @private
2757
+ */
2758
+ NumberFormat.prototype.checkGroupingFormatOptions = function () {
2759
+ if (this.oFormatOptions.groupingEnabled && this.oFormatOptions.groupingSize <= 0) {
2760
+ Log.warning("Grouping is disabled due to non-positive groupingSize set to '" + this.oFormatOptions.groupingSize + "'.");
2761
+ this.oFormatOptions.groupingEnabled = false;
2762
+ }
2763
+ };
2764
+
2765
+ /**
2766
+ * Whether or not the given value is in scientific notation
2767
+ *
2768
+ * @param {string} sValue string value, e.g. "9e+4"
2769
+ * @returns {boolean} <code>true</code> if it is in scientific notation
2770
+ */
2771
+ function isScientificNotation(sValue) {
2772
+ return sValue.indexOf("e") > 0 || sValue.indexOf("E") > 0;
2773
+ }
2774
+
2775
+ /**
2776
+ * Evaluates if the given number is an integer and returns it.
2777
+ * Otherwise returns <code>undefined</code>
2778
+ *
2779
+ * @param {string} sValue string value, e.g. "9e+4" or "1.2345e+25"
2780
+ * @returns {int} if value can be parsed to integer e.g. 90000, <code>undefined</code> otherwise
2781
+ */
2782
+ function getInteger(sValue) {
2783
+ // when resolving the e-notation check if there is still a dot character present and after the dot character there are no zeros
2784
+ var sResolvedENotation = NumberFormat._shiftDecimalPoint(sValue, 0);
2785
+ if (sResolvedENotation.indexOf(".") > 0 && !rOnlyZeros.test(sResolvedENotation.split(".")[1])) {
2786
+ return undefined;
2787
+ }
2788
+ var fFloat = parseFloat(sResolvedENotation);
2789
+ var sFloat = "" + fFloat;
2790
+
2791
+ // parseFloat() still produces the scientific notation output for bigger values such
2792
+ // as "1.2345e+25".
2793
+ // This conversion is required because parseInt() cannot handle scientific notation with
2794
+ // the mantissa being a floating point number, e.g. "1.2345e+25"
2795
+ if (isScientificNotation(sFloat)) {
2796
+ // retrieve the string value from the given float number
2797
+ // "1.2345e+25" becomes "12345000000000000000000000"
2798
+ sFloat = NumberFormat._shiftDecimalPoint(sFloat, 0);
2799
+ }
2800
+ var iInt = parseInt(sFloat);
2801
+ if (iInt !== fFloat) {
2802
+ return undefined;
2803
+ }
2804
+ return iInt;
2805
+ }
2806
+
2807
+ /**
2808
+ * Rounds the given value by the given number of fraction digits based on the given rounding mode.
2809
+ *
2810
+ * @param {number|string} vValue
2811
+ * The number to be rounded, may be a string or a number; has to be of type number if a custom rounding function
2812
+ * is used
2813
+ * @param {int|string} iMaxFractionDigits
2814
+ * The maximum number of fraction digits
2815
+ * @param {sap.ui.core.format.NumberFormat.RoundingMode|function(number,int):number} vRoundingMode
2816
+ * The rounding mode or a custom function for rounding which is called with the number and the number of decimal
2817
+ * digits that should be reserved; <b>using a function is deprecated since 1.121.0</b>; string based numbers are
2818
+ * not rounded via this custom function.
2819
+ * @returns {number|string}
2820
+ * The rounded value; the returned type is the same as the type of the given <code>vValue</code>
2821
+ */
2822
+ function rounding(vValue, iMaxFractionDigits, vRoundingMode) {
2823
+ vRoundingMode = vRoundingMode || NumberFormat.RoundingMode.HALF_AWAY_FROM_ZERO;
2824
+ iMaxFractionDigits = parseInt(iMaxFractionDigits);
2825
+
2826
+ // only round if it is required (number of fraction digits is bigger than the maxFractionDigits option)
2827
+ var sValue = "" + vValue;
2828
+ if (!isScientificNotation(sValue)) {
2829
+ var iIndexOfPoint = sValue.indexOf(".");
2830
+ if (iIndexOfPoint < 0) {
2831
+ return vValue;
2832
+ }
2833
+ if (sValue.substring(iIndexOfPoint + 1).length <= iMaxFractionDigits) {
2834
+ if (typeof vValue === "string") {
2835
+ vValue = NumberFormat._shiftDecimalPoint(vValue, 0, true);
2836
+ }
2837
+ return vValue;
2838
+ }
2839
+ }
2840
+ if (typeof vRoundingMode === "function") {
2841
+ // Support custom function for rounding the number
2842
+ vValue = vRoundingMode(vValue, iMaxFractionDigits);
2843
+ } else {
2844
+ // The NumberFormat.RoundingMode had all values in lower case before and later changed all values to upper case
2845
+ // to match the key according to the UI5 guideline for defining enum. Therefore it's needed to support both
2846
+ // lower and upper cases. Here checks whether the value has only lower case letters and converts it all to upper
2847
+ // case if so.
2848
+ if (vRoundingMode.match(/^[a-z_]+$/)) {
2849
+ vRoundingMode = vRoundingMode.toUpperCase();
2850
+ }
2851
+
2852
+ // 1. Move the decimal point to right by maxFactionDigits; e.g. 1.005 with maxFractionDigits 2 => 100.5
2853
+ vValue = NumberFormat._shiftDecimalPoint(vValue, iMaxFractionDigits, true);
2854
+ // 2. Use the rounding function to round the first digit after decimal point; e.g. ceil(100.5) => 101
2855
+ vValue = mRoundingFunction[vRoundingMode](vValue);
2856
+ // 3. Finally move the decimal point back to the original position; e.g. by 2 digits => 1.01
2857
+ vValue = NumberFormat._shiftDecimalPoint(vValue, -iMaxFractionDigits, true);
2858
+ if (typeof vValue === "string") {
2859
+ vValue = vValue.replace(rRemoveMinusFromZero, "$1");
2860
+ }
2861
+ }
2862
+ return vValue;
2863
+ }
2864
+ function quote(sRegex) {
2865
+ return sRegex.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
2866
+ }
2867
+ function getDecimals(fValue, iPrecision) {
2868
+ var iIntegerDigits = Math.floor(Math.log(Math.abs(fValue)) / Math.LN10);
2869
+ return Math.max(0, iPrecision - iIntegerDigits - 1);
2870
+ }
2871
+
2872
+ /**
2873
+ * Returns the CLDR code and the number value by checking each pattern and finding the best
2874
+ * match. The best match means most of the unit value matched and the number match is shorter.
2875
+ *
2876
+ * Example input: "12km" matches for the unit postfix "m" and the resulting number value is
2877
+ * "12k" while the unit postfix "km" results in "12". Since unit postfix "km" returns a shorter
2878
+ * result it is considered the best match.
2879
+ *
2880
+ * Note: the CLDR data is not distinct in its patterns.
2881
+ * For example "100 c" could be in "en_gb" either 100 units of "volume-cup" or
2882
+ * "duration-century" both having the same pattern "{0} c". Therefore best matches will be
2883
+ * returned in an array.
2884
+ *
2885
+ * @param {object} mUnitPatterns The unit patterns
2886
+ * @param {string} sValue The given value
2887
+ * @param {boolean} bShowNumber Whether the number is shown
2888
+ * @param {string} sLanguageTag The language tag of the locale for language dependent processing
2889
+ * @return {{cldrCode: string[], numberValue: (string|undefined)}}
2890
+ * An object containing the unit codes and the number value
2891
+ */
2892
+ function parseNumberAndUnit(mUnitPatterns, sValue, bShowNumber, sLanguageTag) {
2893
+ var bContainsNumber,
2894
+ sKey,
2895
+ sNumber,
2896
+ iNumberPatternIndex,
2897
+ sPostfix,
2898
+ sPostfixLowerCase,
2899
+ sPrefix,
2900
+ sPrefixLowerCase,
2901
+ sUnitCode,
2902
+ sUnitPattern,
2903
+ sUnitPatternLowerCase,
2904
+ oBestMatch = {
2905
+ numberValue: undefined,
2906
+ cldrCode: []
2907
+ },
2908
+ aCaseInsensitiveMatches = [],
2909
+ bCaseSensitive = true,
2910
+ bPatternMatchWasCaseSensitive = true,
2911
+ iShortestNumberPartLength = Number.POSITIVE_INFINITY,
2912
+ bShortestNumberPartWasCaseSensitive = true,
2913
+ sValueLowerCase = sValue.toLocaleLowerCase(sLanguageTag);
2914
+ for (sUnitCode in mUnitPatterns) {
2915
+ for (sKey in mUnitPatterns[sUnitCode]) {
2916
+ //use only unit patterns
2917
+ if (!sKey.startsWith("unitPattern")) {
2918
+ continue;
2919
+ }
2920
+ sUnitPattern = FormatUtils.normalize(mUnitPatterns[sUnitCode][sKey]);
2921
+
2922
+ // IMPORTANT:
2923
+ // To increase performance we are using native string operations instead of regex,
2924
+ // to match the patterns against the input.
2925
+ //
2926
+ // sample input: e.g. "mi 12 tsd. ms²"
2927
+ // unit pattern: e.g. "mi {0} ms²"
2928
+
2929
+ // The smallest resulting number (String length) will be the best match
2930
+ iNumberPatternIndex = sUnitPattern.indexOf("{0}");
2931
+ bContainsNumber = iNumberPatternIndex > -1;
2932
+ if (bContainsNumber && !bShowNumber) {
2933
+ sUnitPattern = sUnitPattern.replace("{0}", "").trim();
2934
+ bContainsNumber = false;
2935
+ }
2936
+ sNumber = undefined;
2937
+ bCaseSensitive = true;
2938
+ if (bContainsNumber) {
2939
+ sPrefix = sUnitPattern.substring(0, iNumberPatternIndex);
2940
+ sPrefixLowerCase = sPrefix.toLocaleLowerCase(sLanguageTag);
2941
+ sPostfix = sUnitPattern.substring(iNumberPatternIndex + "{0}".length);
2942
+ sPostfixLowerCase = sPostfix.toLocaleLowerCase(sLanguageTag);
2943
+ if (sValue.startsWith(sPrefix) && sValue.endsWith(sPostfix)) {
2944
+ sNumber = sValue.substring(sPrefix.length, sValue.length - sPostfix.length);
2945
+ } else if (sValueLowerCase.startsWith(sPrefixLowerCase) && sValueLowerCase.endsWith(sPostfixLowerCase)) {
2946
+ bCaseSensitive = false;
2947
+ sNumber = sValue.substring(sPrefixLowerCase.length, sValueLowerCase.length - sPostfixLowerCase.length);
2948
+ }
2949
+ if (sNumber) {
2950
+ //get the match with the shortest result.
2951
+ // e.g. 1km -> (.+)m -> "1k" -> length 2
2952
+ // e.g. 1km -> (.+)km -> "1" -> length 1
2953
+
2954
+ if (sNumber.length < iShortestNumberPartLength) {
2955
+ iShortestNumberPartLength = sNumber.length;
2956
+ bShortestNumberPartWasCaseSensitive = bCaseSensitive;
2957
+ oBestMatch.numberValue = sNumber;
2958
+ oBestMatch.cldrCode = [sUnitCode];
2959
+ } else if (sNumber.length === iShortestNumberPartLength && oBestMatch.cldrCode.indexOf(sUnitCode) === -1) {
2960
+ if (bCaseSensitive && !bShortestNumberPartWasCaseSensitive) {
2961
+ oBestMatch.numberValue = sNumber;
2962
+ oBestMatch.cldrCode = [sUnitCode];
2963
+ bShortestNumberPartWasCaseSensitive = true;
2964
+ } else if (bCaseSensitive || !bShortestNumberPartWasCaseSensitive) {
2965
+ //ambiguous unit (en locale)
2966
+ // e.g. 100 c -> (.+) c -> duration-century
2967
+ // e.g. 100 c -> (.+) c -> volume-cup
2968
+ oBestMatch.cldrCode.push(sUnitCode);
2969
+ }
2970
+ }
2971
+ }
2972
+ } else {
2973
+ sUnitPatternLowerCase = sUnitPattern.toLocaleLowerCase(sLanguageTag);
2974
+ if (sUnitPattern === sValue || sUnitPatternLowerCase === sValueLowerCase) {
2975
+ if (bShowNumber) {
2976
+ //for units which do not have a number representation, get the number from the pattern
2977
+ if (sKey.endsWith("-zero")) {
2978
+ sNumber = "0";
2979
+ } else if (sKey.endsWith("-one")) {
2980
+ sNumber = "1";
2981
+ } else if (sKey.endsWith("-two")) {
2982
+ sNumber = "2";
2983
+ }
2984
+ if (sUnitPattern === sValue) {
2985
+ oBestMatch.numberValue = sNumber;
2986
+ oBestMatch.cldrCode = [sUnitCode];
2987
+ return oBestMatch;
2988
+ } else if (!oBestMatch.cldrCode.includes(sUnitCode)) {
2989
+ bPatternMatchWasCaseSensitive = false;
2990
+ oBestMatch.numberValue = sNumber;
2991
+ oBestMatch.cldrCode.push(sUnitCode);
2992
+ }
2993
+ } else if (oBestMatch.cldrCode.indexOf(sUnitCode) === -1) {
2994
+ if (sUnitPattern === sValue) {
2995
+ oBestMatch.cldrCode.push(sUnitCode);
2996
+ } else if (!aCaseInsensitiveMatches.includes(sUnitCode)) {
2997
+ aCaseInsensitiveMatches.push(sUnitCode);
2998
+ }
2999
+ }
3000
+ }
3001
+ }
3002
+ }
3003
+ }
3004
+ if ((!bShortestNumberPartWasCaseSensitive || !bPatternMatchWasCaseSensitive) && oBestMatch.cldrCode.length > 1) {
3005
+ oBestMatch.numberValue = undefined;
3006
+ }
3007
+ if (!bShowNumber && !oBestMatch.cldrCode.length) {
3008
+ oBestMatch.cldrCode = aCaseInsensitiveMatches;
3009
+ }
3010
+ return oBestMatch;
3011
+ }
3012
+
3013
+ /**
3014
+ * Identify the longest match between a sub string of <code>sValue</code>
3015
+ * and one of the values of the <code>mCollection</code> map.
3016
+ *
3017
+ * @param {string} sValue
3018
+ * The string value which is checked for all currency codes/symbols
3019
+ * @param {Object<string, string>} mCollection
3020
+ * An object mapping a currency code to a either a currency symbol or the currency code itself
3021
+ * @param {boolean} bCaseInsensitive Whether case insensitive matches are allowed
3022
+ * @return {{code: string, recognizedCurrency: string, symbol: string}}
3023
+ * An object with the code, the recognized currency and the symbol found in the given value;
3024
+ * an empty object in case of either conflicting case insensitive matches, or no match
3025
+ */
3026
+ function findLongestMatch(sValue, mCollection, bCaseInsensitive) {
3027
+ var sCode,
3028
+ sCurCode,
3029
+ sCurSymbol,
3030
+ sCurSymbolToUpperCase,
3031
+ iIndex,
3032
+ sLanguageTag,
3033
+ sRecognizedCurrency,
3034
+ sValueSubStr,
3035
+ bDuplicate = false,
3036
+ bExactMatch = false,
3037
+ sSymbol = "";
3038
+ for (sCurCode in mCollection) {
3039
+ sCurSymbol = mCollection[sCurCode];
3040
+ if (!sCurSymbol) {
3041
+ continue;
3042
+ }
3043
+ sCurSymbol = FormatUtils.normalize(sCurSymbol);
3044
+ if (sValue.indexOf(sCurSymbol) >= 0 && sSymbol.length <= sCurSymbol.length) {
3045
+ sCode = sCurCode;
3046
+ bDuplicate = false;
3047
+ bExactMatch = true;
3048
+ sSymbol = sCurSymbol;
3049
+ sRecognizedCurrency = sCurSymbol;
3050
+ } else if (bCaseInsensitive) {
3051
+ sLanguageTag = Localization.getLanguageTag().toString();
3052
+ sCurSymbolToUpperCase = sCurSymbol.toLocaleUpperCase(sLanguageTag);
3053
+ iIndex = sValue.toLocaleUpperCase(sLanguageTag).indexOf(sCurSymbolToUpperCase);
3054
+ if (iIndex >= 0) {
3055
+ if (sSymbol.length === sCurSymbol.length && !bExactMatch) {
3056
+ bDuplicate = true;
3057
+ } else if (sSymbol.length < sCurSymbol.length) {
3058
+ sValueSubStr = sValue.substring(iIndex, iIndex + sCurSymbol.length);
3059
+ if (sValueSubStr.toLocaleUpperCase(sLanguageTag) === sCurSymbolToUpperCase) {
3060
+ sCode = sCurCode;
3061
+ bDuplicate = false;
3062
+ bExactMatch = false;
3063
+ sSymbol = sCurSymbol;
3064
+ sRecognizedCurrency = sValueSubStr;
3065
+ }
3066
+ }
3067
+ }
3068
+ }
3069
+ }
3070
+ if (bDuplicate || !sCode) {
3071
+ return {};
3072
+ }
3073
+ return {
3074
+ code: sCode,
3075
+ recognizedCurrency: sRecognizedCurrency,
3076
+ symbol: sSymbol
3077
+ };
3078
+ }
3079
+
3080
+ /**
3081
+ * Parses number and currency.
3082
+ *
3083
+ * Search for the currency symbol first, looking for the longest match. In case no currency
3084
+ * symbol is found, search for a three letter currency code.
3085
+ *
3086
+ * @param {object} oConfig
3087
+ * @param {string} oConfig.value the string value to be parse
3088
+ * @param {object} oConfig.currencySymbols the list of currency symbols to respect during parsing
3089
+ * @param {object} oConfig.customCurrencyCodes the list of currency codes used for parsing in case no symbol was found in the value string
3090
+ * @param {object} oConfig.duplicatedSymbols a list of all duplicated symbols;
3091
+ * In case oFormatOptions.currencyCode is set to false and the value string contains a duplicated symbol,
3092
+ * the value is not parsable. The result will be a parsed number and <code>undefined</code> for the currency.
3093
+ * @param {boolean} oConfig.customCurrenciesAvailable a flag to mark if custom currencies are available on the instance
3094
+ *
3095
+ * @private
3096
+ * @returns {object|undefined} returns object containing numberValue and currencyCode or undefined
3097
+ */
3098
+ function parseNumberAndCurrency(oConfig) {
3099
+ var aIsoMatches,
3100
+ sValue = oConfig.value;
3101
+
3102
+ // Search for known symbols (longest match)
3103
+ // no distinction between default and custom currencies
3104
+ var oMatch = findLongestMatch(sValue, oConfig.currencySymbols);
3105
+
3106
+ // Search for currency code
3107
+ if (!oMatch.code) {
3108
+ // before falling back to the default regex for ISO codes we check the
3109
+ // codes for custom currencies (if defined)
3110
+ oMatch = findLongestMatch(sValue, oConfig.customCurrencyCodes, true);
3111
+ if (!oMatch.code && !oConfig.customCurrenciesAvailable) {
3112
+ // Match 3-letter iso code
3113
+ aIsoMatches = sValue.match(/(^[A-Z]{3}|[A-Z]{3}$)/i);
3114
+ oMatch.code = aIsoMatches && aIsoMatches[0].toLocaleUpperCase(Localization.getLanguageTag().toString());
3115
+ oMatch.recognizedCurrency = aIsoMatches && aIsoMatches[0];
3116
+ }
3117
+ }
3118
+
3119
+ // Remove symbol/code from value
3120
+ if (oMatch.code) {
3121
+ var iLastCodeIndex = oMatch.recognizedCurrency.length - 1;
3122
+ var sLastCodeChar = oMatch.recognizedCurrency.charAt(iLastCodeIndex);
3123
+ var iDelimiterPos;
3124
+ var rValidDelimiters = /[\-\s]+/;
3125
+
3126
+ // Check whether last character of matched code is a number
3127
+ if (/\d$/.test(sLastCodeChar)) {
3128
+ // Check whether parse string starts with the matched code
3129
+ if (sValue.startsWith(oMatch.recognizedCurrency)) {
3130
+ iDelimiterPos = iLastCodeIndex + 1;
3131
+ // \s matching any whitespace character including
3132
+ // non-breaking ws and invisible non-breaking ws
3133
+ if (!rValidDelimiters.test(sValue.charAt(iDelimiterPos))) {
3134
+ return undefined;
3135
+ }
3136
+ }
3137
+ // Check whether first character of matched code is a number
3138
+ } else if (/^\d/.test(oMatch.recognizedCurrency)) {
3139
+ // Check whether parse string ends with the matched code
3140
+ if (sValue.endsWith(oMatch.recognizedCurrency)) {
3141
+ iDelimiterPos = sValue.indexOf(oMatch.recognizedCurrency) - 1;
3142
+ if (!rValidDelimiters.test(sValue.charAt(iDelimiterPos))) {
3143
+ return undefined;
3144
+ }
3145
+ }
3146
+ }
3147
+ sValue = sValue.replace(oMatch.recognizedCurrency, "");
3148
+ }
3149
+
3150
+ // Set currency code to undefined, as the defined custom currencies
3151
+ // contain multiple currencies having the same symbol.
3152
+ var bDuplicatedSymbolFound = false;
3153
+ if (oConfig.duplicatedSymbols && oConfig.duplicatedSymbols[oMatch.symbol]) {
3154
+ oMatch.code = undefined;
3155
+ bDuplicatedSymbolFound = true;
3156
+ Log.error("The parsed currency symbol '" + oMatch.symbol + "' is defined multiple " + "times in custom currencies.Therefore the result is not distinct.");
3157
+ }
3158
+ return {
3159
+ numberValue: sValue,
3160
+ currencyCode: oMatch.code || undefined,
3161
+ duplicatedSymbolFound: bDuplicatedSymbolFound
3162
+ };
3163
+ }
3164
+ export default NumberFormat;