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 #include "numbertest.h"
9 #include "unicode/numberrangeformatter.h"
10
11 #include <cmath>
12 #include <numparse_affixes.h>
13
14 // Horrible workaround for the lack of a status code in the constructor...
15 // (Also affects numbertest_api.cpp)
16 UErrorCode globalNumberRangeFormatterTestStatus = U_ZERO_ERROR;
17
NumberRangeFormatterTest()18 NumberRangeFormatterTest::NumberRangeFormatterTest()
19 : NumberRangeFormatterTest(globalNumberRangeFormatterTestStatus) {
20 }
21
NumberRangeFormatterTest(UErrorCode & status)22 NumberRangeFormatterTest::NumberRangeFormatterTest(UErrorCode& status)
23 : USD(u"USD", status),
24 GBP(u"GBP", status),
25 PTE(u"PTE", status) {
26
27 // Check for error on the first MeasureUnit in case there is no data
28 LocalPointer<MeasureUnit> unit(MeasureUnit::createMeter(status));
29 if (U_FAILURE(status)) {
30 dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status));
31 return;
32 }
33 METER = *unit;
34
35 KILOMETER = *LocalPointer<MeasureUnit>(MeasureUnit::createKilometer(status));
36 FAHRENHEIT = *LocalPointer<MeasureUnit>(MeasureUnit::createFahrenheit(status));
37 KELVIN = *LocalPointer<MeasureUnit>(MeasureUnit::createKelvin(status));
38 }
39
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)40 void NumberRangeFormatterTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
41 if (exec) {
42 logln("TestSuite NumberRangeFormatterTest: ");
43 }
44 TESTCASE_AUTO_BEGIN;
45 TESTCASE_AUTO(testSanity);
46 TESTCASE_AUTO(testBasic);
47 TESTCASE_AUTO(testCollapse);
48 TESTCASE_AUTO(testIdentity);
49 TESTCASE_AUTO(testDifferentFormatters);
50 TESTCASE_AUTO(testPlurals);
51 TESTCASE_AUTO(testFieldPositions);
52 TESTCASE_AUTO(testCopyMove);
53 TESTCASE_AUTO(toObject);
54 TESTCASE_AUTO(testGetDecimalNumbers);
55 TESTCASE_AUTO_END;
56 }
57
testSanity()58 void NumberRangeFormatterTest::testSanity() {
59 IcuTestErrorCode status(*this, "testSanity");
60 LocalizedNumberRangeFormatter lnrf1 = NumberRangeFormatter::withLocale("en-us");
61 LocalizedNumberRangeFormatter lnrf2 = NumberRangeFormatter::with().locale("en-us");
62 assertEquals("Formatters should have same behavior 1",
63 lnrf1.formatFormattableRange(4, 6, status).toString(status),
64 lnrf2.formatFormattableRange(4, 6, status).toString(status));
65 }
66
testBasic()67 void NumberRangeFormatterTest::testBasic() {
68 assertFormatRange(
69 u"Basic",
70 NumberRangeFormatter::with(),
71 Locale("en-us"),
72 u"1–5",
73 u"~5",
74 u"~5",
75 u"0–3",
76 u"~0",
77 u"3–3,000",
78 u"3,000–5,000",
79 u"4,999–5,001",
80 u"~5,000",
81 u"5,000–5,000,000");
82
83 assertFormatRange(
84 u"Basic with units",
85 NumberRangeFormatter::with()
86 .numberFormatterBoth(NumberFormatter::with().unit(METER)),
87 Locale("en-us"),
88 u"1–5 m",
89 u"~5 m",
90 u"~5 m",
91 u"0–3 m",
92 u"~0 m",
93 u"3–3,000 m",
94 u"3,000–5,000 m",
95 u"4,999–5,001 m",
96 u"~5,000 m",
97 u"5,000–5,000,000 m");
98
99 assertFormatRange(
100 u"Basic with different units",
101 NumberRangeFormatter::with()
102 .numberFormatterFirst(NumberFormatter::with().unit(METER))
103 .numberFormatterSecond(NumberFormatter::with().unit(KILOMETER)),
104 Locale("en-us"),
105 u"1 m – 5 km",
106 u"5 m – 5 km",
107 u"5 m – 5 km",
108 u"0 m – 3 km",
109 u"0 m – 0 km",
110 u"3 m – 3,000 km",
111 u"3,000 m – 5,000 km",
112 u"4,999 m – 5,001 km",
113 u"5,000 m – 5,000 km",
114 u"5,000 m – 5,000,000 km");
115
116 assertFormatRange(
117 u"Basic long unit",
118 NumberRangeFormatter::with()
119 .numberFormatterBoth(NumberFormatter::with().unit(METER).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
120 Locale("en-us"),
121 u"1–5 meters",
122 u"~5 meters",
123 u"~5 meters",
124 u"0–3 meters",
125 u"~0 meters",
126 u"3–3,000 meters",
127 u"3,000–5,000 meters",
128 u"4,999–5,001 meters",
129 u"~5,000 meters",
130 u"5,000–5,000,000 meters");
131
132 assertFormatRange(
133 u"Non-English locale and unit",
134 NumberRangeFormatter::with()
135 .numberFormatterBoth(NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
136 Locale("fr-FR"),
137 u"1–5\u00A0degrés Fahrenheit",
138 u"≈5\u00A0degrés Fahrenheit",
139 u"≈5\u00A0degrés Fahrenheit",
140 u"0–3\u00A0degrés Fahrenheit",
141 u"≈0\u00A0degré Fahrenheit",
142 u"3–3\u202F000\u00A0degrés Fahrenheit",
143 u"3\u202F000–5\u202F000\u00A0degrés Fahrenheit",
144 u"4\u202F999–5\u202F001\u00A0degrés Fahrenheit",
145 u"≈5\u202F000\u00A0degrés Fahrenheit",
146 u"5\u202F000–5\u202F000\u202F000\u00A0degrés Fahrenheit");
147
148 assertFormatRange(
149 u"Locale with custom range separator",
150 NumberRangeFormatter::with(),
151 Locale("ja"),
152 u"1~5",
153 u"約 5",
154 u"約 5",
155 u"0~3",
156 u"約 0",
157 u"3~3,000",
158 u"3,000~5,000",
159 u"4,999~5,001",
160 u"約 5,000",
161 u"5,000~5,000,000");
162
163 assertFormatRange(
164 u"Locale that already has spaces around range separator",
165 NumberRangeFormatter::with()
166 .collapse(UNUM_RANGE_COLLAPSE_NONE)
167 .numberFormatterBoth(NumberFormatter::with().unit(KELVIN)),
168 Locale("hr"),
169 u"1 K – 5 K",
170 u"~5 K",
171 u"~5 K",
172 u"0 K – 3 K",
173 u"~0 K",
174 u"3 K – 3.000 K",
175 u"3.000 K – 5.000 K",
176 u"4.999 K – 5.001 K",
177 u"~5.000 K",
178 u"5.000 K – 5.000.000 K");
179
180 assertFormatRange(
181 u"Locale with custom numbering system and no plural ranges data",
182 NumberRangeFormatter::with(),
183 Locale("shn@numbers=beng"),
184 // 012459 = ০১৩৪৫৯
185 u"১–৫",
186 u"~৫",
187 u"~৫",
188 u"০–৩",
189 u"~০",
190 u"৩–৩,০০০",
191 u"৩,০০০–৫,০০০",
192 u"৪,৯৯৯–৫,০০১",
193 u"~৫,০০০",
194 u"৫,০০০–৫,০০০,০০০");
195
196 assertFormatRange(
197 u"Portuguese currency",
198 NumberRangeFormatter::with()
199 .numberFormatterBoth(NumberFormatter::with().unit(PTE)),
200 Locale("pt-PT"),
201 u"1$00 - 5$00 \u200B",
202 u"~5$00 \u200B",
203 u"~5$00 \u200B",
204 u"0$00 - 3$00 \u200B",
205 u"~0$00 \u200B",
206 u"3$00 - 3000$00 \u200B",
207 u"3000$00 - 5000$00 \u200B",
208 u"4999$00 - 5001$00 \u200B",
209 u"~5000$00 \u200B",
210 u"5000$00 - 5,000,000$00 \u200B");
211 }
212
testCollapse()213 void NumberRangeFormatterTest::testCollapse() {
214 assertFormatRange(
215 u"Default collapse on currency (default rounding)",
216 NumberRangeFormatter::with()
217 .numberFormatterBoth(NumberFormatter::with().unit(USD)),
218 Locale("en-us"),
219 u"$1.00 – $5.00",
220 u"~$5.00",
221 u"~$5.00",
222 u"$0.00 – $3.00",
223 u"~$0.00",
224 u"$3.00 – $3,000.00",
225 u"$3,000.00 – $5,000.00",
226 u"$4,999.00 – $5,001.00",
227 u"~$5,000.00",
228 u"$5,000.00 – $5,000,000.00");
229
230 assertFormatRange(
231 u"Default collapse on currency",
232 NumberRangeFormatter::with()
233 .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
234 Locale("en-us"),
235 u"$1 – $5",
236 u"~$5",
237 u"~$5",
238 u"$0 – $3",
239 u"~$0",
240 u"$3 – $3,000",
241 u"$3,000 – $5,000",
242 u"$4,999 – $5,001",
243 u"~$5,000",
244 u"$5,000 – $5,000,000");
245
246 assertFormatRange(
247 u"No collapse on currency",
248 NumberRangeFormatter::with()
249 .collapse(UNUM_RANGE_COLLAPSE_NONE)
250 .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
251 Locale("en-us"),
252 u"$1 – $5",
253 u"~$5",
254 u"~$5",
255 u"$0 – $3",
256 u"~$0",
257 u"$3 – $3,000",
258 u"$3,000 – $5,000",
259 u"$4,999 – $5,001",
260 u"~$5,000",
261 u"$5,000 – $5,000,000");
262
263 assertFormatRange(
264 u"Unit collapse on currency",
265 NumberRangeFormatter::with()
266 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
267 .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
268 Locale("en-us"),
269 u"$1–5",
270 u"~$5",
271 u"~$5",
272 u"$0–3",
273 u"~$0",
274 u"$3–3,000",
275 u"$3,000–5,000",
276 u"$4,999–5,001",
277 u"~$5,000",
278 u"$5,000–5,000,000");
279
280 assertFormatRange(
281 u"All collapse on currency",
282 NumberRangeFormatter::with()
283 .collapse(UNUM_RANGE_COLLAPSE_ALL)
284 .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
285 Locale("en-us"),
286 u"$1–5",
287 u"~$5",
288 u"~$5",
289 u"$0–3",
290 u"~$0",
291 u"$3–3,000",
292 u"$3,000–5,000",
293 u"$4,999–5,001",
294 u"~$5,000",
295 u"$5,000–5,000,000");
296
297 assertFormatRange(
298 u"Default collapse on currency ISO code",
299 NumberRangeFormatter::with()
300 .numberFormatterBoth(NumberFormatter::with()
301 .unit(GBP)
302 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
303 .precision(Precision::integer())),
304 Locale("en-us"),
305 u"GBP 1–5",
306 u"~GBP 5", // TODO: Fix this at some point
307 u"~GBP 5",
308 u"GBP 0–3",
309 u"~GBP 0",
310 u"GBP 3–3,000",
311 u"GBP 3,000–5,000",
312 u"GBP 4,999–5,001",
313 u"~GBP 5,000",
314 u"GBP 5,000–5,000,000");
315
316 assertFormatRange(
317 u"No collapse on currency ISO code",
318 NumberRangeFormatter::with()
319 .collapse(UNUM_RANGE_COLLAPSE_NONE)
320 .numberFormatterBoth(NumberFormatter::with()
321 .unit(GBP)
322 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
323 .precision(Precision::integer())),
324 Locale("en-us"),
325 u"GBP 1 – GBP 5",
326 u"~GBP 5", // TODO: Fix this at some point
327 u"~GBP 5",
328 u"GBP 0 – GBP 3",
329 u"~GBP 0",
330 u"GBP 3 – GBP 3,000",
331 u"GBP 3,000 – GBP 5,000",
332 u"GBP 4,999 – GBP 5,001",
333 u"~GBP 5,000",
334 u"GBP 5,000 – GBP 5,000,000");
335
336 assertFormatRange(
337 u"Unit collapse on currency ISO code",
338 NumberRangeFormatter::with()
339 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
340 .numberFormatterBoth(NumberFormatter::with()
341 .unit(GBP)
342 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
343 .precision(Precision::integer())),
344 Locale("en-us"),
345 u"GBP 1–5",
346 u"~GBP 5", // TODO: Fix this at some point
347 u"~GBP 5",
348 u"GBP 0–3",
349 u"~GBP 0",
350 u"GBP 3–3,000",
351 u"GBP 3,000–5,000",
352 u"GBP 4,999–5,001",
353 u"~GBP 5,000",
354 u"GBP 5,000–5,000,000");
355
356 assertFormatRange(
357 u"All collapse on currency ISO code",
358 NumberRangeFormatter::with()
359 .collapse(UNUM_RANGE_COLLAPSE_ALL)
360 .numberFormatterBoth(NumberFormatter::with()
361 .unit(GBP)
362 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
363 .precision(Precision::integer())),
364 Locale("en-us"),
365 u"GBP 1–5",
366 u"~GBP 5", // TODO: Fix this at some point
367 u"~GBP 5",
368 u"GBP 0–3",
369 u"~GBP 0",
370 u"GBP 3–3,000",
371 u"GBP 3,000–5,000",
372 u"GBP 4,999–5,001",
373 u"~GBP 5,000",
374 u"GBP 5,000–5,000,000");
375
376 // Default collapse on measurement unit is in testBasic()
377
378 assertFormatRange(
379 u"No collapse on measurement unit",
380 NumberRangeFormatter::with()
381 .collapse(UNUM_RANGE_COLLAPSE_NONE)
382 .numberFormatterBoth(NumberFormatter::with().unit(METER)),
383 Locale("en-us"),
384 u"1 m – 5 m",
385 u"~5 m",
386 u"~5 m",
387 u"0 m – 3 m",
388 u"~0 m",
389 u"3 m – 3,000 m",
390 u"3,000 m – 5,000 m",
391 u"4,999 m – 5,001 m",
392 u"~5,000 m",
393 u"5,000 m – 5,000,000 m");
394
395 assertFormatRange(
396 u"Unit collapse on measurement unit",
397 NumberRangeFormatter::with()
398 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
399 .numberFormatterBoth(NumberFormatter::with().unit(METER)),
400 Locale("en-us"),
401 u"1–5 m",
402 u"~5 m",
403 u"~5 m",
404 u"0–3 m",
405 u"~0 m",
406 u"3–3,000 m",
407 u"3,000–5,000 m",
408 u"4,999–5,001 m",
409 u"~5,000 m",
410 u"5,000–5,000,000 m");
411
412 assertFormatRange(
413 u"All collapse on measurement unit",
414 NumberRangeFormatter::with()
415 .collapse(UNUM_RANGE_COLLAPSE_ALL)
416 .numberFormatterBoth(NumberFormatter::with().unit(METER)),
417 Locale("en-us"),
418 u"1–5 m",
419 u"~5 m",
420 u"~5 m",
421 u"0–3 m",
422 u"~0 m",
423 u"3–3,000 m",
424 u"3,000–5,000 m",
425 u"4,999–5,001 m",
426 u"~5,000 m",
427 u"5,000–5,000,000 m");
428
429 assertFormatRange(
430 u"Default collapse, long-form compact notation",
431 NumberRangeFormatter::with()
432 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
433 Locale("de-CH"),
434 u"1–5",
435 u"≈5",
436 u"≈5",
437 u"0–3",
438 u"≈0",
439 u"3–3 Tausend",
440 u"3–5 Tausend",
441 u"≈5 Tausend",
442 u"≈5 Tausend",
443 u"5 Tausend – 5 Millionen");
444
445 assertFormatRange(
446 u"Unit collapse, long-form compact notation",
447 NumberRangeFormatter::with()
448 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
449 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
450 Locale("de-CH"),
451 u"1–5",
452 u"≈5",
453 u"≈5",
454 u"0–3",
455 u"≈0",
456 u"3–3 Tausend",
457 u"3 Tausend – 5 Tausend",
458 u"≈5 Tausend",
459 u"≈5 Tausend",
460 u"5 Tausend – 5 Millionen");
461
462 assertFormatRange(
463 u"Default collapse on measurement unit with compact-short notation",
464 NumberRangeFormatter::with()
465 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
466 Locale("en-us"),
467 u"1–5 m",
468 u"~5 m",
469 u"~5 m",
470 u"0–3 m",
471 u"~0 m",
472 u"3–3K m",
473 u"3K – 5K m",
474 u"~5K m",
475 u"~5K m",
476 u"5K – 5M m");
477
478 assertFormatRange(
479 u"No collapse on measurement unit with compact-short notation",
480 NumberRangeFormatter::with()
481 .collapse(UNUM_RANGE_COLLAPSE_NONE)
482 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
483 Locale("en-us"),
484 u"1 m – 5 m",
485 u"~5 m",
486 u"~5 m",
487 u"0 m – 3 m",
488 u"~0 m",
489 u"3 m – 3K m",
490 u"3K m – 5K m",
491 u"~5K m",
492 u"~5K m",
493 u"5K m – 5M m");
494
495 assertFormatRange(
496 u"Unit collapse on measurement unit with compact-short notation",
497 NumberRangeFormatter::with()
498 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
499 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
500 Locale("en-us"),
501 u"1–5 m",
502 u"~5 m",
503 u"~5 m",
504 u"0–3 m",
505 u"~0 m",
506 u"3–3K m",
507 u"3K – 5K m",
508 u"~5K m",
509 u"~5K m",
510 u"5K – 5M m");
511
512 assertFormatRange(
513 u"All collapse on measurement unit with compact-short notation",
514 NumberRangeFormatter::with()
515 .collapse(UNUM_RANGE_COLLAPSE_ALL)
516 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
517 Locale("en-us"),
518 u"1–5 m",
519 u"~5 m",
520 u"~5 m",
521 u"0–3 m",
522 u"~0 m",
523 u"3–3K m",
524 u"3–5K m", // this one is the key use case for ALL
525 u"~5K m",
526 u"~5K m",
527 u"5K – 5M m");
528
529 assertFormatRange(
530 u"No collapse on scientific notation",
531 NumberRangeFormatter::with()
532 .collapse(UNUM_RANGE_COLLAPSE_NONE)
533 .numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
534 Locale("en-us"),
535 u"1E0 – 5E0",
536 u"~5E0",
537 u"~5E0",
538 u"0E0 – 3E0",
539 u"~0E0",
540 u"3E0 – 3E3",
541 u"3E3 – 5E3",
542 u"4.999E3 – 5.001E3",
543 u"~5E3",
544 u"5E3 – 5E6");
545
546 assertFormatRange(
547 u"All collapse on scientific notation",
548 NumberRangeFormatter::with()
549 .collapse(UNUM_RANGE_COLLAPSE_ALL)
550 .numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
551 Locale("en-us"),
552 u"1–5E0",
553 u"~5E0",
554 u"~5E0",
555 u"0–3E0",
556 u"~0E0",
557 u"3E0 – 3E3",
558 u"3–5E3",
559 u"4.999–5.001E3",
560 u"~5E3",
561 u"5E3 – 5E6");
562
563 // TODO: Test compact currency?
564 // The code is not smart enough to differentiate the notation from the unit.
565 }
566
testIdentity()567 void NumberRangeFormatterTest::testIdentity() {
568 assertFormatRange(
569 u"Identity fallback Range",
570 NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_RANGE),
571 Locale("en-us"),
572 u"1–5",
573 u"5–5",
574 u"5–5",
575 u"0–3",
576 u"0–0",
577 u"3–3,000",
578 u"3,000–5,000",
579 u"4,999–5,001",
580 u"5,000–5,000",
581 u"5,000–5,000,000");
582
583 assertFormatRange(
584 u"Identity fallback Approximately or Single Value",
585 NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE),
586 Locale("en-us"),
587 u"1–5",
588 u"~5",
589 u"5",
590 u"0–3",
591 u"0",
592 u"3–3,000",
593 u"3,000–5,000",
594 u"4,999–5,001",
595 u"5,000",
596 u"5,000–5,000,000");
597
598 assertFormatRange(
599 u"Identity fallback Single Value",
600 NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE),
601 Locale("en-us"),
602 u"1–5",
603 u"5",
604 u"5",
605 u"0–3",
606 u"0",
607 u"3–3,000",
608 u"3,000–5,000",
609 u"4,999–5,001",
610 u"5,000",
611 u"5,000–5,000,000");
612
613 assertFormatRange(
614 u"Identity fallback Approximately or Single Value with compact notation",
615 NumberRangeFormatter::with()
616 .identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE)
617 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort())),
618 Locale("en-us"),
619 u"1–5",
620 u"~5",
621 u"5",
622 u"0–3",
623 u"0",
624 u"3–3K",
625 u"3K – 5K",
626 u"~5K",
627 u"5K",
628 u"5K – 5M");
629
630 assertFormatRange(
631 u"Approximately in middle of unit string",
632 NumberRangeFormatter::with().numberFormatterBoth(
633 NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
634 Locale("zh-Hant"),
635 u"華氏 1-5 度",
636 u"華氏 ~5 度",
637 u"華氏 ~5 度",
638 u"華氏 0-3 度",
639 u"華氏 ~0 度",
640 u"華氏 3-3,000 度",
641 u"華氏 3,000-5,000 度",
642 u"華氏 4,999-5,001 度",
643 u"華氏 ~5,000 度",
644 u"華氏 5,000-5,000,000 度");
645 }
646
testDifferentFormatters()647 void NumberRangeFormatterTest::testDifferentFormatters() {
648 assertFormatRange(
649 u"Different rounding rules",
650 NumberRangeFormatter::with()
651 .numberFormatterFirst(NumberFormatter::with().precision(Precision::integer()))
652 .numberFormatterSecond(NumberFormatter::with().precision(Precision::fixedSignificantDigits(2))),
653 Locale("en-us"),
654 u"1–5.0",
655 u"5–5.0",
656 u"5–5.0",
657 u"0–3.0",
658 u"0–0.0",
659 u"3–3,000",
660 u"3,000–5,000",
661 u"4,999–5,000",
662 u"5,000–5,000", // TODO: Should this one be ~5,000?
663 u"5,000–5,000,000");
664 }
665
testPlurals()666 void NumberRangeFormatterTest::testPlurals() {
667 IcuTestErrorCode status(*this, "testPlurals");
668
669 // Locale sl has interesting plural forms:
670 // GBP{
671 // one{"britanski funt"}
672 // two{"britanska funta"}
673 // few{"britanski funti"}
674 // other{"britanskih funtov"}
675 // }
676 Locale locale("sl");
677
678 UnlocalizedNumberFormatter unf = NumberFormatter::with()
679 .unit(GBP)
680 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
681 .precision(Precision::integer());
682 LocalizedNumberFormatter lnf = unf.locale(locale);
683
684 // For comparison, run the non-range version of the formatter
685 assertEquals(Int64ToUnicodeString(1), u"1 britanski funt", lnf.formatDouble(1, status).toString(status));
686 assertEquals(Int64ToUnicodeString(2), u"2 britanska funta", lnf.formatDouble(2, status).toString(status));
687 assertEquals(Int64ToUnicodeString(3), u"3 britanski funti", lnf.formatDouble(3, status).toString(status));
688 assertEquals(Int64ToUnicodeString(5), u"5 britanskih funtov", lnf.formatDouble(5, status).toString(status));
689 if (status.errIfFailureAndReset()) { return; }
690
691 LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::with()
692 .numberFormatterBoth(unf)
693 .identityFallback(UNUM_IDENTITY_FALLBACK_RANGE)
694 .locale(locale);
695
696 struct TestCase {
697 double first;
698 double second;
699 const char16_t* expected;
700 } cases[] = {
701 {1, 1, u"1–1 britanski funti"}, // one + one -> few
702 {1, 2, u"1–2 britanska funta"}, // one + two -> two
703 {1, 3, u"1–3 britanski funti"}, // one + few -> few
704 {1, 5, u"1–5 britanskih funtov"}, // one + other -> other
705 {2, 1, u"2–1 britanski funti"}, // two + one -> few
706 {2, 2, u"2–2 britanska funta"}, // two + two -> two
707 {2, 3, u"2–3 britanski funti"}, // two + few -> few
708 {2, 5, u"2–5 britanskih funtov"}, // two + other -> other
709 {3, 1, u"3–1 britanski funti"}, // few + one -> few
710 {3, 2, u"3–2 britanska funta"}, // few + two -> two
711 {3, 3, u"3–3 britanski funti"}, // few + few -> few
712 {3, 5, u"3–5 britanskih funtov"}, // few + other -> other
713 {5, 1, u"5–1 britanski funti"}, // other + one -> few
714 {5, 2, u"5–2 britanska funta"}, // other + two -> two
715 {5, 3, u"5–3 britanski funti"}, // other + few -> few
716 {5, 5, u"5–5 britanskih funtov"}, // other + other -> other
717 };
718 for (auto& cas : cases) {
719 UnicodeString message = Int64ToUnicodeString(static_cast<int64_t>(cas.first));
720 message += u" ";
721 message += Int64ToUnicodeString(static_cast<int64_t>(cas.second));
722 status.setScope(message);
723 UnicodeString actual = lnrf.formatFormattableRange(cas.first, cas.second, status).toString(status);
724 assertEquals(message, cas.expected, actual);
725 status.errIfFailureAndReset();
726 }
727 }
728
testFieldPositions()729 void NumberRangeFormatterTest::testFieldPositions() {
730 {
731 const char16_t* message = u"Field position test 1";
732 const char16_t* expectedString = u"3K – 5K m";
733 FormattedNumberRange result = assertFormattedRangeEquals(
734 message,
735 NumberRangeFormatter::with()
736 .numberFormatterBoth(NumberFormatter::with()
737 .unit(METER)
738 .notation(Notation::compactShort()))
739 .locale("en-us"),
740 3000,
741 5000,
742 expectedString);
743 static const UFieldPositionWithCategory expectedFieldPositions[] = {
744 // category, field, begin index, end index
745 {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, 0, 2},
746 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 1},
747 {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 1, 2},
748 {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, 5, 7},
749 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 5, 6},
750 {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 6, 7},
751 {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD, 8, 9}};
752 checkMixedFormattedValue(
753 message,
754 result,
755 expectedString,
756 expectedFieldPositions,
757 UPRV_LENGTHOF(expectedFieldPositions));
758 }
759
760 {
761 const char16_t* message = u"Field position test 2";
762 const char16_t* expectedString = u"87,654,321–98,765,432";
763 FormattedNumberRange result = assertFormattedRangeEquals(
764 message,
765 NumberRangeFormatter::withLocale("en-us"),
766 87654321,
767 98765432,
768 expectedString);
769 static const UFieldPositionWithCategory expectedFieldPositions[] = {
770 // category, field, begin index, end index
771 {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, 0, 10},
772 {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 2, 3},
773 {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 6, 7},
774 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 10},
775 {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, 11, 21},
776 {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 13, 14},
777 {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 17, 18},
778 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 11, 21}};
779 checkMixedFormattedValue(
780 message,
781 result,
782 expectedString,
783 expectedFieldPositions,
784 UPRV_LENGTHOF(expectedFieldPositions));
785 }
786 }
787
testCopyMove()788 void NumberRangeFormatterTest::testCopyMove() {
789 IcuTestErrorCode status(*this, "testCopyMove");
790
791 // Default constructors
792 LocalizedNumberRangeFormatter l1;
793 assertEquals("Initial behavior", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status));
794 if (status.errDataIfFailureAndReset()) { return; }
795
796 // Setup
797 l1 = NumberRangeFormatter::withLocale("fr-FR")
798 .numberFormatterBoth(NumberFormatter::with().unit(USD));
799 assertEquals("Currency behavior", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
800
801 // Copy constructor
802 LocalizedNumberRangeFormatter l2 = l1;
803 assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
804
805 // Move constructor
806 LocalizedNumberRangeFormatter l3 = std::move(l1);
807 assertEquals("Move constructor", u"1,00–5,00 $US", l3.formatFormattableRange(1, 5, status).toString(status));
808
809 // Reset objects for assignment tests
810 l1 = NumberRangeFormatter::withLocale("en-us");
811 l2 = NumberRangeFormatter::withLocale("en-us");
812 assertEquals("Rest behavior, l1", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status));
813 assertEquals("Rest behavior, l2", u"1–5", l2.formatFormattableRange(1, 5, status).toString(status));
814
815 // Copy assignment
816 l1 = l3;
817 assertEquals("Copy constructor", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
818
819 // Move assignment
820 l2 = std::move(l3);
821 assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
822
823 // FormattedNumberRange
824 FormattedNumberRange result = l1.formatFormattableRange(1, 5, status);
825 assertEquals("FormattedNumberRange move constructor", u"1,00–5,00 $US", result.toString(status));
826 result = l1.formatFormattableRange(3, 6, status);
827 assertEquals("FormattedNumberRange move assignment", u"3,00–6,00 $US", result.toString(status));
828 }
829
toObject()830 void NumberRangeFormatterTest::toObject() {
831 IcuTestErrorCode status(*this, "toObject");
832
833 // const lvalue version
834 {
835 LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en");
836 LocalPointer<LocalizedNumberRangeFormatter> lnf2(lnf.clone());
837 assertFalse("should create successfully, const lvalue", lnf2.isNull());
838 assertEquals("object API test, const lvalue", u"5–7",
839 lnf2->formatFormattableRange(5, 7, status).toString(status));
840 }
841
842 // rvalue reference version
843 {
844 LocalPointer<LocalizedNumberRangeFormatter> lnf(
845 NumberRangeFormatter::withLocale("en").clone());
846 assertFalse("should create successfully, rvalue reference", lnf.isNull());
847 assertEquals("object API test, rvalue reference", u"5–7",
848 lnf->formatFormattableRange(5, 7, status).toString(status));
849 }
850
851 // to std::unique_ptr via assignment
852 {
853 std::unique_ptr<LocalizedNumberRangeFormatter> lnf =
854 NumberRangeFormatter::withLocale("en").clone();
855 assertTrue("should create successfully, unique_ptr B", static_cast<bool>(lnf));
856 assertEquals("object API test, unique_ptr B", u"5–7",
857 lnf->formatFormattableRange(5, 7, status).toString(status));
858 }
859
860 // make sure no memory leaks
861 {
862 NumberRangeFormatter::with().clone();
863 }
864 }
865
testGetDecimalNumbers()866 void NumberRangeFormatterTest::testGetDecimalNumbers() {
867 IcuTestErrorCode status(*this, "testGetDecimalNumbers");
868
869 LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en")
870 .numberFormatterBoth(NumberFormatter::with().unit(USD));
871
872 // Range of numbers
873 {
874 FormattedNumberRange range = lnf.formatFormattableRange(1, 5, status);
875 assertEquals("Range: Formatted string should be as expected",
876 u"$1.00 \u2013 $5.00",
877 range.toString(status));
878 auto decimalNumbers = range.getDecimalNumbers<std::string>(status);
879 // TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem?
880 if (logKnownIssue("ICU-21281")) {
881 assertEquals("First decimal number", "1", decimalNumbers.first.c_str());
882 assertEquals("Second decimal number", "5", decimalNumbers.second.c_str());
883 } else {
884 assertEquals("First decimal number", "1.00", decimalNumbers.first.c_str());
885 assertEquals("Second decimal number", "5.00", decimalNumbers.second.c_str());
886 }
887 }
888
889 // Identity fallback
890 {
891 FormattedNumberRange range = lnf.formatFormattableRange(3, 3, status);
892 assertEquals("Identity: Formatted string should be as expected",
893 u"~$3.00",
894 range.toString(status));
895 auto decimalNumbers = range.getDecimalNumbers<std::string>(status);
896 // NOTE: DecNum doesn't retain trailing zeros. Is that a problem?
897 // TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem?
898 if (logKnownIssue("ICU-21281")) {
899 assertEquals("First decimal number", "3", decimalNumbers.first.c_str());
900 assertEquals("Second decimal number", "3", decimalNumbers.second.c_str());
901 } else {
902 assertEquals("First decimal number", "3.00", decimalNumbers.first.c_str());
903 assertEquals("Second decimal number", "3.00", decimalNumbers.second.c_str());
904 }
905 }
906 }
907
assertFormatRange(const char16_t * message,const UnlocalizedNumberRangeFormatter & f,Locale locale,const char16_t * expected_10_50,const char16_t * expected_49_51,const char16_t * expected_50_50,const char16_t * expected_00_30,const char16_t * expected_00_00,const char16_t * expected_30_3K,const char16_t * expected_30K_50K,const char16_t * expected_49K_51K,const char16_t * expected_50K_50K,const char16_t * expected_50K_50M)908 void NumberRangeFormatterTest::assertFormatRange(
909 const char16_t* message,
910 const UnlocalizedNumberRangeFormatter& f,
911 Locale locale,
912 const char16_t* expected_10_50,
913 const char16_t* expected_49_51,
914 const char16_t* expected_50_50,
915 const char16_t* expected_00_30,
916 const char16_t* expected_00_00,
917 const char16_t* expected_30_3K,
918 const char16_t* expected_30K_50K,
919 const char16_t* expected_49K_51K,
920 const char16_t* expected_50K_50K,
921 const char16_t* expected_50K_50M) {
922 LocalizedNumberRangeFormatter l = f.locale(locale);
923 assertFormattedRangeEquals(message, l, 1, 5, expected_10_50);
924 assertFormattedRangeEquals(message, l, 4.9999999, 5.0000001, expected_49_51);
925 assertFormattedRangeEquals(message, l, 5, 5, expected_50_50);
926 assertFormattedRangeEquals(message, l, 0, 3, expected_00_30);
927 assertFormattedRangeEquals(message, l, 0, 0, expected_00_00);
928 assertFormattedRangeEquals(message, l, 3, 3000, expected_30_3K);
929 assertFormattedRangeEquals(message, l, 3000, 5000, expected_30K_50K);
930 assertFormattedRangeEquals(message, l, 4999, 5001, expected_49K_51K);
931 assertFormattedRangeEquals(message, l, 5000, 5000, expected_50K_50K);
932 assertFormattedRangeEquals(message, l, 5e3, 5e6, expected_50K_50M);
933 }
934
assertFormattedRangeEquals(const char16_t * message,const LocalizedNumberRangeFormatter & l,double first,double second,const char16_t * expected)935 FormattedNumberRange NumberRangeFormatterTest::assertFormattedRangeEquals(
936 const char16_t* message,
937 const LocalizedNumberRangeFormatter& l,
938 double first,
939 double second,
940 const char16_t* expected) {
941 IcuTestErrorCode status(*this, "assertFormattedRangeEquals");
942 UnicodeString fullMessage = UnicodeString(message) + u": " + DoubleToUnicodeString(first) + u", " + DoubleToUnicodeString(second);
943 status.setScope(fullMessage);
944 FormattedNumberRange fnr = l.formatFormattableRange(first, second, status);
945 UnicodeString actual = fnr.toString(status);
946 assertEquals(fullMessage, expected, actual);
947 return fnr;
948 }
949
950
951 #endif
952