1 // © 2018 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3
4 #include "unicode/utypes.h"
5
6 #if !UCONFIG_NO_FORMATTING
7
8 // Allow implicit conversion from char16_t* to UnicodeString for this file:
9 // Helpful in toString methods and elsewhere.
10 #define UNISTR_FROM_STRING_EXPLICIT
11
12 #include <stdbool.h>
13 #include <stdio.h>
14 #include "unicode/unumberformatter.h"
15 #include "unicode/usimplenumberformatter.h"
16 #include "unicode/umisc.h"
17 #include "unicode/unum.h"
18 #include "unicode/ustring.h"
19 #include "cformtst.h"
20 #include "cintltst.h"
21 #include "cmemory.h"
22
23 static void TestSkeletonFormatToString(void);
24
25 static void TestSkeletonFormatToFields(void);
26
27 static void TestExampleCode(void);
28
29 static void TestSimpleNumberFormatterExample(void);
30
31 static void TestSimpleNumberFormatterFull(void);
32
33 static void TestFormattedValue(void);
34
35 static void TestSkeletonParseError(void);
36
37 static void TestToDecimalNumber(void);
38
39 static void TestPerUnitInArabic(void);
40
41 static void Test21674_State(void);
42
43 static void TestNegativeDegrees(void);
44
45 void addUNumberFormatterTest(TestNode** root);
46
47 #define TESTCASE(x) addTest(root, &x, "tsformat/unumberformatter/" #x)
48
addUNumberFormatterTest(TestNode ** root)49 void addUNumberFormatterTest(TestNode** root) {
50 TESTCASE(TestSkeletonFormatToString);
51 TESTCASE(TestSkeletonFormatToFields);
52 TESTCASE(TestExampleCode);
53 TESTCASE(TestSimpleNumberFormatterExample);
54 TESTCASE(TestSimpleNumberFormatterFull);
55 TESTCASE(TestFormattedValue);
56 TESTCASE(TestSkeletonParseError);
57 TESTCASE(TestToDecimalNumber);
58 TESTCASE(TestPerUnitInArabic);
59 TESTCASE(Test21674_State);
60 TESTCASE(TestNegativeDegrees);
61 }
62
63
64 #define CAPACITY 30
65
TestSkeletonFormatToString(void)66 static void TestSkeletonFormatToString(void) {
67 UErrorCode ec = U_ZERO_ERROR;
68 UChar buffer[CAPACITY];
69 UFormattedNumber* result = NULL;
70
71 // setup:
72 UNumberFormatter* f = unumf_openForSkeletonAndLocale(
73 u"precision-integer currency/USD sign-accounting", -1, "en", &ec);
74 assertSuccessCheck("Should create without error", &ec, true);
75 result = unumf_openResult(&ec);
76 assertSuccess("Should create result without error", &ec);
77
78 // int64 test:
79 unumf_formatInt(f, -444444, result, &ec);
80 // Missing data will give a U_MISSING_RESOURCE_ERROR here.
81 if (assertSuccessCheck("Should format integer without error", &ec, true)) {
82 unumf_resultToString(result, buffer, CAPACITY, &ec);
83 assertSuccess("Should print string to buffer without error", &ec);
84 assertUEquals("Should produce expected string result", u"($444,444)", buffer);
85
86 // double test:
87 unumf_formatDouble(f, -5142.3, result, &ec);
88 assertSuccess("Should format double without error", &ec);
89 unumf_resultToString(result, buffer, CAPACITY, &ec);
90 assertSuccess("Should print string to buffer without error", &ec);
91 assertUEquals("Should produce expected string result", u"($5,142)", buffer);
92
93 // decnumber test:
94 unumf_formatDecimal(f, "9.876E2", -1, result, &ec);
95 assertSuccess("Should format decimal without error", &ec);
96 unumf_resultToString(result, buffer, CAPACITY, &ec);
97 assertSuccess("Should print string to buffer without error", &ec);
98 assertUEquals("Should produce expected string result", u"$988", buffer);
99 }
100
101 // cleanup:
102 unumf_closeResult(result);
103 unumf_close(f);
104 }
105
106
TestSkeletonFormatToFields(void)107 static void TestSkeletonFormatToFields(void) {
108 UErrorCode ec = U_ZERO_ERROR;
109 UFieldPositionIterator* ufpositer = NULL;
110
111 // setup:
112 UNumberFormatter* uformatter = unumf_openForSkeletonAndLocale(
113 u".00 measure-unit/length-meter sign-always", -1, "en", &ec);
114 assertSuccessCheck("Should create without error", &ec, true);
115 UFormattedNumber* uresult = unumf_openResult(&ec);
116 assertSuccess("Should create result without error", &ec);
117 unumf_formatInt(uformatter, 9876543210L, uresult, &ec); // "+9,876,543,210.00 m"
118 if (assertSuccessCheck("unumf_formatInt() failed", &ec, true)) {
119
120 // field position test:
121 UFieldPosition ufpos = {UNUM_DECIMAL_SEPARATOR_FIELD, 0, 0};
122 unumf_resultNextFieldPosition(uresult, &ufpos, &ec);
123 assertIntEquals("Field position should be correct", 14, ufpos.beginIndex);
124 assertIntEquals("Field position should be correct", 15, ufpos.endIndex);
125
126 // field position iterator test:
127 ufpositer = ufieldpositer_open(&ec);
128 if (assertSuccessCheck("Should create iterator without error", &ec, true)) {
129
130 unumf_resultGetAllFieldPositions(uresult, ufpositer, &ec);
131 static const UFieldPosition expectedFields[] = {
132 // Field, begin index, end index
133 {UNUM_SIGN_FIELD, 0, 1},
134 {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3},
135 {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7},
136 {UNUM_GROUPING_SEPARATOR_FIELD, 10, 11},
137 {UNUM_INTEGER_FIELD, 1, 14},
138 {UNUM_DECIMAL_SEPARATOR_FIELD, 14, 15},
139 {UNUM_FRACTION_FIELD, 15, 17},
140 {UNUM_MEASURE_UNIT_FIELD, 18, 19}
141 };
142 UFieldPosition actual;
143 for (int32_t i = 0; i < (int32_t)(sizeof(expectedFields) / sizeof(*expectedFields)); i++) {
144 // Iterate using the UFieldPosition to hold state...
145 UFieldPosition expected = expectedFields[i];
146 actual.field = ufieldpositer_next(ufpositer, &actual.beginIndex, &actual.endIndex);
147 assertTrue("Should not return a negative index yet", actual.field >= 0);
148 if (expected.field != actual.field) {
149 log_err(
150 "FAIL: iteration %d; expected field %d; got %d\n", i, expected.field, actual.field);
151 }
152 if (expected.beginIndex != actual.beginIndex) {
153 log_err(
154 "FAIL: iteration %d; expected beginIndex %d; got %d\n",
155 i,
156 expected.beginIndex,
157 actual.beginIndex);
158 }
159 if (expected.endIndex != actual.endIndex) {
160 log_err(
161 "FAIL: iteration %d; expected endIndex %d; got %d\n",
162 i,
163 expected.endIndex,
164 actual.endIndex);
165 }
166 }
167 actual.field = ufieldpositer_next(ufpositer, &actual.beginIndex, &actual.endIndex);
168 assertTrue("No more fields; should return a negative index", actual.field < 0);
169
170 // next field iteration:
171 actual.field = UNUM_GROUPING_SEPARATOR_FIELD;
172 actual.beginIndex = 0;
173 actual.endIndex = 0;
174 int32_t i = 1;
175 while (unumf_resultNextFieldPosition(uresult, &actual, &ec)) {
176 UFieldPosition expected = expectedFields[i++];
177 assertIntEquals("Grouping separator begin index", expected.beginIndex, actual.beginIndex);
178 assertIntEquals("Grouping separator end index", expected.endIndex, actual.endIndex);
179 }
180 assertIntEquals("Should have seen all grouping separators", 4, i);
181 }
182 }
183
184 // cleanup:
185 unumf_closeResult(uresult);
186 unumf_close(uformatter);
187 ufieldpositer_close(ufpositer);
188 }
189
190
TestExampleCode(void)191 static void TestExampleCode(void) {
192 // This is the example code given in unumberformatter.h.
193
194 // Setup:
195 UErrorCode ec = U_ZERO_ERROR;
196 UNumberFormatter* uformatter = unumf_openForSkeletonAndLocale(u"precision-integer", -1, "en", &ec);
197 UFormattedNumber* uresult = unumf_openResult(&ec);
198 UChar* buffer = NULL;
199 assertSuccessCheck("There should not be a failure in the example code", &ec, true);
200
201 // Format a double:
202 unumf_formatDouble(uformatter, 5142.3, uresult, &ec);
203 if (assertSuccessCheck("There should not be a failure in the example code", &ec, true)) {
204
205 // Export the string to a malloc'd buffer:
206 int32_t len = unumf_resultToString(uresult, NULL, 0, &ec);
207 assertTrue("No buffer yet", ec == U_BUFFER_OVERFLOW_ERROR);
208 ec = U_ZERO_ERROR;
209 buffer = (UChar*) uprv_malloc((len+1)*sizeof(UChar));
210 unumf_resultToString(uresult, buffer, len+1, &ec);
211 assertSuccess("There should not be a failure in the example code", &ec);
212 assertUEquals("Should produce expected string result", u"5,142", buffer);
213 }
214
215 // Cleanup:
216 unumf_close(uformatter);
217 unumf_closeResult(uresult);
218 uprv_free(buffer);
219 }
220
221
TestSimpleNumberFormatterExample(void)222 static void TestSimpleNumberFormatterExample(void) {
223 // This is the example in usimplenumberformatter.h
224 UErrorCode ec = U_ZERO_ERROR;
225 USimpleNumberFormatter* uformatter = usnumf_openForLocale("bn", &ec);
226 USimpleNumber* unumber = usnum_openForInt64(1000007, &ec);
227 UFormattedNumber* uresult = unumf_openResult(&ec);
228 usnumf_format(uformatter, unumber, uresult, &ec);
229 int32_t len;
230 const UChar* str = ufmtval_getString(unumf_resultAsValue(uresult, &ec), &len, &ec);
231 if (assertSuccess("Formatting end-to-end 1", &ec)) {
232 assertUEquals("Should produce a result in Bangla digits", u"১০,০০,০০৭", str);
233 }
234
235 // Cleanup:
236 unumf_closeResult(uresult);
237 usnum_close(unumber);
238 usnumf_close(uformatter);
239 }
240
241
TestSimpleNumberFormatterFull(void)242 static void TestSimpleNumberFormatterFull(void) {
243 UErrorCode ec = U_ZERO_ERROR;
244 USimpleNumberFormatter* uformatter = usnumf_openForLocaleAndGroupingStrategy("de-CH", UNUM_GROUPING_ON_ALIGNED, &ec);
245 UFormattedNumber* uresult = unumf_openResult(&ec);
246
247 usnumf_formatInt64(uformatter, 4321, uresult, &ec);
248 int32_t len;
249 const UChar* str = str = ufmtval_getString(unumf_resultAsValue(uresult, &ec), &len, &ec);
250 if (assertSuccess("Formatting end-to-end 2", &ec)) {
251 assertUEquals("Should produce a result with Swiss symbols", u"4’321", str);
252 }
253
254 USimpleNumber* unumber = usnum_openForInt64(1000007, &ec);
255 usnum_setToInt64(unumber, 98765, &ec);
256 usnum_multiplyByPowerOfTen(unumber, -2, &ec);
257 usnum_roundTo(unumber, -1, UNUM_ROUND_HALFDOWN, &ec);
258 usnum_setMaximumIntegerDigits(unumber, 1, &ec);
259 usnum_setMinimumIntegerDigits(unumber, 4, &ec);
260 usnum_setMinimumFractionDigits(unumber, 3, &ec);
261 usnum_setSign(unumber, UNUM_SIMPLE_NUMBER_PLUS_SIGN, &ec);
262
263 usnumf_format(uformatter, unumber, uresult, &ec);
264 str = ufmtval_getString(unumf_resultAsValue(uresult, &ec), &len, &ec);
265 if (assertSuccess("Formatting end-to-end 3", &ec)) {
266 assertUEquals("Should produce a result with mutated number", u"+0’007.600", str);
267 }
268
269 // Cleanup:
270 unumf_closeResult(uresult);
271 usnum_close(unumber);
272 usnumf_close(uformatter);
273 }
274
275
TestFormattedValue(void)276 static void TestFormattedValue(void) {
277 UErrorCode ec = U_ZERO_ERROR;
278 UNumberFormatter* uformatter = unumf_openForSkeletonAndLocale(
279 u".00 compact-short", -1, "en", &ec);
280 assertSuccessCheck("Should create without error", &ec, true);
281 UFormattedNumber* uresult = unumf_openResult(&ec);
282 assertSuccess("Should create result without error", &ec);
283
284 unumf_formatInt(uformatter, 55000, uresult, &ec); // "55.00 K"
285 if (assertSuccessCheck("Should format without error", &ec, true)) {
286 const UFormattedValue* fv = unumf_resultAsValue(uresult, &ec);
287 assertSuccess("Should convert without error", &ec);
288 static const UFieldPosition expectedFieldPositions[] = {
289 // field, begin index, end index
290 {UNUM_INTEGER_FIELD, 0, 2},
291 {UNUM_DECIMAL_SEPARATOR_FIELD, 2, 3},
292 {UNUM_FRACTION_FIELD, 3, 5},
293 {UNUM_COMPACT_FIELD, 5, 6}};
294 checkFormattedValue(
295 "FormattedNumber as FormattedValue",
296 fv,
297 u"55.00K",
298 UFIELD_CATEGORY_NUMBER,
299 expectedFieldPositions,
300 UPRV_LENGTHOF(expectedFieldPositions));
301 }
302
303 // cleanup:
304 unumf_closeResult(uresult);
305 unumf_close(uformatter);
306 }
307
308
TestSkeletonParseError(void)309 static void TestSkeletonParseError(void) {
310 UErrorCode ec = U_ZERO_ERROR;
311 UNumberFormatter* uformatter;
312 UParseError perror;
313
314 // The UParseError can be null. The following should not segfault.
315 uformatter = unumf_openForSkeletonAndLocaleWithError(
316 u".00 measure-unit/typo", -1, "en", NULL, &ec);
317 unumf_close(uformatter);
318
319 // Now test the behavior.
320 ec = U_ZERO_ERROR;
321 uformatter = unumf_openForSkeletonAndLocaleWithError(
322 u".00 measure-unit/typo", -1, "en", &perror, &ec);
323
324 assertIntEquals("Should have set error code", U_NUMBER_SKELETON_SYNTAX_ERROR, ec);
325 assertIntEquals("Should have correct skeleton error offset", 17, perror.offset);
326 assertUEquals("Should have correct pre context", u"0 measure-unit/", perror.preContext);
327 assertUEquals("Should have correct post context", u"typo", perror.postContext);
328
329 // cleanup:
330 unumf_close(uformatter);
331 }
332
333
TestToDecimalNumber(void)334 static void TestToDecimalNumber(void) {
335 UErrorCode ec = U_ZERO_ERROR;
336 UNumberFormatter* uformatter = unumf_openForSkeletonAndLocale(
337 u"currency/USD",
338 -1,
339 "en-US",
340 &ec);
341 assertSuccessCheck("Should create without error", &ec, true);
342 UFormattedNumber* uresult = unumf_openResult(&ec);
343 assertSuccess("Should create result without error", &ec);
344
345 unumf_formatDouble(uformatter, 3.0, uresult, &ec);
346 const UChar* str = ufmtval_getString(unumf_resultAsValue(uresult, &ec), NULL, &ec);
347 assertSuccessCheck("Formatting should succeed", &ec, true);
348 assertUEquals("Should produce expected string result", u"$3.00", str);
349
350 char buffer[CAPACITY];
351
352 int32_t len = unumf_resultToDecimalNumber(uresult, buffer, CAPACITY, &ec);
353 assertIntEquals("Length should be as expected", strlen(buffer), len);
354 assertEquals("Decimal should be as expected", "3", buffer);
355
356 // cleanup:
357 unumf_closeResult(uresult);
358 unumf_close(uformatter);
359 }
360
361
TestPerUnitInArabic(void)362 static void TestPerUnitInArabic(void) {
363 const char* simpleMeasureUnits[] = {
364 "area-acre",
365 "digital-bit",
366 "digital-byte",
367 "temperature-celsius",
368 "length-centimeter",
369 "duration-day",
370 "angle-degree",
371 "temperature-fahrenheit",
372 "volume-fluid-ounce",
373 "length-foot",
374 "volume-gallon",
375 "digital-gigabit",
376 "digital-gigabyte",
377 "mass-gram",
378 "area-hectare",
379 "duration-hour",
380 "length-inch",
381 "digital-kilobit",
382 "digital-kilobyte",
383 "mass-kilogram",
384 "length-kilometer",
385 "volume-liter",
386 "digital-megabit",
387 "digital-megabyte",
388 "length-meter",
389 "length-mile",
390 "length-mile-scandinavian",
391 "volume-milliliter",
392 "length-millimeter",
393 "duration-millisecond",
394 "duration-minute",
395 "duration-month",
396 "mass-ounce",
397 "concentr-percent",
398 "digital-petabyte",
399 "mass-pound",
400 "duration-second",
401 "mass-stone",
402 "digital-terabit",
403 "digital-terabyte",
404 "duration-week",
405 "length-yard",
406 "duration-year"
407 };
408 #define BUFFER_LEN 256
409 char buffer[BUFFER_LEN];
410 UChar ubuffer[BUFFER_LEN];
411 const char* locale = "ar";
412 UErrorCode status = U_ZERO_ERROR;
413 UFormattedNumber* formatted = unumf_openResult(&status);
414 if (U_FAILURE(status)) {
415 log_err("FAIL: unumf_openResult failed");
416 return;
417 }
418 for(int32_t i=0; i < UPRV_LENGTHOF(simpleMeasureUnits); ++i) {
419 for(int32_t j=0; j < UPRV_LENGTHOF(simpleMeasureUnits); ++j) {
420 status = U_ZERO_ERROR;
421 snprintf(buffer, sizeof(buffer), "measure-unit/%s per-measure-unit/%s",
422 simpleMeasureUnits[i], simpleMeasureUnits[j]);
423 int32_t outputlen = 0;
424 u_strFromUTF8(ubuffer, BUFFER_LEN, &outputlen, buffer, (int32_t)strlen(buffer), &status);
425 if (U_FAILURE(status)) {
426 log_err("FAIL u_strFromUTF8: %s = %s ( %s )\n", locale, buffer,
427 u_errorName(status));
428 }
429 UNumberFormatter* nf = unumf_openForSkeletonAndLocale(
430 ubuffer, outputlen, locale, &status);
431 if (U_FAILURE(status)) {
432 log_err("FAIL unumf_openForSkeletonAndLocale: %s = %s ( %s )\n",
433 locale, buffer, u_errorName(status));
434 } else {
435 unumf_formatDouble(nf, 1, formatted, &status);
436 if (U_FAILURE(status)) {
437 log_err("FAIL unumf_formatDouble: %s = %s ( %s )\n",
438 locale, buffer, u_errorName(status));
439 }
440 }
441 unumf_close(nf);
442 }
443 }
444 unumf_closeResult(formatted);
445 }
446
447
Test21674_State(void)448 static void Test21674_State(void) {
449 UErrorCode status = U_ZERO_ERROR;
450 UNumberFormatter* nf = NULL;
451 UFormattedNumber* result = NULL;
452
453 nf = unumf_openForSkeletonAndLocale(u"precision-increment/0.05/w", -1, "en", &status);
454 if (!assertSuccess("unumf_openForSkeletonAndLocale", &status)) { goto cleanup; }
455
456 result = unumf_openResult(&status);
457 if (!assertSuccess("unumf_openResult", &status)) { goto cleanup; }
458
459 typedef struct TestCase {
460 double num;
461 const UChar* expected;
462 } TestCase;
463 TestCase cases[] = {
464 { 1.975, u"2" },
465 { 1.97, u"1.95" },
466 { 1.975, u"2" },
467 };
468 for (int i=0; i<3; i++) {
469 unumf_formatDouble(nf, cases[i].num, result, &status);
470 if (!assertSuccess("unumf_formatDouble", &status)) { goto cleanup; }
471
472 const UFormattedValue* formattedValue = unumf_resultAsValue(result, &status);
473 if (!assertSuccess("unumf_resultAsValue", &status)) { goto cleanup; }
474
475 int32_t length;
476 const UChar* str = ufmtval_getString(formattedValue, &length, &status);
477 if (!assertSuccess("ufmtval_getString", &status)) { goto cleanup; }
478
479 char message[] = {i + '0', '\0'};
480 assertUEquals(message, cases[i].expected, str);
481 }
482
483 cleanup:
484 unumf_close(nf);
485 unumf_closeResult(result);
486 }
487
488 // Test for ICU-22105
TestNegativeDegrees(void)489 static void TestNegativeDegrees(void) {
490 typedef struct {
491 const UChar* skeleton;
492 double value;
493 const UChar* expectedResult;
494 } TestCase;
495
496 TestCase testCases[] = {
497 { u"measure-unit/temperature-celsius unit-width-short", 0, u"0°C" },
498 { u"measure-unit/temperature-celsius unit-width-short usage/default", 0, u"32°F" },
499 { u"measure-unit/temperature-celsius unit-width-short usage/weather", 0, u"32°F" },
500
501 { u"measure-unit/temperature-celsius unit-width-short", -1, u"-1°C" },
502 { u"measure-unit/temperature-celsius unit-width-short usage/default", -1, u"30°F" },
503 { u"measure-unit/temperature-celsius unit-width-short usage/weather", -1, u"30°F" }
504 };
505
506 for (int32_t i = 0; i < UPRV_LENGTHOF(testCases); i++) {
507 UErrorCode err = U_ZERO_ERROR;
508 UNumberFormatter* nf = unumf_openForSkeletonAndLocale(testCases[i].skeleton, -1, "en_US", &err);
509 UFormattedNumber* fn = unumf_openResult(&err);
510
511 if (assertSuccess("Failed to create formatter or result", &err)) {
512 UChar result[200];
513 unumf_formatDouble(nf, testCases[i].value, fn, &err);
514 unumf_resultToString(fn, result, 200, &err);
515
516 if (assertSuccess("Formatting number failed", &err)) {
517 assertUEquals("Got wrong result", testCases[i].expectedResult, result);
518 }
519 }
520
521 unumf_closeResult(fn);
522 unumf_close(nf);
523 }
524 }
525
526
527 #endif /* #if !UCONFIG_NO_FORMATTING */
528