• 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           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