1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4//
5
6#include <assert.h>
7#include <string.h>
8#include <vector>
9
10#include "icushim.h"
11#include "locale.hpp"
12#include "holders.h"
13
14// invariant character definitions used by ICU
15#define UCHAR_CURRENCY ((UChar)0x00A4) // international currency
16#define UCHAR_SPACE ((UChar)0x0020) // space
17#define UCHAR_NBSPACE ((UChar)0x00A0) // space
18#define UCHAR_DIGIT ((UChar)0x0023) // '#'
19#define UCHAR_SEMICOLON ((UChar)0x003B) // ';'
20#define UCHAR_MINUS ((UChar)0x002D) // '-'
21#define UCHAR_PERCENT ((UChar)0x0025) // '%'
22#define UCHAR_OPENPAREN ((UChar)0x0028) // '('
23#define UCHAR_CLOSEPAREN ((UChar)0x0029) // ')'
24
25#define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0]))
26
27// Enum that corresponds to managed enum CultureData.LocaleNumberData.
28// The numeric values of the enum members match their Win32 counterparts.
29enum LocaleNumberData : int32_t
30{
31 LanguageId = 0x00000001,
32 MeasurementSystem = 0x0000000D,
33 FractionalDigitsCount = 0x00000011,
34 NegativeNumberFormat = 0x00001010,
35 MonetaryFractionalDigitsCount = 0x00000019,
36 PositiveMonetaryNumberFormat = 0x0000001B,
37 NegativeMonetaryNumberFormat = 0x0000001C,
38 FirstDayofWeek = 0x0000100C,
39 FirstWeekOfYear = 0x0000100D,
40 ReadingLayout = 0x00000070,
41 NegativePercentFormat = 0x00000074,
42 PositivePercentFormat = 0x00000075,
43 Digit = 0x00000010,
44 Monetary = 0x00000018
45};
46
47// Enum that corresponds to managed enum System.Globalization.CalendarWeekRule
48enum CalendarWeekRule : int32_t
49{
50 FirstDay = 0,
51 FirstFullWeek = 1,
52 FirstFourDayWeek = 2
53};
54
55/*
56Function:
57NormalizeNumericPattern
58
59Returns a numeric string pattern in a format that we can match against the
60appropriate managed pattern.
61*/
62std::string NormalizeNumericPattern(const UChar* srcPattern, bool isNegative)
63{
64 // A srcPattern example: "#,##0.00 C;(#,##0.00 C)" but where C is the
65 // international currency symbol (UCHAR_CURRENCY)
66 // The positive pattern comes first, then an optional negative pattern
67 // separated by a semicolon
68 // A destPattern example: "(C n)" where C represents the currency symbol, and
69 // n is the number
70 std::string destPattern;
71
72 int iStart = 0;
73 int iEnd = u_strlen(srcPattern);
74 int32_t iNegativePatternStart = -1;
75
76 for (int i = iStart; i < iEnd; i++)
77 {
78 if (srcPattern[i] == ';')
79 {
80 iNegativePatternStart = i;
81 }
82 }
83
84 if (iNegativePatternStart >= 0)
85 {
86 if (isNegative)
87 {
88 iStart = iNegativePatternStart + 1;
89 }
90 else
91 {
92 iEnd = iNegativePatternStart - 1;
93 }
94 }
95
96 bool minusAdded = false;
97 bool digitAdded = false;
98 bool currencyAdded = false;
99 bool spaceAdded = false;
100
101 for (int i = iStart; i <= iEnd; i++)
102 {
103 UChar ch = srcPattern[i];
104 switch (ch)
105 {
106 case UCHAR_DIGIT:
107 if (!digitAdded)
108 {
109 digitAdded = true;
110 destPattern.push_back('n');
111 }
112 break;
113
114 case UCHAR_CURRENCY:
115 if (!currencyAdded)
116 {
117 currencyAdded = true;
118 destPattern.push_back('C');
119 }
120 break;
121
122 case UCHAR_SPACE:
123 case UCHAR_NBSPACE:
124 if (!spaceAdded)
125 {
126 spaceAdded = true;
127 destPattern.push_back(' ');
128 }
129 else
130 {
131 assert(false);
132 }
133 break;
134
135 case UCHAR_MINUS:
136 case UCHAR_OPENPAREN:
137 case UCHAR_CLOSEPAREN:
138 minusAdded = true;
139 destPattern.push_back(static_cast<char>(ch));
140 break;
141
142 case UCHAR_PERCENT:
143 destPattern.push_back('%');
144 break;
145 }
146 }
147
148 // if there is no negative subpattern, the ICU convention is to prefix the
149 // minus sign
150 if (isNegative && !minusAdded)
151 {
152 destPattern.insert(destPattern.begin(), '-');
153 }
154
155 return destPattern;
156}
157
158/*
159Function:
160GetNumericPattern
161
162Determines the pattern from the decimalFormat and returns the matching pattern's
163index from patterns[].
164Returns index -1 if no pattern is found.
165*/
166int GetNumericPattern(const UNumberFormat* pNumberFormat, const char* patterns[], int patternsCount, bool isNegative)
167{
168 const int INVALID_FORMAT = -1;
169 const int MAX_DOTNET_NUMERIC_PATTERN_LENGTH = 6; // example: "(C n)" plus terminator
170
171 UErrorCode ignore = U_ZERO_ERROR;
172 int32_t icuPatternLength = unum_toPattern(pNumberFormat, false, nullptr, 0, &ignore);
173
174 std::vector<UChar> icuPattern(icuPatternLength + 1, '\0');
175
176 UErrorCode err = U_ZERO_ERROR;
177
178 unum_toPattern(pNumberFormat, false, icuPattern.data(), icuPattern.size(), &err);
179
180 assert(U_SUCCESS(err));
181
182 std::string normalizedPattern = NormalizeNumericPattern(icuPattern.data(), isNegative);
183
184 assert(normalizedPattern.length() > 0);
185 assert(normalizedPattern.length() < MAX_DOTNET_NUMERIC_PATTERN_LENGTH);
186
187 if (normalizedPattern.length() == 0 || normalizedPattern.length() >= MAX_DOTNET_NUMERIC_PATTERN_LENGTH)
188 {
189 return INVALID_FORMAT;
190 }
191
192 for (int i = 0; i < patternsCount; i++)
193 {
194 if (strcmp(normalizedPattern.c_str(), patterns[i]) == 0)
195 {
196 return i;
197 }
198 };
199
200 assert(false); // should have found a valid pattern
201 return INVALID_FORMAT;
202}
203
204/*
205Function:
206GetCurrencyNegativePattern
207
208Implementation of NumberFormatInfo.CurrencyNegativePattern.
209Returns the pattern index.
210*/
211int GetCurrencyNegativePattern(const char* locale)
212{
213 const int DEFAULT_VALUE = 0;
214 static const char* Patterns[] = {"(Cn)",
215 "-Cn",
216 "C-n",
217 "Cn-",
218 "(nC)",
219 "-nC",
220 "n-C",
221 "nC-",
222 "-n C",
223 "-C n",
224 "n C-",
225 "C n-",
226 "C -n",
227 "n- C",
228 "(C n)",
229 "(n C)"};
230 UErrorCode status = U_ZERO_ERROR;
231
232 UNumberFormat* pFormat = unum_open(UNUM_CURRENCY, nullptr, 0, locale, nullptr, &status);
233 UNumberFormatHolder formatHolder(pFormat, status);
234
235 assert(U_SUCCESS(status));
236
237 if (U_SUCCESS(status))
238 {
239 int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), true);
240 if (value >= 0)
241 {
242 return value;
243 }
244 }
245
246 return DEFAULT_VALUE;
247}
248
249/*
250Function:
251GetCurrencyPositivePattern
252
253Implementation of NumberFormatInfo.CurrencyPositivePattern.
254Returns the pattern index.
255*/
256int GetCurrencyPositivePattern(const char* locale)
257{
258 const int DEFAULT_VALUE = 0;
259 static const char* Patterns[] = {"Cn", "nC", "C n", "n C"};
260 UErrorCode status = U_ZERO_ERROR;
261
262 UNumberFormat* pFormat = unum_open(UNUM_CURRENCY, nullptr, 0, locale, nullptr, &status);
263 UNumberFormatHolder formatHolder(pFormat, status);
264
265 assert(U_SUCCESS(status));
266
267 if (U_SUCCESS(status))
268 {
269 int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), false);
270 if (value >= 0)
271 {
272 return value;
273 }
274 }
275
276 return DEFAULT_VALUE;
277}
278
279/*
280Function:
281GetNumberNegativePattern
282
283Implementation of NumberFormatInfo.NumberNegativePattern.
284Returns the pattern index.
285*/
286int GetNumberNegativePattern(const char* locale)
287{
288 const int DEFAULT_VALUE = 1;
289 static const char* Patterns[] = {"(n)", "-n", "- n", "n-", "n -"};
290 UErrorCode status = U_ZERO_ERROR;
291
292 UNumberFormat* pFormat = unum_open(UNUM_DECIMAL, nullptr, 0, locale, nullptr, &status);
293 UNumberFormatHolder formatHolder(pFormat, status);
294
295 assert(U_SUCCESS(status));
296
297 if (U_SUCCESS(status))
298 {
299 int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), true);
300 if (value >= 0)
301 {
302 return value;
303 }
304 }
305
306 return DEFAULT_VALUE;
307}
308
309/*
310Function:
311GetPercentNegativePattern
312
313Implementation of NumberFormatInfo.PercentNegativePattern.
314Returns the pattern index.
315*/
316int GetPercentNegativePattern(const char* locale)
317{
318 const int DEFAULT_VALUE = 0;
319 static const char* Patterns[] = {
320 "-n %", "-n%", "-%n", "%-n", "%n-", "n-%", "n%-", "-% n", "n %-", "% n-", "% -n", "n- %"};
321 UErrorCode status = U_ZERO_ERROR;
322
323 UNumberFormat* pFormat = unum_open(UNUM_PERCENT, nullptr, 0, locale, nullptr, &status);
324 UNumberFormatHolder formatHolder(pFormat, status);
325
326 assert(U_SUCCESS(status));
327
328 if (U_SUCCESS(status))
329 {
330 int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), true);
331 if (value >= 0)
332 {
333 return value;
334 }
335 }
336
337 return DEFAULT_VALUE;
338}
339
340/*
341Function:
342GetPercentPositivePattern
343
344Implementation of NumberFormatInfo.PercentPositivePattern.
345Returns the pattern index.
346*/
347int GetPercentPositivePattern(const char* locale)
348{
349 const int DEFAULT_VALUE = 0;
350 static const char* Patterns[] = {"n %", "n%", "%n", "% n"};
351 UErrorCode status = U_ZERO_ERROR;
352
353 UNumberFormat* pFormat = unum_open(UNUM_PERCENT, nullptr, 0, locale, nullptr, &status);
354 UNumberFormatHolder formatHolder(pFormat, status);
355
356 assert(U_SUCCESS(status));
357
358 if (U_SUCCESS(status))
359 {
360 int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), false);
361 if (value >= 0)
362 {
363 return value;
364 }
365 }
366
367 return DEFAULT_VALUE;
368}
369
370/*
371Function:
372GetMeasurementSystem
373
374Obtains the measurement system for the local, determining if US or metric.
375Returns 1 for US, 0 otherwise.
376*/
377UErrorCode GetMeasurementSystem(const char* locale, int32_t* value)
378{
379 UErrorCode status = U_ZERO_ERROR;
380
381 UMeasurementSystem measurementSystem = ulocdata_getMeasurementSystem(locale, &status);
382 if (U_SUCCESS(status))
383 {
384 *value = (measurementSystem == UMeasurementSystem::UMS_US) ? 1 : 0;
385 }
386
387 return status;
388}
389
390/*
391PAL Function:
392GetLocaleInfoInt
393
394Obtains integer locale information
395Returns 1 for success, 0 otherwise
396*/
397extern "C" int32_t GlobalizationNative_GetLocaleInfoInt(
398 const UChar* localeName, LocaleNumberData localeNumberData, int32_t* value)
399{
400 UErrorCode status = U_ZERO_ERROR;
401 char locale[ULOC_FULLNAME_CAPACITY];
402 GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &status);
403
404 if (U_FAILURE(status))
405 {
406 return UErrorCodeToBool(U_ILLEGAL_ARGUMENT_ERROR);
407 }
408
409 switch (localeNumberData)
410 {
411 case LanguageId:
412 *value = uloc_getLCID(locale);
413 break;
414 case MeasurementSystem:
415 status = GetMeasurementSystem(locale, value);
416 break;
417 case FractionalDigitsCount:
418 {
419 UNumberFormat* numformat = unum_open(UNUM_DECIMAL, NULL, 0, locale, NULL, &status);
420 if (U_SUCCESS(status))
421 {
422 *value = unum_getAttribute(numformat, UNUM_MAX_FRACTION_DIGITS);
423 unum_close(numformat);
424 }
425 break;
426 }
427 case NegativeNumberFormat:
428 *value = GetNumberNegativePattern(locale);
429 break;
430 case MonetaryFractionalDigitsCount:
431 {
432 UNumberFormat* numformat = unum_open(UNUM_CURRENCY, NULL, 0, locale, NULL, &status);
433 if (U_SUCCESS(status))
434 {
435 *value = unum_getAttribute(numformat, UNUM_MAX_FRACTION_DIGITS);
436 unum_close(numformat);
437 }
438 break;
439 }
440 case PositiveMonetaryNumberFormat:
441 *value = GetCurrencyPositivePattern(locale);
442 break;
443 case NegativeMonetaryNumberFormat:
444 *value = GetCurrencyNegativePattern(locale);
445 break;
446 case FirstWeekOfYear:
447 {
448 // corresponds to DateTimeFormat.CalendarWeekRule
449 UCalendar* pCal = ucal_open(nullptr, 0, locale, UCAL_TRADITIONAL, &status);
450 UCalendarHolder calHolder(pCal, status);
451
452 if (U_SUCCESS(status))
453 {
454 // values correspond to LOCALE_IFIRSTWEEKOFYEAR
455 int minDaysInWeek = ucal_getAttribute(pCal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
456 if (minDaysInWeek == 1)
457 {
458 *value = CalendarWeekRule::FirstDay;
459 }
460 else if (minDaysInWeek == 7)
461 {
462 *value = CalendarWeekRule::FirstFullWeek;
463 }
464 else if (minDaysInWeek >= 4)
465 {
466 *value = CalendarWeekRule::FirstFourDayWeek;
467 }
468 else
469 {
470 status = U_UNSUPPORTED_ERROR;
471 }
472 }
473 break;
474 }
475 case ReadingLayout:
476 {
477 // coresponds to values 0 and 1 in LOCALE_IREADINGLAYOUT (values 2 and 3 not
478 // used in coreclr)
479 // 0 - Left to right (such as en-US)
480 // 1 - Right to left (such as arabic locales)
481 ULayoutType orientation = uloc_getCharacterOrientation(locale, &status);
482 // alternative implementation in ICU 54+ is uloc_isRightToLeft() which
483 // also supports script tags in locale
484 if (U_SUCCESS(status))
485 {
486 *value = (orientation == ULOC_LAYOUT_RTL) ? 1 : 0;
487 }
488 break;
489 }
490 case FirstDayofWeek:
491 {
492 UCalendar* pCal = ucal_open(nullptr, 0, locale, UCAL_TRADITIONAL, &status);
493 UCalendarHolder calHolder(pCal, status);
494
495 if (U_SUCCESS(status))
496 {
497 *value = ucal_getAttribute(pCal, UCAL_FIRST_DAY_OF_WEEK) - 1; // .NET is 0-based and ICU is 1-based
498 }
499 break;
500 }
501 case NegativePercentFormat:
502 *value = GetPercentNegativePattern(locale);
503 break;
504 case PositivePercentFormat:
505 *value = GetPercentPositivePattern(locale);
506 break;
507 default:
508 status = U_UNSUPPORTED_ERROR;
509 assert(false);
510 break;
511 }
512
513 return UErrorCodeToBool(status);
514}
515
516/*
517PAL Function:
518GetLocaleInfoGroupingSizes
519
520Obtains grouping sizes for decimal and currency
521Returns 1 for success, 0 otherwise
522*/
523extern "C" int32_t GlobalizationNative_GetLocaleInfoGroupingSizes(
524 const UChar* localeName, LocaleNumberData localeGroupingData, int32_t* primaryGroupSize, int32_t* secondaryGroupSize)
525{
526 UErrorCode status = U_ZERO_ERROR;
527 char locale[ULOC_FULLNAME_CAPACITY];
528 GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &status);
529
530 if (U_FAILURE(status))
531 {
532 return UErrorCodeToBool(U_ILLEGAL_ARGUMENT_ERROR);
533 }
534
535 UNumberFormatStyle style;
536 switch (localeGroupingData)
537 {
538 case Digit:
539 style = UNUM_DECIMAL;
540 break;
541 case Monetary:
542 style = UNUM_CURRENCY;
543 break;
544 default:
545 return UErrorCodeToBool(U_UNSUPPORTED_ERROR);
546 }
547
548 UNumberFormat* numformat = unum_open(style, NULL, 0, locale, NULL, &status);
549 if (U_SUCCESS(status))
550 {
551 *primaryGroupSize = unum_getAttribute(numformat, UNUM_GROUPING_SIZE);
552 *secondaryGroupSize = unum_getAttribute(numformat, UNUM_SECONDARY_GROUPING_SIZE);
553 unum_close(numformat);
554 }
555
556 return UErrorCodeToBool(status);
557}
558