• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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           CHF(u"CHF", status),
25           GBP(u"GBP", status),
26           PTE(u"PTE", status) {
27 
28     // Check for error on the first MeasureUnit in case there is no data
29     LocalPointer<MeasureUnit> unit(MeasureUnit::createMeter(status));
30     if (U_FAILURE(status)) {
31         dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status));
32         return;
33     }
34     METER = *unit;
35 
36     KILOMETER = *LocalPointer<MeasureUnit>(MeasureUnit::createKilometer(status));
37     FAHRENHEIT = *LocalPointer<MeasureUnit>(MeasureUnit::createFahrenheit(status));
38     KELVIN = *LocalPointer<MeasureUnit>(MeasureUnit::createKelvin(status));
39 }
40 
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)41 void NumberRangeFormatterTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
42     if (exec) {
43         logln("TestSuite NumberRangeFormatterTest: ");
44     }
45     TESTCASE_AUTO_BEGIN;
46         TESTCASE_AUTO(testSanity);
47         TESTCASE_AUTO(testBasic);
48         TESTCASE_AUTO(testCollapse);
49         TESTCASE_AUTO(testIdentity);
50         TESTCASE_AUTO(testDifferentFormatters);
51         TESTCASE_AUTO(testNaNInfinity);
52         TESTCASE_AUTO(testPlurals);
53         TESTCASE_AUTO(testFieldPositions);
54         TESTCASE_AUTO(testCopyMove);
55         TESTCASE_AUTO(toObject);
56         TESTCASE_AUTO(locale);
57         TESTCASE_AUTO(testGetDecimalNumbers);
58         TESTCASE_AUTO(test21684_Performance);
59         TESTCASE_AUTO(test21358_SignPosition);
60         TESTCASE_AUTO(test21683_StateLeak);
61         TESTCASE_AUTO(testCreateLNRFFromNumberingSystemInSkeleton);
62         TESTCASE_AUTO(test22288_DifferentStartEndSettings);
63     TESTCASE_AUTO_END;
64 }
65 
testSanity()66 void NumberRangeFormatterTest::testSanity() {
67     IcuTestErrorCode status(*this, "testSanity");
68     LocalizedNumberRangeFormatter lnrf1 = NumberRangeFormatter::withLocale("en-us");
69     LocalizedNumberRangeFormatter lnrf2 = NumberRangeFormatter::with().locale("en-us");
70     assertEquals("Formatters should have same behavior 1",
71         lnrf1.formatFormattableRange(4, 6, status).toString(status),
72         lnrf2.formatFormattableRange(4, 6, status).toString(status));
73 }
74 
testBasic()75 void NumberRangeFormatterTest::testBasic() {
76     assertFormatRange(
77         u"Basic",
78         NumberRangeFormatter::with(),
79         Locale("en-us"),
80         u"1–5",
81         u"~5",
82         u"~5",
83         u"0–3",
84         u"~0",
85         u"3–3,000",
86         u"3,000–5,000",
87         u"4,999–5,001",
88         u"~5,000",
89         u"5,000–5,000,000");
90 
91     assertFormatRange(
92         u"Basic with units",
93         NumberRangeFormatter::with()
94             .numberFormatterBoth(NumberFormatter::with().unit(METER)),
95         Locale("en-us"),
96         u"1–5 m",
97         u"~5 m",
98         u"~5 m",
99         u"0–3 m",
100         u"~0 m",
101         u"3–3,000 m",
102         u"3,000–5,000 m",
103         u"4,999–5,001 m",
104         u"~5,000 m",
105         u"5,000–5,000,000 m");
106 
107     assertFormatRange(
108         u"Basic with different units",
109         NumberRangeFormatter::with()
110             .numberFormatterFirst(NumberFormatter::with().unit(METER))
111             .numberFormatterSecond(NumberFormatter::with().unit(KILOMETER)),
112         Locale("en-us"),
113         u"1 m – 5 km",
114         u"5 m – 5 km",
115         u"5 m – 5 km",
116         u"0 m – 3 km",
117         u"0 m – 0 km",
118         u"3 m – 3,000 km",
119         u"3,000 m – 5,000 km",
120         u"4,999 m – 5,001 km",
121         u"5,000 m – 5,000 km",
122         u"5,000 m – 5,000,000 km");
123 
124     assertFormatRange(
125         u"Basic long unit",
126         NumberRangeFormatter::with()
127             .numberFormatterBoth(NumberFormatter::with().unit(METER).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
128         Locale("en-us"),
129         u"1–5 meters",
130         u"~5 meters",
131         u"~5 meters",
132         u"0–3 meters",
133         u"~0 meters",
134         u"3–3,000 meters",
135         u"3,000–5,000 meters",
136         u"4,999–5,001 meters",
137         u"~5,000 meters",
138         u"5,000–5,000,000 meters");
139 
140     assertFormatRange(
141         u"Non-English locale and unit",
142         NumberRangeFormatter::with()
143             .numberFormatterBoth(NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
144         Locale("fr-FR"),
145         u"1–5\u00A0degrés Fahrenheit",
146         u"≃5\u00A0degrés Fahrenheit",
147         u"≃5\u00A0degrés Fahrenheit",
148         u"0–3\u00A0degrés Fahrenheit",
149         u"≃0\u00A0degré Fahrenheit",
150         u"3–3\u202F000\u00A0degrés Fahrenheit",
151         u"3\u202F000–5\u202F000\u00A0degrés Fahrenheit",
152         u"4\u202F999–5\u202F001\u00A0degrés Fahrenheit",
153         u"≃5\u202F000\u00A0degrés Fahrenheit",
154         u"5\u202F000–5\u202F000\u202F000\u00A0degrés Fahrenheit");
155 
156     assertFormatRange(
157         u"Locale with custom range separator",
158         NumberRangeFormatter::with(),
159         Locale("ja"),
160         u"1~5",
161         u"約5",
162         u"約5",
163         u"0~3",
164         u"約0",
165         u"3~3,000",
166         u"3,000~5,000",
167         u"4,999~5,001",
168         u"約5,000",
169         u"5,000~5,000,000");
170 
171     assertFormatRange(
172         u"Locale that already has spaces around range separator",
173         NumberRangeFormatter::with()
174             .collapse(UNUM_RANGE_COLLAPSE_NONE)
175             .numberFormatterBoth(NumberFormatter::with().unit(KELVIN)),
176         Locale("hr"),
177         u"1 K – 5 K",
178         u"~5 K",
179         u"~5 K",
180         u"0 K – 3 K",
181         u"~0 K",
182         u"3 K – 3.000 K",
183         u"3.000 K – 5.000 K",
184         u"4.999 K – 5.001 K",
185         u"~5.000 K",
186         u"5.000 K – 5.000.000 K");
187 
188     assertFormatRange(
189         u"Locale with custom numbering system and no plural ranges data",
190         NumberRangeFormatter::with(),
191         Locale("shn@numbers=beng"),
192         // 012459 = ০১৩৪৫৯
193         u"১–৫",
194         u"~৫",
195         u"~৫",
196         u"০–৩",
197         u"~০",
198         u"৩–৩,০০০",
199         u"৩,০০০–৫,০০০",
200         u"৪,৯৯৯–৫,০০১",
201         u"~৫,০০০",
202         u"৫,০০০–৫,০০০,০০০");
203 
204     assertFormatRange(
205         u"Portuguese currency",
206         NumberRangeFormatter::with()
207             .numberFormatterBoth(NumberFormatter::with().unit(PTE)),
208         Locale("pt-PT"),
209         u"1$00 - 5$00 \u200B",
210         u"~5$00 \u200B",
211         u"~5$00 \u200B",
212         u"0$00 - 3$00 \u200B",
213         u"~0$00 \u200B",
214         u"3$00 - 3000$00 \u200B",
215         u"3000$00 - 5000$00 \u200B",
216         u"4999$00 - 5001$00 \u200B",
217         u"~5000$00 \u200B",
218         u"5000$00 - 5,000,000$00 \u200B");
219 }
220 
testCollapse()221 void NumberRangeFormatterTest::testCollapse() {
222     assertFormatRange(
223         u"Default collapse on currency (default rounding)",
224         NumberRangeFormatter::with()
225             .numberFormatterBoth(NumberFormatter::with().unit(USD)),
226         Locale("en-us"),
227         u"$1.00 – $5.00",
228         u"~$5.00",
229         u"~$5.00",
230         u"$0.00 – $3.00",
231         u"~$0.00",
232         u"$3.00 – $3,000.00",
233         u"$3,000.00 – $5,000.00",
234         u"$4,999.00 – $5,001.00",
235         u"~$5,000.00",
236         u"$5,000.00 – $5,000,000.00");
237 
238     assertFormatRange(
239         u"Default collapse on currency",
240         NumberRangeFormatter::with()
241             .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
242         Locale("en-us"),
243         u"$1 – $5",
244         u"~$5",
245         u"~$5",
246         u"$0 – $3",
247         u"~$0",
248         u"$3 – $3,000",
249         u"$3,000 – $5,000",
250         u"$4,999 – $5,001",
251         u"~$5,000",
252         u"$5,000 – $5,000,000");
253 
254     assertFormatRange(
255         u"No collapse on currency",
256         NumberRangeFormatter::with()
257             .collapse(UNUM_RANGE_COLLAPSE_NONE)
258             .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
259         Locale("en-us"),
260         u"$1 – $5",
261         u"~$5",
262         u"~$5",
263         u"$0 – $3",
264         u"~$0",
265         u"$3 – $3,000",
266         u"$3,000 – $5,000",
267         u"$4,999 – $5,001",
268         u"~$5,000",
269         u"$5,000 – $5,000,000");
270 
271     assertFormatRange(
272         u"Unit collapse on currency",
273         NumberRangeFormatter::with()
274             .collapse(UNUM_RANGE_COLLAPSE_UNIT)
275             .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
276         Locale("en-us"),
277         u"$1–5",
278         u"~$5",
279         u"~$5",
280         u"$0–3",
281         u"~$0",
282         u"$3–3,000",
283         u"$3,000–5,000",
284         u"$4,999–5,001",
285         u"~$5,000",
286         u"$5,000–5,000,000");
287 
288     assertFormatRange(
289         u"All collapse on currency",
290         NumberRangeFormatter::with()
291             .collapse(UNUM_RANGE_COLLAPSE_ALL)
292             .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
293         Locale("en-us"),
294         u"$1–5",
295         u"~$5",
296         u"~$5",
297         u"$0–3",
298         u"~$0",
299         u"$3–3,000",
300         u"$3,000–5,000",
301         u"$4,999–5,001",
302         u"~$5,000",
303         u"$5,000–5,000,000");
304 
305     assertFormatRange(
306         u"Default collapse on currency ISO code",
307         NumberRangeFormatter::with()
308             .numberFormatterBoth(NumberFormatter::with()
309                 .unit(GBP)
310                 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
311                 .precision(Precision::integer())),
312         Locale("en-us"),
313         u"GBP 1–5",
314         u"~GBP 5",  // TODO: Fix this at some point
315         u"~GBP 5",
316         u"GBP 0–3",
317         u"~GBP 0",
318         u"GBP 3–3,000",
319         u"GBP 3,000–5,000",
320         u"GBP 4,999–5,001",
321         u"~GBP 5,000",
322         u"GBP 5,000–5,000,000");
323 
324     assertFormatRange(
325         u"No collapse on currency ISO code",
326         NumberRangeFormatter::with()
327             .collapse(UNUM_RANGE_COLLAPSE_NONE)
328             .numberFormatterBoth(NumberFormatter::with()
329                 .unit(GBP)
330                 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
331                 .precision(Precision::integer())),
332         Locale("en-us"),
333         u"GBP 1 – GBP 5",
334         u"~GBP 5",  // TODO: Fix this at some point
335         u"~GBP 5",
336         u"GBP 0 – GBP 3",
337         u"~GBP 0",
338         u"GBP 3 – GBP 3,000",
339         u"GBP 3,000 – GBP 5,000",
340         u"GBP 4,999 – GBP 5,001",
341         u"~GBP 5,000",
342         u"GBP 5,000 – GBP 5,000,000");
343 
344     assertFormatRange(
345         u"Unit collapse on currency ISO code",
346         NumberRangeFormatter::with()
347             .collapse(UNUM_RANGE_COLLAPSE_UNIT)
348             .numberFormatterBoth(NumberFormatter::with()
349                 .unit(GBP)
350                 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
351                 .precision(Precision::integer())),
352         Locale("en-us"),
353         u"GBP 1–5",
354         u"~GBP 5",  // TODO: Fix this at some point
355         u"~GBP 5",
356         u"GBP 0–3",
357         u"~GBP 0",
358         u"GBP 3–3,000",
359         u"GBP 3,000–5,000",
360         u"GBP 4,999–5,001",
361         u"~GBP 5,000",
362         u"GBP 5,000–5,000,000");
363 
364     assertFormatRange(
365         u"All collapse on currency ISO code",
366         NumberRangeFormatter::with()
367             .collapse(UNUM_RANGE_COLLAPSE_ALL)
368             .numberFormatterBoth(NumberFormatter::with()
369                 .unit(GBP)
370                 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
371                 .precision(Precision::integer())),
372         Locale("en-us"),
373         u"GBP 1–5",
374         u"~GBP 5",  // TODO: Fix this at some point
375         u"~GBP 5",
376         u"GBP 0–3",
377         u"~GBP 0",
378         u"GBP 3–3,000",
379         u"GBP 3,000–5,000",
380         u"GBP 4,999–5,001",
381         u"~GBP 5,000",
382         u"GBP 5,000–5,000,000");
383 
384     // Default collapse on measurement unit is in testBasic()
385 
386     assertFormatRange(
387         u"No collapse on measurement unit",
388         NumberRangeFormatter::with()
389             .collapse(UNUM_RANGE_COLLAPSE_NONE)
390             .numberFormatterBoth(NumberFormatter::with().unit(METER)),
391         Locale("en-us"),
392         u"1 m – 5 m",
393         u"~5 m",
394         u"~5 m",
395         u"0 m – 3 m",
396         u"~0 m",
397         u"3 m – 3,000 m",
398         u"3,000 m – 5,000 m",
399         u"4,999 m – 5,001 m",
400         u"~5,000 m",
401         u"5,000 m – 5,000,000 m");
402 
403     assertFormatRange(
404         u"Unit collapse on measurement unit",
405         NumberRangeFormatter::with()
406             .collapse(UNUM_RANGE_COLLAPSE_UNIT)
407             .numberFormatterBoth(NumberFormatter::with().unit(METER)),
408         Locale("en-us"),
409         u"1–5 m",
410         u"~5 m",
411         u"~5 m",
412         u"0–3 m",
413         u"~0 m",
414         u"3–3,000 m",
415         u"3,000–5,000 m",
416         u"4,999–5,001 m",
417         u"~5,000 m",
418         u"5,000–5,000,000 m");
419 
420     assertFormatRange(
421         u"All collapse on measurement unit",
422         NumberRangeFormatter::with()
423             .collapse(UNUM_RANGE_COLLAPSE_ALL)
424             .numberFormatterBoth(NumberFormatter::with().unit(METER)),
425         Locale("en-us"),
426         u"1–5 m",
427         u"~5 m",
428         u"~5 m",
429         u"0–3 m",
430         u"~0 m",
431         u"3–3,000 m",
432         u"3,000–5,000 m",
433         u"4,999–5,001 m",
434         u"~5,000 m",
435         u"5,000–5,000,000 m");
436 
437     assertFormatRange(
438         u"Default collapse, long-form compact notation",
439         NumberRangeFormatter::with()
440             .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
441         Locale("de-CH"),
442         u"1–5",
443         u"≈5",
444         u"≈5",
445         u"0–3",
446         u"≈0",
447         u"3–3 Tausend",
448         u"3–5 Tausend",
449         u"≈5 Tausend",
450         u"≈5 Tausend",
451         u"5 Tausend – 5 Millionen");
452 
453     assertFormatRange(
454         u"Unit collapse, long-form compact notation",
455         NumberRangeFormatter::with()
456             .collapse(UNUM_RANGE_COLLAPSE_UNIT)
457             .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
458         Locale("de-CH"),
459         u"1–5",
460         u"≈5",
461         u"≈5",
462         u"0–3",
463         u"≈0",
464         u"3–3 Tausend",
465         u"3 Tausend – 5 Tausend",
466         u"≈5 Tausend",
467         u"≈5 Tausend",
468         u"5 Tausend – 5 Millionen");
469 
470     assertFormatRange(
471         u"Default collapse on measurement unit with compact-short notation",
472         NumberRangeFormatter::with()
473             .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
474         Locale("en-us"),
475         u"1–5 m",
476         u"~5 m",
477         u"~5 m",
478         u"0–3 m",
479         u"~0 m",
480         u"3–3K m",
481         u"3K – 5K m",
482         u"~5K m",
483         u"~5K m",
484         u"5K – 5M m");
485 
486     assertFormatRange(
487         u"No collapse on measurement unit with compact-short notation",
488         NumberRangeFormatter::with()
489             .collapse(UNUM_RANGE_COLLAPSE_NONE)
490             .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
491         Locale("en-us"),
492         u"1 m – 5 m",
493         u"~5 m",
494         u"~5 m",
495         u"0 m – 3 m",
496         u"~0 m",
497         u"3 m – 3K m",
498         u"3K m – 5K m",
499         u"~5K m",
500         u"~5K m",
501         u"5K m – 5M m");
502 
503     assertFormatRange(
504         u"Unit collapse on measurement unit with compact-short notation",
505         NumberRangeFormatter::with()
506             .collapse(UNUM_RANGE_COLLAPSE_UNIT)
507             .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
508         Locale("en-us"),
509         u"1–5 m",
510         u"~5 m",
511         u"~5 m",
512         u"0–3 m",
513         u"~0 m",
514         u"3–3K m",
515         u"3K – 5K m",
516         u"~5K m",
517         u"~5K m",
518         u"5K – 5M m");
519 
520     assertFormatRange(
521         u"All collapse on measurement unit with compact-short notation",
522         NumberRangeFormatter::with()
523             .collapse(UNUM_RANGE_COLLAPSE_ALL)
524             .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
525         Locale("en-us"),
526         u"1–5 m",
527         u"~5 m",
528         u"~5 m",
529         u"0–3 m",
530         u"~0 m",
531         u"3–3K m",
532         u"3–5K m",  // this one is the key use case for ALL
533         u"~5K m",
534         u"~5K m",
535         u"5K – 5M m");
536 
537     assertFormatRange(
538         u"No collapse on scientific notation",
539         NumberRangeFormatter::with()
540             .collapse(UNUM_RANGE_COLLAPSE_NONE)
541             .numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
542         Locale("en-us"),
543         u"1E0 – 5E0",
544         u"~5E0",
545         u"~5E0",
546         u"0E0 – 3E0",
547         u"~0E0",
548         u"3E0 – 3E3",
549         u"3E3 – 5E3",
550         u"4.999E3 – 5.001E3",
551         u"~5E3",
552         u"5E3 – 5E6");
553 
554     assertFormatRange(
555         u"All collapse on scientific notation",
556         NumberRangeFormatter::with()
557             .collapse(UNUM_RANGE_COLLAPSE_ALL)
558             .numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
559         Locale("en-us"),
560         u"1–5E0",
561         u"~5E0",
562         u"~5E0",
563         u"0–3E0",
564         u"~0E0",
565         u"3E0 – 3E3",
566         u"3–5E3",
567         u"4.999–5.001E3",
568         u"~5E3",
569         u"5E3 – 5E6");
570 
571     // TODO: Test compact currency?
572     // The code is not smart enough to differentiate the notation from the unit.
573 }
574 
testIdentity()575 void NumberRangeFormatterTest::testIdentity() {
576     assertFormatRange(
577         u"Identity fallback Range",
578         NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_RANGE),
579         Locale("en-us"),
580         u"1–5",
581         u"5–5",
582         u"5–5",
583         u"0–3",
584         u"0–0",
585         u"3–3,000",
586         u"3,000–5,000",
587         u"4,999–5,001",
588         u"5,000–5,000",
589         u"5,000–5,000,000");
590 
591     assertFormatRange(
592         u"Identity fallback Approximately or Single Value",
593         NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE),
594         Locale("en-us"),
595         u"1–5",
596         u"~5",
597         u"5",
598         u"0–3",
599         u"0",
600         u"3–3,000",
601         u"3,000–5,000",
602         u"4,999–5,001",
603         u"5,000",
604         u"5,000–5,000,000");
605 
606     assertFormatRange(
607         u"Identity fallback Single Value",
608         NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE),
609         Locale("en-us"),
610         u"1–5",
611         u"5",
612         u"5",
613         u"0–3",
614         u"0",
615         u"3–3,000",
616         u"3,000–5,000",
617         u"4,999–5,001",
618         u"5,000",
619         u"5,000–5,000,000");
620 
621     assertFormatRange(
622         u"Identity fallback Approximately or Single Value with compact notation",
623         NumberRangeFormatter::with()
624             .identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE)
625             .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort())),
626         Locale("en-us"),
627         u"1–5",
628         u"~5",
629         u"5",
630         u"0–3",
631         u"0",
632         u"3–3K",
633         u"3K – 5K",
634         u"~5K",
635         u"5K",
636         u"5K – 5M");
637 
638     assertFormatRange(
639         u"Approximately in middle of unit string",
640         NumberRangeFormatter::with().numberFormatterBoth(
641             NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
642         Locale("zh-Hant"),
643         u"華氏 1-5 度",
644         u"華氏 ~5 度",
645         u"華氏 ~5 度",
646         u"華氏 0-3 度",
647         u"華氏 ~0 度",
648         u"華氏 3-3,000 度",
649         u"華氏 3,000-5,000 度",
650         u"華氏 4,999-5,001 度",
651         u"華氏 ~5,000 度",
652         u"華氏 5,000-5,000,000 度");
653 }
654 
testDifferentFormatters()655 void NumberRangeFormatterTest::testDifferentFormatters() {
656     assertFormatRange(
657         u"Different rounding rules",
658         NumberRangeFormatter::with()
659             .numberFormatterFirst(NumberFormatter::with().precision(Precision::integer()))
660             .numberFormatterSecond(NumberFormatter::with().precision(Precision::fixedSignificantDigits(2))),
661         Locale("en-us"),
662         u"1–5.0",
663         u"5–5.0",
664         u"5–5.0",
665         u"0–3.0",
666         u"0–0.0",
667         u"3–3,000",
668         u"3,000–5,000",
669         u"4,999–5,000",
670         u"5,000–5,000",  // TODO: Should this one be ~5,000?
671         u"5,000–5,000,000");
672 }
673 
testNaNInfinity()674 void NumberRangeFormatterTest::testNaNInfinity() {
675     IcuTestErrorCode status(*this, "testNaNInfinity");
676 
677     auto lnf = NumberRangeFormatter::withLocale("en");
678     auto result1 = lnf.formatFormattableRange(-uprv_getInfinity(), 0, status);
679     auto result2 = lnf.formatFormattableRange(0, uprv_getInfinity(), status);
680     auto result3 = lnf.formatFormattableRange(-uprv_getInfinity(), uprv_getInfinity(), status);
681     auto result4 = lnf.formatFormattableRange(uprv_getNaN(), 0, status);
682     auto result5 = lnf.formatFormattableRange(0, uprv_getNaN(), status);
683     auto result6 = lnf.formatFormattableRange(uprv_getNaN(), uprv_getNaN(), status);
684     auto result7 = lnf.formatFormattableRange({"1000", status}, {"Infinity", status}, status);
685     auto result8 = lnf.formatFormattableRange({"-Infinity", status}, {"NaN", status}, status);
686 
687     assertEquals("0 - inf", u"-∞ – 0", result1.toTempString(status));
688     assertEquals("-inf - 0", u"0–∞", result2.toTempString(status));
689     assertEquals("-inf - inf", u"-∞ – ∞", result3.toTempString(status));
690     assertEquals("NaN - 0", u"NaN–0", result4.toTempString(status));
691     assertEquals("0 - NaN", u"0–NaN", result5.toTempString(status));
692     assertEquals("NaN - NaN", u"~NaN", result6.toTempString(status));
693     assertEquals("1000 - inf", u"1,000–∞", result7.toTempString(status));
694     assertEquals("-inf - NaN", u"-∞ – NaN", result8.toTempString(status));
695 }
696 
testPlurals()697 void NumberRangeFormatterTest::testPlurals() {
698     IcuTestErrorCode status(*this, "testPlurals");
699 
700     // Locale sl has interesting plural forms:
701     // GBP{
702     //     one{"britanski funt"}
703     //     two{"britanska funta"}
704     //     few{"britanski funti"}
705     //     other{"britanskih funtov"}
706     // }
707     Locale locale("sl");
708 
709     UnlocalizedNumberFormatter unf = NumberFormatter::with()
710         .unit(GBP)
711         .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
712         .precision(Precision::integer());
713     LocalizedNumberFormatter lnf = unf.locale(locale);
714 
715     // For comparison, run the non-range version of the formatter
716     assertEquals(Int64ToUnicodeString(1), u"1 britanski funt", lnf.formatDouble(1, status).toString(status));
717     assertEquals(Int64ToUnicodeString(2), u"2 britanska funta", lnf.formatDouble(2, status).toString(status));
718     assertEquals(Int64ToUnicodeString(3), u"3 britanski funti", lnf.formatDouble(3, status).toString(status));
719     assertEquals(Int64ToUnicodeString(5), u"5 britanskih funtov", lnf.formatDouble(5, status).toString(status));
720     if (status.errIfFailureAndReset()) { return; }
721 
722     LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::with()
723         .numberFormatterBoth(unf)
724         .identityFallback(UNUM_IDENTITY_FALLBACK_RANGE)
725         .locale(locale);
726 
727     struct TestCase {
728         double first;
729         double second;
730         const char16_t* expected;
731     } cases[] = {
732         {1, 1, u"1–1 britanski funti"}, // one + one -> few
733         {1, 2, u"1–2 britanska funta"}, // one + two -> two
734         {1, 3, u"1–3 britanski funti"}, // one + few -> few
735         {1, 5, u"1–5 britanskih funtov"}, // one + other -> other
736         {2, 1, u"2–1 britanski funti"}, // two + one -> few
737         {2, 2, u"2–2 britanska funta"}, // two + two -> two
738         {2, 3, u"2–3 britanski funti"}, // two + few -> few
739         {2, 5, u"2–5 britanskih funtov"}, // two + other -> other
740         {3, 1, u"3–1 britanski funti"}, // few + one -> few
741         {3, 2, u"3–2 britanska funta"}, // few + two -> two
742         {3, 3, u"3–3 britanski funti"}, // few + few -> few
743         {3, 5, u"3–5 britanskih funtov"}, // few + other -> other
744         {5, 1, u"5–1 britanski funti"}, // other + one -> few
745         {5, 2, u"5–2 britanska funta"}, // other + two -> two
746         {5, 3, u"5–3 britanski funti"}, // other + few -> few
747         {5, 5, u"5–5 britanskih funtov"}, // other + other -> other
748     };
749     for (auto& cas : cases) {
750         UnicodeString message = Int64ToUnicodeString(static_cast<int64_t>(cas.first));
751         message += u" ";
752         message += Int64ToUnicodeString(static_cast<int64_t>(cas.second));
753         status.setScope(message);
754         UnicodeString actual = lnrf.formatFormattableRange(cas.first, cas.second, status).toString(status);
755         assertEquals(message, cas.expected, actual);
756         status.errIfFailureAndReset();
757     }
758 }
759 
testFieldPositions()760 void NumberRangeFormatterTest::testFieldPositions() {
761     {
762         const char16_t* message = u"Field position test 1";
763         const char16_t* expectedString = u"3K – 5K m";
764         FormattedNumberRange result = assertFormattedRangeEquals(
765             message,
766             NumberRangeFormatter::with()
767                 .numberFormatterBoth(NumberFormatter::with()
768                     .unit(METER)
769                     .notation(Notation::compactShort()))
770                 .locale("en-us"),
771             3000,
772             5000,
773             expectedString);
774         static const UFieldPositionWithCategory expectedFieldPositions[] = {
775             // category, field, begin index, end index
776             {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, 0, 2},
777             {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 1},
778             {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 1, 2},
779             {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, 5, 7},
780             {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 5, 6},
781             {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 6, 7},
782             {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD, 8, 9}};
783         checkMixedFormattedValue(
784             message,
785             result,
786             expectedString,
787             expectedFieldPositions,
788             UPRV_LENGTHOF(expectedFieldPositions));
789     }
790 
791     {
792         const char16_t* message = u"Field position test 2";
793         const char16_t* expectedString = u"87,654,321–98,765,432";
794         FormattedNumberRange result = assertFormattedRangeEquals(
795             message,
796             NumberRangeFormatter::withLocale("en-us"),
797             87654321,
798             98765432,
799             expectedString);
800         static const UFieldPositionWithCategory expectedFieldPositions[] = {
801             // category, field, begin index, end index
802             {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, 0, 10},
803             {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 2, 3},
804             {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 6, 7},
805             {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 10},
806             {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, 11, 21},
807             {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 13, 14},
808             {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 17, 18},
809             {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 11, 21}};
810         checkMixedFormattedValue(
811             message,
812             result,
813             expectedString,
814             expectedFieldPositions,
815             UPRV_LENGTHOF(expectedFieldPositions));
816     }
817 
818     {
819         const char16_t* message = u"Field position with approximately sign";
820         const char16_t* expectedString = u"~-100";
821         FormattedNumberRange result = assertFormattedRangeEquals(
822             message,
823             NumberRangeFormatter::withLocale("en-us"),
824             -100,
825             -100,
826             expectedString);
827         static const UFieldPositionWithCategory expectedFieldPositions[] = {
828             // category, field, begin index, end index
829             {UFIELD_CATEGORY_NUMBER, UNUM_APPROXIMATELY_SIGN_FIELD, 0, 1},
830             {UFIELD_CATEGORY_NUMBER, UNUM_SIGN_FIELD, 1, 2},
831             {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 2, 5}};
832         checkMixedFormattedValue(
833             message,
834             result,
835             expectedString,
836             expectedFieldPositions,
837             UPRV_LENGTHOF(expectedFieldPositions));
838     }
839 }
840 
testCopyMove()841 void NumberRangeFormatterTest::testCopyMove() {
842     IcuTestErrorCode status(*this, "testCopyMove");
843 
844     // Default constructors
845     LocalizedNumberRangeFormatter l1;
846     assertEquals("Initial behavior", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status));
847     if (status.errDataIfFailureAndReset()) { return; }
848 
849     // Setup
850     l1 = NumberRangeFormatter::withLocale("fr-FR")
851         .numberFormatterBoth(NumberFormatter::with().unit(USD));
852     assertEquals("Currency behavior", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
853 
854     // Copy constructor
855     LocalizedNumberRangeFormatter l2 = l1;
856     assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
857 
858     // Move constructor
859     LocalizedNumberRangeFormatter l3 = std::move(l1);
860     assertEquals("Move constructor", u"1,00–5,00 $US", l3.formatFormattableRange(1, 5, status).toString(status));
861 
862     // Reset objects for assignment tests
863     l1 = NumberRangeFormatter::withLocale("en-us");
864     l2 = NumberRangeFormatter::withLocale("en-us");
865     assertEquals("Rest behavior, l1", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status));
866     assertEquals("Rest behavior, l2", u"1–5", l2.formatFormattableRange(1, 5, status).toString(status));
867 
868     // Copy assignment
869     l1 = l3;
870     assertEquals("Copy constructor", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
871 
872     // Move assignment
873     l2 = std::move(l3);
874     assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
875 
876     // FormattedNumberRange
877     FormattedNumberRange result = l1.formatFormattableRange(1, 5, status);
878     assertEquals("FormattedNumberRange move constructor", u"1,00–5,00 $US", result.toString(status));
879     result = l1.formatFormattableRange(3, 6, status);
880     assertEquals("FormattedNumberRange move assignment", u"3,00–6,00 $US", result.toString(status));
881     FormattedNumberRange fnrdefault;
882     fnrdefault.toString(status);
883     status.expectErrorAndReset(U_INVALID_STATE_ERROR);
884 }
885 
toObject()886 void NumberRangeFormatterTest::toObject() {
887     IcuTestErrorCode status(*this, "toObject");
888 
889     // const lvalue version
890     {
891         LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en");
892         LocalPointer<LocalizedNumberRangeFormatter> lnf2(lnf.clone());
893         assertFalse("should create successfully, const lvalue", lnf2.isNull());
894         assertEquals("object API test, const lvalue", u"5–7",
895             lnf2->formatFormattableRange(5, 7, status).toString(status));
896     }
897 
898     // rvalue reference version
899     {
900         LocalPointer<LocalizedNumberRangeFormatter> lnf(
901             NumberRangeFormatter::withLocale("en").clone());
902         assertFalse("should create successfully, rvalue reference", lnf.isNull());
903         assertEquals("object API test, rvalue reference", u"5–7",
904             lnf->formatFormattableRange(5, 7, status).toString(status));
905     }
906 
907     // to std::unique_ptr via assignment
908     {
909         std::unique_ptr<LocalizedNumberRangeFormatter> lnf =
910             NumberRangeFormatter::withLocale("en").clone();
911         assertTrue("should create successfully, unique_ptr B", static_cast<bool>(lnf));
912         assertEquals("object API test, unique_ptr B", u"5–7",
913             lnf->formatFormattableRange(5, 7, status).toString(status));
914     }
915 
916     // make sure no memory leaks
917     {
918         NumberRangeFormatter::with().clone();
919     }
920 }
921 
locale()922 void NumberRangeFormatterTest::locale() {
923     IcuTestErrorCode status(*this, "locale");
924 
925     LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en")
926         .identityFallback(UNUM_IDENTITY_FALLBACK_RANGE);
927     UnlocalizedNumberRangeFormatter unf1 = lnf.withoutLocale();
928     UnlocalizedNumberRangeFormatter unf2 = NumberRangeFormatter::with()
929         .identityFallback(UNUM_IDENTITY_FALLBACK_RANGE)
930         .locale("ar-EG")
931         .withoutLocale();
932 
933     FormattedNumberRange res1 = unf1.locale("bn").formatFormattableRange(5, 5, status);
934     assertEquals(u"res1", u"\u09EB\u2013\u09EB", res1.toTempString(status));
935     FormattedNumberRange res2 = unf2.locale("ja-JP").formatFormattableRange(5, 5, status);
936     assertEquals(u"res2", u"5\uFF5E5", res2.toTempString(status));
937 }
938 
testGetDecimalNumbers()939 void NumberRangeFormatterTest::testGetDecimalNumbers() {
940     IcuTestErrorCode status(*this, "testGetDecimalNumbers");
941 
942     LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en")
943         .numberFormatterBoth(NumberFormatter::with().unit(USD));
944 
945     // Range of numbers
946     {
947         FormattedNumberRange range = lnf.formatFormattableRange(1, 5, status);
948         assertEquals("Range: Formatted string should be as expected",
949             u"$1.00 \u2013 $5.00",
950             range.toString(status));
951         auto decimalNumbers = range.getDecimalNumbers<std::string>(status);
952         // TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem?
953         if (logKnownIssue("ICU-21281")) {
954             assertEquals("First decimal number", "1", decimalNumbers.first.c_str());
955             assertEquals("Second decimal number", "5", decimalNumbers.second.c_str());
956         } else {
957             assertEquals("First decimal number", "1.00", decimalNumbers.first.c_str());
958             assertEquals("Second decimal number", "5.00", decimalNumbers.second.c_str());
959         }
960     }
961 
962     // Identity fallback
963     {
964         FormattedNumberRange range = lnf.formatFormattableRange(3, 3, status);
965         assertEquals("Identity: Formatted string should be as expected",
966             u"~$3.00",
967             range.toString(status));
968         auto decimalNumbers = range.getDecimalNumbers<std::string>(status);
969         // NOTE: DecNum doesn't retain trailing zeros. Is that a problem?
970         // TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem?
971         if (logKnownIssue("ICU-21281")) {
972             assertEquals("First decimal number", "3", decimalNumbers.first.c_str());
973             assertEquals("Second decimal number", "3", decimalNumbers.second.c_str());
974         } else {
975             assertEquals("First decimal number", "3.00", decimalNumbers.first.c_str());
976             assertEquals("Second decimal number", "3.00", decimalNumbers.second.c_str());
977         }
978     }
979 }
980 
test21684_Performance()981 void NumberRangeFormatterTest::test21684_Performance() {
982     IcuTestErrorCode status(*this, "test21684_Performance");
983     LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en");
984     // The following two lines of code should finish quickly.
985     lnf.formatFormattableRange({"-1e99999", status}, {"0", status}, status);
986     lnf.formatFormattableRange({"0", status}, {"1e99999", status}, status);
987 }
988 
test21358_SignPosition()989 void NumberRangeFormatterTest::test21358_SignPosition() {
990     IcuTestErrorCode status(*this, "test21358_SignPosition");
991 
992     // de-CH has currency pattern "¤ #,##0.00;¤-#,##0.00"
993     assertFormatRange(
994         u"Approximately sign position with spacing from pattern",
995         NumberRangeFormatter::with()
996             .numberFormatterBoth(NumberFormatter::with().unit(CHF)),
997         Locale("de-CH"),
998         u"CHF 1.00–5.00",
999         u"CHF≈5.00",
1000         u"CHF≈5.00",
1001         u"CHF 0.00–3.00",
1002         u"CHF≈0.00",
1003         u"CHF 3.00–3’000.00",
1004         u"CHF 3’000.00–5’000.00",
1005         u"CHF 4’999.00–5’001.00",
1006         u"CHF≈5’000.00",
1007         u"CHF 5’000.00–5’000’000.00");
1008 
1009     // TODO(ICU-21420): Move the sign to the inside of the number
1010     assertFormatRange(
1011         u"Approximately sign position with currency spacing",
1012         NumberRangeFormatter::with()
1013             .numberFormatterBoth(NumberFormatter::with().unit(CHF)),
1014         Locale("en-US"),
1015         u"CHF 1.00–5.00",
1016         u"~CHF 5.00",
1017         u"~CHF 5.00",
1018         u"CHF 0.00–3.00",
1019         u"~CHF 0.00",
1020         u"CHF 3.00–3,000.00",
1021         u"CHF 3,000.00–5,000.00",
1022         u"CHF 4,999.00–5,001.00",
1023         u"~CHF 5,000.00",
1024         u"CHF 5,000.00–5,000,000.00");
1025 
1026     {
1027         LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("de-CH");
1028         UnicodeString actual = lnrf.formatFormattableRange(-2, 3, status).toString(status);
1029         assertEquals("Negative to positive range", u"-2 – 3", actual);
1030     }
1031 
1032     {
1033         LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("de-CH")
1034             .numberFormatterBoth(NumberFormatter::forSkeleton(u"%", status));
1035         UnicodeString actual = lnrf.formatFormattableRange(-2, 3, status).toString(status);
1036         assertEquals("Negative to positive percent", u"-2% – 3%", actual);
1037     }
1038 
1039     {
1040         LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("de-CH");
1041         UnicodeString actual = lnrf.formatFormattableRange(2, -3, status).toString(status);
1042         assertEquals("Positive to negative range", u"2–-3", actual);
1043     }
1044 
1045     {
1046         LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("de-CH")
1047             .numberFormatterBoth(NumberFormatter::forSkeleton(u"%", status));
1048         UnicodeString actual = lnrf.formatFormattableRange(2, -3, status).toString(status);
1049         assertEquals("Positive to negative percent", u"2% – -3%", actual);
1050     }
1051 }
1052 
testCreateLNRFFromNumberingSystemInSkeleton()1053 void NumberRangeFormatterTest::testCreateLNRFFromNumberingSystemInSkeleton() {
1054     IcuTestErrorCode status(*this, "testCreateLNRFFromNumberingSystemInSkeleton");
1055     {
1056         LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("en")
1057             .numberFormatterBoth(NumberFormatter::forSkeleton(
1058                 u".### rounding-mode-half-up", status));
1059         UnicodeString actual = lnrf.formatFormattableRange(1, 234, status).toString(status);
1060         assertEquals("default numbering system", u"1–234", actual);
1061         status.errIfFailureAndReset("default numbering system");
1062     }
1063     {
1064         LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("th")
1065             .numberFormatterBoth(NumberFormatter::forSkeleton(
1066                 u".### rounding-mode-half-up numbering-system/thai", status));
1067         UnicodeString actual = lnrf.formatFormattableRange(1, 234, status).toString(status);
1068         assertEquals("Thai numbering system", u"๑-๒๓๔", actual);
1069         status.errIfFailureAndReset("thai numbering system");
1070     }
1071     {
1072         LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("en")
1073             .numberFormatterBoth(NumberFormatter::forSkeleton(
1074                 u".### rounding-mode-half-up numbering-system/arab", status));
1075         UnicodeString actual = lnrf.formatFormattableRange(1, 234, status).toString(status);
1076         assertEquals("Arabic numbering system", u"١–٢٣٤", actual);
1077         status.errIfFailureAndReset("arab numbering system");
1078     }
1079     {
1080         LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("en")
1081             .numberFormatterFirst(NumberFormatter::forSkeleton(u"numbering-system/arab", status))
1082             .numberFormatterSecond(NumberFormatter::forSkeleton(u"numbering-system/arab", status));
1083         UnicodeString actual = lnrf.formatFormattableRange(1, 234, status).toString(status);
1084         assertEquals("Double Arabic numbering system", u"١–٢٣٤", actual);
1085         status.errIfFailureAndReset("double arab numbering system");
1086     }
1087     {
1088         LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("en")
1089             .numberFormatterFirst(NumberFormatter::forSkeleton(u"numbering-system/arab", status))
1090             .numberFormatterSecond(NumberFormatter::forSkeleton(u"numbering-system/latn", status));
1091         // Note: The error is not set until `formatFormattableRange` because this is where the
1092         // formatter object gets built.
1093         lnrf.formatFormattableRange(1, 234, status);
1094         status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
1095     }
1096 }
1097 
test21683_StateLeak()1098 void NumberRangeFormatterTest::test21683_StateLeak() {
1099     IcuTestErrorCode status(*this, "test21683_StateLeak");
1100     UNumberRangeFormatter* nrf = nullptr;
1101     UFormattedNumberRange* result = nullptr;
1102     UConstrainedFieldPosition* fpos = nullptr;
1103 
1104     struct Range {
1105         double start;
1106         double end;
1107         const char16_t* expected;
1108         int numFields;
1109     } ranges[] = {
1110         {1, 2, u"1\u20132", 4},
1111         {1, 1, u"~1", 2},
1112     };
1113 
1114     UParseError* perror = nullptr;
1115     nrf = unumrf_openForSkeletonWithCollapseAndIdentityFallback(
1116         u"", -1,
1117         UNUM_RANGE_COLLAPSE_AUTO,
1118         UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
1119         "en", perror, status);
1120     if (status.errIfFailureAndReset("unumrf_openForSkeletonWithCollapseAndIdentityFallback")) {
1121         goto cleanup;
1122     }
1123 
1124     result = unumrf_openResult(status);
1125     if (status.errIfFailureAndReset("unumrf_openResult")) { goto cleanup; }
1126 
1127     for (auto range : ranges) {
1128         unumrf_formatDoubleRange(nrf, range.start, range.end, result, status);
1129         if (status.errIfFailureAndReset("unumrf_formatDoubleRange")) { goto cleanup; }
1130 
1131         const auto* formattedValue = unumrf_resultAsValue(result, status);
1132         if (status.errIfFailureAndReset("unumrf_resultAsValue")) { goto cleanup; }
1133 
1134         int32_t utf16Length;
1135         const char16_t* utf16Str = ufmtval_getString(formattedValue, &utf16Length, status);
1136         if (status.errIfFailureAndReset("ufmtval_getString")) { goto cleanup; }
1137 
1138         assertEquals("Format", range.expected, utf16Str);
1139 
1140         ucfpos_close(fpos);
1141         fpos = ucfpos_open(status);
1142         if (status.errIfFailureAndReset("ucfpos_open")) { goto cleanup; }
1143 
1144         int numFields = 0;
1145         while (true) {
1146             bool hasMore = ufmtval_nextPosition(formattedValue, fpos, status);
1147             if (status.errIfFailureAndReset("ufmtval_nextPosition")) { goto cleanup; }
1148             if (!hasMore) {
1149                 break;
1150             }
1151             numFields++;
1152         }
1153         assertEquals("numFields", range.numFields, numFields);
1154     }
1155 
1156 cleanup:
1157     unumrf_close(nrf);
1158     unumrf_closeResult(result);
1159     ucfpos_close(fpos);
1160 }
1161 
test22288_DifferentStartEndSettings()1162 void NumberRangeFormatterTest::test22288_DifferentStartEndSettings() {
1163     IcuTestErrorCode status(*this, "test22288_DifferentStartEndSettings");
1164     LocalizedNumberRangeFormatter lnrf(NumberRangeFormatter
1165             ::withLocale("en")
1166             .collapse(UNUM_RANGE_COLLAPSE_UNIT)
1167             .numberFormatterFirst(
1168                 NumberFormatter::with()
1169                     .unit(CurrencyUnit("USD", status))
1170                     .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
1171                     .precision(Precision::integer())
1172                     .roundingMode(UNUM_ROUND_FLOOR))
1173             .numberFormatterSecond(
1174                 NumberFormatter::with()
1175                     .unit(CurrencyUnit("USD", status))
1176                     .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
1177                     .precision(Precision::integer())
1178                     .roundingMode(UNUM_ROUND_CEILING)));
1179         FormattedNumberRange result = lnrf.formatFormattableRange(2.5, 2.5, status);
1180         assertEquals("Should format successfully", u"2–3 US dollars", result.toString(status));
1181 }
1182 
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)1183 void  NumberRangeFormatterTest::assertFormatRange(
1184       const char16_t* message,
1185       const UnlocalizedNumberRangeFormatter& f,
1186       Locale locale,
1187       const char16_t* expected_10_50,
1188       const char16_t* expected_49_51,
1189       const char16_t* expected_50_50,
1190       const char16_t* expected_00_30,
1191       const char16_t* expected_00_00,
1192       const char16_t* expected_30_3K,
1193       const char16_t* expected_30K_50K,
1194       const char16_t* expected_49K_51K,
1195       const char16_t* expected_50K_50K,
1196       const char16_t* expected_50K_50M) {
1197     LocalizedNumberRangeFormatter l = f.locale(locale);
1198     assertFormattedRangeEquals(message, l, 1, 5, expected_10_50);
1199     assertFormattedRangeEquals(message, l, 4.9999999, 5.0000001, expected_49_51);
1200     assertFormattedRangeEquals(message, l, 5, 5, expected_50_50);
1201     assertFormattedRangeEquals(message, l, 0, 3, expected_00_30);
1202     assertFormattedRangeEquals(message, l, 0, 0, expected_00_00);
1203     assertFormattedRangeEquals(message, l, 3, 3000, expected_30_3K);
1204     assertFormattedRangeEquals(message, l, 3000, 5000, expected_30K_50K);
1205     assertFormattedRangeEquals(message, l, 4999, 5001, expected_49K_51K);
1206     assertFormattedRangeEquals(message, l, 5000, 5000, expected_50K_50K);
1207     assertFormattedRangeEquals(message, l, 5e3, 5e6, expected_50K_50M);
1208 }
1209 
assertFormattedRangeEquals(const char16_t * message,const LocalizedNumberRangeFormatter & l,double first,double second,const char16_t * expected)1210 FormattedNumberRange NumberRangeFormatterTest::assertFormattedRangeEquals(
1211       const char16_t* message,
1212       const LocalizedNumberRangeFormatter& l,
1213       double first,
1214       double second,
1215       const char16_t* expected) {
1216     IcuTestErrorCode status(*this, "assertFormattedRangeEquals");
1217     UnicodeString fullMessage = UnicodeString(message) + u": " + DoubleToUnicodeString(first) + u", " + DoubleToUnicodeString(second);
1218     status.setScope(fullMessage);
1219     FormattedNumberRange fnr = l.formatFormattableRange(first, second, status);
1220     UnicodeString actual = fnr.toString(status);
1221     assertEquals(fullMessage, expected, actual);
1222     return fnr;
1223 }
1224 
1225 
1226 #endif
1227