1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 ********************************************************************************
5 * Copyright (C) 2005-2016, International Business Machines
6 * Corporation and others. All Rights Reserved.
7 ********************************************************************************
11 ********************************************************************************
14 #include "unicode/utypes.h"
16 #if U_PLATFORM_USES_ONLY_WIN32_API
18 #if !UCONFIG_NO_FORMATTING
22 #include "unicode/format.h"
23 #include "unicode/numfmt.h"
24 #include "unicode/locid.h"
25 #include "unicode/ustring.h"
31 #ifndef WIN32_LEAN_AND_MEAN
32 # define WIN32_LEAN_AND_MEAN
47 CURRENCYFMTW currency
;
50 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(Win32NumberFormat
)
52 #define NEW_ARRAY(type,count) (type *) uprv_malloc((count) * sizeof(type))
53 #define DELETE_ARRAY(array) uprv_free((void *) (array))
55 #define STACK_BUFFER_SIZE 32
58 * Turns a string of the form "3;2;0" into the grouping UINT
59 * needed for NUMBERFMT and CURRENCYFMT. If the string does not
60 * end in ";0" then the return value should be multiplied by 10.
61 * (e.g. "3" => 30, "3;2" => 320)
63 static UINT
getGrouping(const wchar_t *grouping
)
68 for (s
= grouping
; *s
!= L
'\0'; s
+= 1) {
69 if (*s
> L
'0' && *s
< L
'9') {
70 g
= g
* 10 + (*s
- L
'0');
71 } else if (*s
!= L
';') {
83 static void getNumberFormat(NUMBERFMTW
*fmt
, const wchar_t *windowsLocaleName
)
87 GetLocaleInfoEx(windowsLocaleName
, LOCALE_RETURN_NUMBER
|LOCALE_IDIGITS
, (LPWSTR
) &fmt
->NumDigits
, sizeof(UINT
));
88 GetLocaleInfoEx(windowsLocaleName
, LOCALE_RETURN_NUMBER
|LOCALE_ILZERO
, (LPWSTR
) &fmt
->LeadingZero
, sizeof(UINT
));
90 GetLocaleInfoEx(windowsLocaleName
, LOCALE_SGROUPING
, (LPWSTR
)buf
, 10);
91 fmt
->Grouping
= getGrouping(buf
);
93 fmt
->lpDecimalSep
= NEW_ARRAY(wchar_t, 6);
94 GetLocaleInfoEx(windowsLocaleName
, LOCALE_SDECIMAL
, fmt
->lpDecimalSep
, 6);
96 fmt
->lpThousandSep
= NEW_ARRAY(wchar_t, 6);
97 GetLocaleInfoEx(windowsLocaleName
, LOCALE_STHOUSAND
, fmt
->lpThousandSep
, 6);
99 GetLocaleInfoEx(windowsLocaleName
, LOCALE_RETURN_NUMBER
|LOCALE_INEGNUMBER
, (LPWSTR
) &fmt
->NegativeOrder
, sizeof(UINT
));
102 static void freeNumberFormat(NUMBERFMTW
*fmt
)
105 DELETE_ARRAY(fmt
->lpThousandSep
);
106 DELETE_ARRAY(fmt
->lpDecimalSep
);
110 static void getCurrencyFormat(CURRENCYFMTW
*fmt
, const wchar_t *windowsLocaleName
)
114 GetLocaleInfoEx(windowsLocaleName
, LOCALE_RETURN_NUMBER
|LOCALE_ICURRDIGITS
, (LPWSTR
) &fmt
->NumDigits
, sizeof(UINT
));
115 GetLocaleInfoEx(windowsLocaleName
, LOCALE_RETURN_NUMBER
|LOCALE_ILZERO
, (LPWSTR
) &fmt
->LeadingZero
, sizeof(UINT
));
117 GetLocaleInfoEx(windowsLocaleName
, LOCALE_SMONGROUPING
, (LPWSTR
)buf
, sizeof(buf
));
118 fmt
->Grouping
= getGrouping(buf
);
120 fmt
->lpDecimalSep
= NEW_ARRAY(wchar_t, 6);
121 GetLocaleInfoEx(windowsLocaleName
, LOCALE_SMONDECIMALSEP
, fmt
->lpDecimalSep
, 6);
123 fmt
->lpThousandSep
= NEW_ARRAY(wchar_t, 6);
124 GetLocaleInfoEx(windowsLocaleName
, LOCALE_SMONTHOUSANDSEP
, fmt
->lpThousandSep
, 6);
126 GetLocaleInfoEx(windowsLocaleName
, LOCALE_RETURN_NUMBER
|LOCALE_INEGCURR
, (LPWSTR
) &fmt
->NegativeOrder
, sizeof(UINT
));
127 GetLocaleInfoEx(windowsLocaleName
, LOCALE_RETURN_NUMBER
|LOCALE_ICURRENCY
, (LPWSTR
) &fmt
->PositiveOrder
, sizeof(UINT
));
129 fmt
->lpCurrencySymbol
= NEW_ARRAY(wchar_t, 8);
130 GetLocaleInfoEx(windowsLocaleName
, LOCALE_SCURRENCY
, (LPWSTR
) fmt
->lpCurrencySymbol
, 8);
133 static void freeCurrencyFormat(CURRENCYFMTW
*fmt
)
136 DELETE_ARRAY(fmt
->lpCurrencySymbol
);
137 DELETE_ARRAY(fmt
->lpThousandSep
);
138 DELETE_ARRAY(fmt
->lpDecimalSep
);
142 // TODO: This is copied in both winnmfmt.cpp and windtfmt.cpp, but really should
143 // be factored out into a common helper for both.
144 static UErrorCode
GetEquivalentWindowsLocaleName(const Locale
& locale
, UnicodeString
** buffer
)
146 UErrorCode status
= U_ZERO_ERROR
;
147 char asciiBCP47Tag
[LOCALE_NAME_MAX_LENGTH
] = {};
149 // Convert from names like "en_CA" and "de_DE@collation=phonebook" to "en-CA" and "de-DE-u-co-phonebk".
150 (void) uloc_toLanguageTag(locale
.getName(), asciiBCP47Tag
, UPRV_LENGTHOF(asciiBCP47Tag
), FALSE
, &status
);
152 if (U_SUCCESS(status
))
154 // Need it to be UTF-16, not 8-bit
155 // TODO: This seems like a good thing for a helper
156 wchar_t bcp47Tag
[LOCALE_NAME_MAX_LENGTH
] = {};
158 for (i
= 0; i
< UPRV_LENGTHOF(bcp47Tag
); i
++)
160 if (asciiBCP47Tag
[i
] == '\0')
166 // normally just copy the character
167 bcp47Tag
[i
] = static_cast<wchar_t>(asciiBCP47Tag
[i
]);
171 // Ensure it's null terminated
172 if (i
< (UPRV_LENGTHOF(bcp47Tag
) - 1))
179 bcp47Tag
[UPRV_LENGTHOF(bcp47Tag
) - 1] = L
'\0';
183 wchar_t windowsLocaleName
[LOCALE_NAME_MAX_LENGTH
] = {};
185 // Note: On Windows versions below 10, there is no support for locale name aliases.
186 // This means that it will fail for locales where ICU has a completely different
187 // name (like ku vs ckb), and it will also not work for alternate sort locale
188 // names like "de-DE-u-co-phonebk".
190 // TODO: We could add some sort of exception table for cases like ku vs ckb.
192 int length
= ResolveLocaleName(bcp47Tag
, windowsLocaleName
, UPRV_LENGTHOF(windowsLocaleName
));
196 *buffer
= new UnicodeString(windowsLocaleName
);
200 status
= U_UNSUPPORTED_ERROR
;
206 Win32NumberFormat::Win32NumberFormat(const Locale
&locale
, UBool currency
, UErrorCode
&status
)
207 : NumberFormat(), fCurrency(currency
), fFormatInfo(NULL
), fFractionDigitsSet(FALSE
), fWindowsLocaleName(nullptr)
209 if (!U_FAILURE(status
)) {
210 fLCID
= locale
.getLCID();
212 GetEquivalentWindowsLocaleName(locale
, &fWindowsLocaleName
);
213 // Note: In the previous code, it would look up the LCID for the locale, and if
214 // the locale was not recognized then it would get an LCID of 0, which is a
215 // synonym for LOCALE_USER_DEFAULT on Windows.
216 // If the above method fails, then fWindowsLocaleName will remain as nullptr, and
217 // then we will pass nullptr to API GetLocaleInfoEx, which is the same as passing
218 // LOCALE_USER_DEFAULT.
220 // Resolve actual locale to be used later
221 UErrorCode tmpsts
= U_ZERO_ERROR
;
222 char tmpLocID
[ULOC_FULLNAME_CAPACITY
];
223 int32_t len
= uloc_getLocaleForLCID(fLCID
, tmpLocID
, UPRV_LENGTHOF(tmpLocID
) - 1, &tmpsts
);
224 if (U_SUCCESS(tmpsts
)) {
226 fLocale
= Locale((const char*)tmpLocID
);
229 const wchar_t *localeName
= nullptr;
231 if (fWindowsLocaleName
!= nullptr)
233 localeName
= reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName
->getTerminatedBuffer()));
236 fFormatInfo
= (FormatInfo
*)uprv_malloc(sizeof(FormatInfo
));
239 getCurrencyFormat(&fFormatInfo
->currency
, localeName
);
241 getNumberFormat(&fFormatInfo
->number
, localeName
);
246 Win32NumberFormat::Win32NumberFormat(const Win32NumberFormat
&other
)
247 : NumberFormat(other
), fFormatInfo((FormatInfo
*)uprv_malloc(sizeof(FormatInfo
)))
249 if (fFormatInfo
!= NULL
) {
250 uprv_memset(fFormatInfo
, 0, sizeof(*fFormatInfo
));
255 Win32NumberFormat::~Win32NumberFormat()
257 if (fFormatInfo
!= NULL
) {
259 freeCurrencyFormat(&fFormatInfo
->currency
);
261 freeNumberFormat(&fFormatInfo
->number
);
264 uprv_free(fFormatInfo
);
266 delete fWindowsLocaleName
;
269 Win32NumberFormat
&Win32NumberFormat::operator=(const Win32NumberFormat
&other
)
271 NumberFormat::operator=(other
);
273 this->fCurrency
= other
.fCurrency
;
274 this->fLocale
= other
.fLocale
;
275 this->fLCID
= other
.fLCID
;
276 this->fFractionDigitsSet
= other
.fFractionDigitsSet
;
277 this->fWindowsLocaleName
= other
.fWindowsLocaleName
== NULL
? NULL
: new UnicodeString(*other
.fWindowsLocaleName
);
279 const wchar_t *localeName
= nullptr;
281 if (fWindowsLocaleName
!= nullptr)
283 localeName
= reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName
->getTerminatedBuffer()));
287 freeCurrencyFormat(&fFormatInfo
->currency
);
288 getCurrencyFormat(&fFormatInfo
->currency
, localeName
);
290 freeNumberFormat(&fFormatInfo
->number
);
291 getNumberFormat(&fFormatInfo
->number
, localeName
);
297 Win32NumberFormat
*Win32NumberFormat::clone() const
299 return new Win32NumberFormat(*this);
302 UnicodeString
& Win32NumberFormat::format(double number
, UnicodeString
& appendTo
, FieldPosition
& /* pos */) const
304 return format(getMaximumFractionDigits(), appendTo
, L
"%.16f", number
);
307 UnicodeString
& Win32NumberFormat::format(int32_t number
, UnicodeString
& appendTo
, FieldPosition
& /* pos */) const
309 return format(getMinimumFractionDigits(), appendTo
, L
"%I32d", number
);
312 UnicodeString
& Win32NumberFormat::format(int64_t number
, UnicodeString
& appendTo
, FieldPosition
& /* pos */) const
314 return format(getMinimumFractionDigits(), appendTo
, L
"%I64d", number
);
317 void Win32NumberFormat::parse(const UnicodeString
& text
, Formattable
& result
, ParsePosition
& parsePosition
) const
319 UErrorCode status
= U_ZERO_ERROR
;
320 NumberFormat
*nf
= fCurrency
? NumberFormat::createCurrencyInstance(fLocale
, status
) : NumberFormat::createInstance(fLocale
, status
);
322 nf
->parse(text
, result
, parsePosition
);
325 void Win32NumberFormat::setMaximumFractionDigits(int32_t newValue
)
327 fFractionDigitsSet
= TRUE
;
328 NumberFormat::setMaximumFractionDigits(newValue
);
331 void Win32NumberFormat::setMinimumFractionDigits(int32_t newValue
)
333 fFractionDigitsSet
= TRUE
;
334 NumberFormat::setMinimumFractionDigits(newValue
);
337 UnicodeString
&Win32NumberFormat::format(int32_t numDigits
, UnicodeString
&appendTo
, const wchar_t *fmt
, ...) const
339 wchar_t nStackBuffer
[STACK_BUFFER_SIZE
];
340 wchar_t *nBuffer
= nStackBuffer
;
346 /* Due to the arguments causing a result to be <= 23 characters (+2 for NULL and minus),
347 we don't need to reallocate the buffer. */
349 result
= _vsnwprintf(nBuffer
, STACK_BUFFER_SIZE
, fmt
, args
);
352 /* Just to make sure of the above statement, we add this assert */
353 U_ASSERT(result
>=0);
354 // The following code is not used because _vscwprintf isn't available on MinGW at the moment.
359 newLength = _vscwprintf(fmt, args);
362 nBuffer = NEW_ARRAY(UChar, newLength + 1);
365 result = _vsnwprintf(nBuffer, newLength + 1, fmt, args);
369 // vswprintf is sensitive to the locale set by setlocale. For some locales
370 // it doesn't use "." as the decimal separator, which is what GetNumberFormatW
371 // and GetCurrencyFormatW both expect to see.
373 // To fix this, we scan over the string and replace the first non-digits, except
374 // for a leading "-", with a "."
376 // Note: (nBuffer[0] == L'-') will evaluate to 1 if there is a leading '-' in the
377 // number, and 0 otherwise.
378 for (wchar_t *p
= &nBuffer
[nBuffer
[0] == L
'-']; *p
!= L
'\0'; p
+= 1) {
379 if (*p
< L
'0' || *p
> L
'9') {
385 wchar_t stackBuffer
[STACK_BUFFER_SIZE
];
386 wchar_t *buffer
= stackBuffer
;
387 FormatInfo formatInfo
;
389 formatInfo
= *fFormatInfo
;
392 const wchar_t *localeName
= nullptr;
394 if (fWindowsLocaleName
!= nullptr)
396 localeName
= reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName
->getTerminatedBuffer()));
400 if (fFractionDigitsSet
) {
401 formatInfo
.currency
.NumDigits
= (UINT
) numDigits
;
404 if (!isGroupingUsed()) {
405 formatInfo
.currency
.Grouping
= 0;
408 result
= GetCurrencyFormatEx(localeName
, 0, nBuffer
, &formatInfo
.currency
, buffer
, STACK_BUFFER_SIZE
);
411 DWORD lastError
= GetLastError();
413 if (lastError
== ERROR_INSUFFICIENT_BUFFER
) {
414 int newLength
= GetCurrencyFormatEx(localeName
, 0, nBuffer
, &formatInfo
.currency
, NULL
, 0);
416 buffer
= NEW_ARRAY(wchar_t, newLength
);
418 GetCurrencyFormatEx(localeName
, 0, nBuffer
, &formatInfo
.currency
, buffer
, newLength
);
422 if (fFractionDigitsSet
) {
423 formatInfo
.number
.NumDigits
= (UINT
) numDigits
;
426 if (!isGroupingUsed()) {
427 formatInfo
.number
.Grouping
= 0;
430 result
= GetNumberFormatEx(localeName
, 0, nBuffer
, &formatInfo
.number
, buffer
, STACK_BUFFER_SIZE
);
433 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER
) {
434 int newLength
= GetNumberFormatEx(localeName
, 0, nBuffer
, &formatInfo
.number
, NULL
, 0);
436 buffer
= NEW_ARRAY(wchar_t, newLength
);
438 GetNumberFormatEx(localeName
, 0, nBuffer
, &formatInfo
.number
, buffer
, newLength
);
443 appendTo
.append((UChar
*)buffer
, (int32_t) wcslen(buffer
));
445 if (buffer
!= stackBuffer
) {
446 DELETE_ARRAY(buffer
);
449 /*if (nBuffer != nStackBuffer) {
450 DELETE_ARRAY(nBuffer);
458 #endif /* #if !UCONFIG_NO_FORMATTING */
460 #endif // U_PLATFORM_USES_ONLY_WIN32_API