1 // © 2017 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 package com.ibm.icu.dev.test.number; 4 5 6 import java.lang.reflect.InvocationTargetException; 7 import java.lang.reflect.Method; 8 import java.math.BigDecimal; 9 import java.math.RoundingMode; 10 import java.text.FieldPosition; 11 import java.text.Format; 12 import java.util.HashMap; 13 import java.util.HashSet; 14 import java.util.Locale; 15 import java.util.Map; 16 import java.util.Set; 17 18 import org.junit.Assert; 19 import org.junit.Ignore; 20 import org.junit.Test; 21 22 import com.ibm.icu.dev.test.TestFmwk; 23 import com.ibm.icu.dev.test.format.FormattedValueTest; 24 import com.ibm.icu.dev.test.serializable.SerializableTestUtility; 25 import com.ibm.icu.impl.IllegalIcuArgumentException; 26 import com.ibm.icu.impl.number.Grouper; 27 import com.ibm.icu.impl.number.LocalizedNumberFormatterAsFormat; 28 import com.ibm.icu.impl.number.MacroProps; 29 import com.ibm.icu.impl.number.Padder; 30 import com.ibm.icu.impl.number.Padder.PadPosition; 31 import com.ibm.icu.impl.number.PatternStringParser; 32 import com.ibm.icu.number.CompactNotation; 33 import com.ibm.icu.number.FormattedNumber; 34 import com.ibm.icu.number.FractionPrecision; 35 import com.ibm.icu.number.IntegerWidth; 36 import com.ibm.icu.number.LocalizedNumberFormatter; 37 import com.ibm.icu.number.Notation; 38 import com.ibm.icu.number.NumberFormatter; 39 import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay; 40 import com.ibm.icu.number.NumberFormatter.GroupingStrategy; 41 import com.ibm.icu.number.NumberFormatter.RoundingPriority; 42 import com.ibm.icu.number.NumberFormatter.SignDisplay; 43 import com.ibm.icu.number.NumberFormatter.TrailingZeroDisplay; 44 import com.ibm.icu.number.NumberFormatter.UnitWidth; 45 import com.ibm.icu.number.Precision; 46 import com.ibm.icu.number.Scale; 47 import com.ibm.icu.number.ScientificNotation; 48 import com.ibm.icu.number.SkeletonSyntaxException; 49 import com.ibm.icu.number.UnlocalizedNumberFormatter; 50 import com.ibm.icu.text.ConstrainedFieldPosition; 51 import com.ibm.icu.text.DecimalFormatSymbols; 52 import com.ibm.icu.text.DisplayOptions; 53 import com.ibm.icu.text.DisplayOptions.GrammaticalCase; 54 import com.ibm.icu.text.DisplayOptions.NounClass; 55 import com.ibm.icu.text.NumberFormat; 56 import com.ibm.icu.text.NumberingSystem; 57 import com.ibm.icu.util.Currency; 58 import com.ibm.icu.util.Currency.CurrencyUsage; 59 import com.ibm.icu.util.CurrencyAmount; 60 import com.ibm.icu.util.Measure; 61 import com.ibm.icu.util.MeasureUnit; 62 import com.ibm.icu.util.NoUnit; 63 import com.ibm.icu.util.ULocale; 64 65 public class NumberFormatterApiTest extends TestFmwk { 66 67 private static final Currency USD = Currency.getInstance("USD"); 68 private static final Currency GBP = Currency.getInstance("GBP"); 69 private static final Currency CZK = Currency.getInstance("CZK"); 70 private static final Currency CAD = Currency.getInstance("CAD"); 71 private static final Currency ESP = Currency.getInstance("ESP"); 72 private static final Currency PTE = Currency.getInstance("PTE"); 73 private static final Currency RON = Currency.getInstance("RON"); 74 private static final Currency TWD = Currency.getInstance("TWD"); 75 private static final Currency TRY = Currency.getInstance("TRY"); 76 private static final Currency CNY = Currency.getInstance("CNY"); 77 78 @Test notationSimple()79 public void notationSimple() { 80 assertFormatDescending( 81 "Basic", 82 "", 83 "", 84 NumberFormatter.with(), 85 ULocale.ENGLISH, 86 "87,650", 87 "8,765", 88 "876.5", 89 "87.65", 90 "8.765", 91 "0.8765", 92 "0.08765", 93 "0.008765", 94 "0"); 95 96 assertFormatDescendingBig( 97 "Big Simple", 98 "notation-simple", 99 "", 100 NumberFormatter.with().notation(Notation.simple()), 101 ULocale.ENGLISH, 102 "87,650,000", 103 "8,765,000", 104 "876,500", 105 "87,650", 106 "8,765", 107 "876.5", 108 "87.65", 109 "8.765", 110 "0"); 111 112 assertFormatSingle( 113 "Basic with Negative Sign", 114 "", 115 "", 116 NumberFormatter.with(), 117 ULocale.ENGLISH, 118 -9876543.21, 119 "-9,876,543.21"); 120 } 121 122 @Test notationScientific()123 public void notationScientific() { 124 assertFormatDescending( 125 "Scientific", 126 "scientific", 127 "E0", 128 NumberFormatter.with().notation(Notation.scientific()), 129 ULocale.ENGLISH, 130 "8.765E4", 131 "8.765E3", 132 "8.765E2", 133 "8.765E1", 134 "8.765E0", 135 "8.765E-1", 136 "8.765E-2", 137 "8.765E-3", 138 "0E0"); 139 140 assertFormatDescending( 141 "Engineering", 142 "engineering", 143 "EE0", 144 NumberFormatter.with().notation(Notation.engineering()), 145 ULocale.ENGLISH, 146 "87.65E3", 147 "8.765E3", 148 "876.5E0", 149 "87.65E0", 150 "8.765E0", 151 "876.5E-3", 152 "87.65E-3", 153 "8.765E-3", 154 "0E0"); 155 156 assertFormatDescending( 157 "Scientific sign always shown", 158 "scientific/sign-always", 159 "E+!0", 160 NumberFormatter.with().notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS)), 161 ULocale.ENGLISH, 162 "8.765E+4", 163 "8.765E+3", 164 "8.765E+2", 165 "8.765E+1", 166 "8.765E+0", 167 "8.765E-1", 168 "8.765E-2", 169 "8.765E-3", 170 "0E+0"); 171 172 assertFormatDescending( 173 "Scientific min exponent digits", 174 "scientific/*ee", 175 "E00", 176 NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)), 177 ULocale.ENGLISH, 178 "8.765E04", 179 "8.765E03", 180 "8.765E02", 181 "8.765E01", 182 "8.765E00", 183 "8.765E-01", 184 "8.765E-02", 185 "8.765E-03", 186 "0E00"); 187 188 assertFormatSingle( 189 "Scientific Negative", 190 "scientific", 191 "E0", 192 NumberFormatter.with().notation(Notation.scientific()), 193 ULocale.ENGLISH, 194 -1000000, 195 "-1E6"); 196 197 assertFormatSingle( 198 "Scientific Infinity", 199 "scientific", 200 "E0", 201 NumberFormatter.with().notation(Notation.scientific()), 202 ULocale.ENGLISH, 203 Double.NEGATIVE_INFINITY, 204 "-∞"); 205 206 assertFormatSingle( 207 "Scientific NaN", 208 "scientific", 209 "E0", 210 NumberFormatter.with().notation(Notation.scientific()), 211 ULocale.ENGLISH, 212 Double.NaN, 213 "NaN"); 214 } 215 216 @Test notationCompact()217 public void notationCompact() { 218 assertFormatDescendingBig( 219 "Compact Short", 220 "compact-short", 221 "K", 222 NumberFormatter.with().notation(Notation.compactShort()), 223 ULocale.ENGLISH, 224 "88M", 225 "8.8M", 226 "876K", 227 "88K", 228 "8.8K", 229 "876", 230 "88", 231 "8.8", 232 "0"); 233 234 assertFormatDescendingBig( 235 "Compact Long", 236 "compact-long", 237 "KK", 238 NumberFormatter.with().notation(Notation.compactLong()), 239 ULocale.ENGLISH, 240 "88 million", 241 "8.8 million", 242 "876 thousand", 243 "88 thousand", 244 "8.8 thousand", 245 "876", 246 "88", 247 "8.8", 248 "0"); 249 250 assertFormatDescending( 251 "Compact Short Currency", 252 "compact-short currency/USD", 253 "K currency/USD", 254 NumberFormatter.with().notation(Notation.compactShort()).unit(USD), 255 ULocale.ENGLISH, 256 "$88K", 257 "$8.8K", 258 "$876", 259 "$88", 260 "$8.8", 261 "$0.88", 262 "$0.088", 263 "$0.0088", 264 "$0"); 265 266 assertFormatDescending( 267 "Compact Short with ISO Currency", 268 "compact-short currency/USD unit-width-iso-code", 269 "K currency/USD unit-width-iso-code", 270 NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.ISO_CODE), 271 ULocale.ENGLISH, 272 "USD 88K", 273 "USD 8.8K", 274 "USD 876", 275 "USD 88", 276 "USD 8.8", 277 "USD 0.88", 278 "USD 0.088", 279 "USD 0.0088", 280 "USD 0"); 281 282 assertFormatDescending( 283 "Compact Short with Long Name Currency", 284 "compact-short currency/USD unit-width-full-name", 285 "K currency/USD unit-width-full-name", 286 NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.FULL_NAME), 287 ULocale.ENGLISH, 288 "88K US dollars", 289 "8.8K US dollars", 290 "876 US dollars", 291 "88 US dollars", 292 "8.8 US dollars", 293 "0.88 US dollars", 294 "0.088 US dollars", 295 "0.0088 US dollars", 296 "0 US dollars"); 297 298 // Note: Most locales don't have compact long currency, so this currently falls back to short. 299 // This test case should be fixed when proper compact long currency patterns are added. 300 assertFormatDescending( 301 "Compact Long Currency", 302 "compact-long currency/USD", 303 "KK currency/USD", 304 NumberFormatter.with().notation(Notation.compactLong()).unit(USD), 305 ULocale.ENGLISH, 306 "$88K", // should be something like "$88 thousand" 307 "$8.8K", 308 "$876", 309 "$88", 310 "$8.8", 311 "$0.88", 312 "$0.088", 313 "$0.0088", 314 "$0"); 315 316 // Note: Most locales don't have compact long currency, so this currently falls back to short. 317 // This test case should be fixed when proper compact long currency patterns are added. 318 assertFormatDescending( 319 "Compact Long with ISO Currency", 320 "compact-long currency/USD unit-width-iso-code", 321 "KK currency/USD unit-width-iso-code", 322 NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.ISO_CODE), 323 ULocale.ENGLISH, 324 "USD 88K", // should be something like "USD 88 thousand" 325 "USD 8.8K", 326 "USD 876", 327 "USD 88", 328 "USD 8.8", 329 "USD 0.88", 330 "USD 0.088", 331 "USD 0.0088", 332 "USD 0"); 333 334 // TODO: This behavior could be improved and should be revisited. 335 assertFormatDescending( 336 "Compact Long with Long Name Currency", 337 "compact-long currency/USD unit-width-full-name", 338 "KK currency/USD unit-width-full-name", 339 NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.FULL_NAME), 340 ULocale.ENGLISH, 341 "88 thousand US dollars", 342 "8.8 thousand US dollars", 343 "876 US dollars", 344 "88 US dollars", 345 "8.8 US dollars", 346 "0.88 US dollars", 347 "0.088 US dollars", 348 "0.0088 US dollars", 349 "0 US dollars"); 350 351 assertFormatSingle( 352 "Compact Plural One", 353 "compact-long", 354 "KK", 355 NumberFormatter.with().notation(Notation.compactLong()), 356 ULocale.forLanguageTag("es"), 357 1000000, 358 "1 millón"); 359 360 assertFormatSingle( 361 "Compact Plural One with rounding", 362 "compact-long precision-integer", 363 "KK precision-integer", 364 NumberFormatter.with().notation(Notation.compactLong()).precision(Precision.integer()), 365 ULocale.forLanguageTag("es"), 366 1222222, 367 "1 millón"); 368 369 assertFormatSingle( 370 "Compact Plural Other", 371 "compact-long", 372 "KK", 373 NumberFormatter.with().notation(Notation.compactLong()), 374 ULocale.forLanguageTag("es"), 375 2000000, 376 "2 millones"); 377 378 assertFormatSingle( 379 "Compact with Negative Sign", 380 "compact-short", 381 "K", 382 NumberFormatter.with().notation(Notation.compactShort()), 383 ULocale.ENGLISH, 384 -9876543.21, 385 "-9.9M"); 386 387 assertFormatSingle( 388 "Compact Rounding", 389 "compact-short", 390 "K", 391 NumberFormatter.with().notation(Notation.compactShort()), 392 ULocale.ENGLISH, 393 990000, 394 "990K"); 395 396 assertFormatSingle( 397 "Compact Rounding", 398 "compact-short", 399 "K", 400 NumberFormatter.with().notation(Notation.compactShort()), 401 ULocale.ENGLISH, 402 999000, 403 "999K"); 404 405 assertFormatSingle( 406 "Compact Rounding", 407 "compact-short", 408 "K", 409 NumberFormatter.with().notation(Notation.compactShort()), 410 ULocale.ENGLISH, 411 999900, 412 "1M"); 413 414 assertFormatSingle( 415 "Compact Rounding", 416 "compact-short", 417 "K", 418 NumberFormatter.with().notation(Notation.compactShort()), 419 ULocale.ENGLISH, 420 9900000, 421 "9.9M"); 422 423 assertFormatSingle( 424 "Compact Rounding", 425 "compact-short", 426 "K", 427 NumberFormatter.with().notation(Notation.compactShort()), 428 ULocale.ENGLISH, 429 9990000, 430 "10M"); 431 432 assertFormatSingle( 433 "Compact in zh-Hant-HK", 434 "compact-short", 435 "K", 436 NumberFormatter.with().notation(Notation.compactShort()), 437 new ULocale("zh-Hant-HK"), 438 1e7, 439 "10M"); 440 441 assertFormatSingle( 442 "Compact in zh-Hant", 443 "compact-short", 444 "K", 445 NumberFormatter.with().notation(Notation.compactShort()), 446 new ULocale("zh-Hant"), 447 1e7, 448 "1000\u842C"); 449 450 assertFormatSingle( 451 "Compact with plural form =1 (ICU-21258)", 452 "compact-long", 453 "KK", 454 NumberFormatter.with().notation(Notation.compactLong()), 455 ULocale.FRANCE, 456 1e3, 457 "mille"); 458 459 assertFormatSingle( 460 "Compact Infinity", 461 "compact-short", 462 "K", 463 NumberFormatter.with().notation(Notation.compactShort()), 464 ULocale.ENGLISH, 465 Double.NEGATIVE_INFINITY, 466 "-∞"); 467 468 assertFormatSingle( 469 "Compact NaN", 470 "compact-short", 471 "K", 472 NumberFormatter.with().notation(Notation.compactShort()), 473 ULocale.ENGLISH, 474 Double.NaN, 475 "NaN"); 476 477 Map<String, Map<String, String>> compactCustomData = new HashMap<>(); 478 Map<String, String> entry = new HashMap<>(); 479 entry.put("one", "Kun"); 480 entry.put("other", "0KK"); 481 compactCustomData.put("1000", entry); 482 assertFormatSingle( 483 "Compact Somali No Figure", 484 null, // feature not supported in skeleton 485 null, 486 NumberFormatter.with().notation(CompactNotation.forCustomData(compactCustomData)), 487 ULocale.ENGLISH, 488 1000, 489 "Kun"); 490 } 491 492 @Test unitWithLocaleTags()493 public void unitWithLocaleTags() { 494 String[][] tests = { 495 // 0-message, 1- locale, 2- input unit, 3- input value, 4- usage, 5- output unit, 6- output value, 7- formatted number. 496 // Test without any tag behaviour 497 {"Test the locale without any addition and without usage", "en-US", "celsius", "0", null, "celsius", "0.0", "0 degrees Celsius"}, 498 {"Test the locale without any addition and usage", "en-US", "celsius", "0", "default", "fahrenheit", "32.0", "32 degrees Fahrenheit"}, 499 500 // Test the behaviour of the `mu` tag. 501 {"Test the locale with mu = celsius and without usage", "en-US-u-mu-celsius", "fahrenheit", "0", null, "fahrenheit", "0.0", "0 degrees Fahrenheit"}, 502 {"Test the locale with mu = celsius and with usage", "en-US-u-mu-celsius", "fahrenheit", "0", "default", "celsius", "-18.0", "-18 degrees Celsius"}, 503 {"Test the locale with mu = calsius (wrong spelling) and with usage", "en-US-u-mu-calsius", "fahrenheit", "0", "default", "fahrenheit", "0.0", "0 degrees Fahrenheit"}, 504 {"Test the locale with mu = fahrenheit and without usage", "en-US-u-mu-fahrenheit", "celsius", "0", null, "celsius", "0.0", "0 degrees Celsius"}, 505 {"Test the locale with mu = fahrenheit and with usage", "en-US-u-mu-fahrenheit", "celsius", "0", "default", "fahrenheit", "32.0", "32 degrees Fahrenheit"}, 506 {"Test the locale with mu = meter (only temprature units are supported) and with usage", "en-US-u-mu-meter", "foot", "0", "default", "foot", "0.0", "0 inches"}, 507 508 // Test the behaviour of the `ms` tag 509 {"Test the locale with ms = metric and without usage", "en-US-u-ms-metric", "fahrenheit", "0", null, "fahrenheit", "0.0", "0 degrees Fahrenheit"}, 510 {"Test the locale with ms = metric and with usage", "en-US-u-ms-metric", "fahrenheit", "0", "default", "celsius", "-18", "-18 degrees Celsius"}, 511 {"Test the locale with ms = Matric (wrong spelling) and with usage", "en-US-u-ms-Matric", "fahrenheit", "0", "default", "fahrenheit", "0.0", "0 degrees Fahrenheit"}, 512 513 // Test the behaviour of the `rg` tag 514 {"Test the locale with rg = UK and without usage", "en-US-u-rg-ukzzzz", "fahrenheit", "0", null, "fahrenheit", "0.0", "0 degrees Fahrenheit"}, 515 {"Test the locale with rg = UK and with usage", "en-US-u-rg-ukzzzz", "fahrenheit", "0", "default", "celsius", "-18", "-18 degrees Celsius"}, 516 {"Test the locale with rg = UKOI and with usage", "en-US-u-rg-ukoizzzz", "fahrenheit", "0", "default", "celsius", "-18" , "-18 degrees Celsius"}, 517 518 // Test the priorities 519 {"Test the locale with mu,ms,rg --> mu tag wins", "en-US-u-mu-celsius-ms-ussystem-rg-uszzzz", "celsius", "0", "default", "celsius", "0.0", "0 degrees Celsius"}, 520 {"Test the locale with ms,rg --> ms tag wins", "en-US-u-ms-metric-rg-uszzzz", "foot", "1", "default", "foot", "30.0", "30 centimeters"}, 521 }; 522 523 int testIndex = 0; 524 for (String[] test : tests) { 525 String message = test[0] + ", index = " + testIndex++; 526 ULocale locale = ULocale.forLanguageTag(test[1]); 527 MeasureUnit inputUnit = MeasureUnit.forIdentifier(test[2]); 528 double inputValue = Double.parseDouble(test[3]); 529 String usage = test[4]; 530 MeasureUnit expectedOutputUnit = MeasureUnit.forIdentifier(test[5]); 531 BigDecimal expectedOutputValue = new BigDecimal(test[6]); 532 String expectedFormattedMessage = test[7]; 533 534 LocalizedNumberFormatter nf = NumberFormatter.with().locale(locale).unit(inputUnit).unitWidth(UnitWidth.FULL_NAME); 535 if (usage != null) { 536 nf = nf.usage(usage); 537 } 538 539 FormattedNumber fn = nf.format(inputValue); 540 MeasureUnit actualOutputUnit = fn.getOutputUnit(); 541 BigDecimal actualOutputValue = fn.toBigDecimal(); 542 String actualFormattedMessage = fn.toString(); 543 544 assertEquals(message, expectedFormattedMessage, actualFormattedMessage); 545 // TODO: ICU-22154 546 // assertEquals(message, expectedOutputUnit, actualOutputUnit); 547 assertTrue(message, expectedOutputValue.subtract(actualOutputValue).abs().compareTo(BigDecimal.valueOf(0.0001)) <= 0); 548 } 549 } 550 551 @Test unitMeasure()552 public void unitMeasure() { 553 assertFormatDescending( 554 "Meters Short", 555 "measure-unit/length-meter", 556 "unit/meter", 557 NumberFormatter.with().unit(MeasureUnit.METER), 558 ULocale.ENGLISH, 559 "87,650 m", 560 "8,765 m", 561 "876.5 m", 562 "87.65 m", 563 "8.765 m", 564 "0.8765 m", 565 "0.08765 m", 566 "0.008765 m", 567 "0 m"); 568 569 assertFormatDescending( 570 "Meters Long", 571 "measure-unit/length-meter unit-width-full-name", 572 "unit/meter unit-width-full-name", 573 NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME), 574 ULocale.ENGLISH, 575 "87,650 meters", 576 "8,765 meters", 577 "876.5 meters", 578 "87.65 meters", 579 "8.765 meters", 580 "0.8765 meters", 581 "0.08765 meters", 582 "0.008765 meters", 583 "0 meters"); 584 585 assertFormatDescending( 586 "Compact Meters Long", 587 "compact-long measure-unit/length-meter unit-width-full-name", 588 "KK unit/meter unit-width-full-name", 589 NumberFormatter.with().notation(Notation.compactLong()).unit(MeasureUnit.METER) 590 .unitWidth(UnitWidth.FULL_NAME), 591 ULocale.ENGLISH, 592 "88 thousand meters", 593 "8.8 thousand meters", 594 "876 meters", 595 "88 meters", 596 "8.8 meters", 597 "0.88 meters", 598 "0.088 meters", 599 "0.0088 meters", 600 "0 meters"); 601 602 assertFormatDescending( 603 "Hectometers", 604 "unit/hectometer", 605 "unit/hectometer", 606 NumberFormatter.with().unit(MeasureUnit.forIdentifier("hectometer")), 607 ULocale.ENGLISH, 608 "87,650 hm", 609 "8,765 hm", 610 "876.5 hm", 611 "87.65 hm", 612 "8.765 hm", 613 "0.8765 hm", 614 "0.08765 hm", 615 "0.008765 hm", 616 "0 hm"); 617 618 assertFormatSingleMeasure( 619 "Meters with Measure Input", 620 "unit-width-full-name", 621 "unit-width-full-name", 622 NumberFormatter.with().unitWidth(UnitWidth.FULL_NAME), 623 ULocale.ENGLISH, 624 new Measure(5.43, MeasureUnit.METER), 625 "5.43 meters"); 626 627 assertFormatSingleMeasure( 628 "Measure format method takes precedence over fluent chain", 629 "measure-unit/length-meter", 630 "unit/meter", 631 NumberFormatter.with().unit(MeasureUnit.METER), 632 ULocale.ENGLISH, 633 new Measure(5.43, USD), 634 "$5.43"); 635 636 assertFormatSingle( 637 "Meters with Negative Sign", 638 "measure-unit/length-meter", 639 "unit/meter", 640 NumberFormatter.with().unit(MeasureUnit.METER), 641 ULocale.ENGLISH, 642 -9876543.21, 643 "-9,876,543.21 m"); 644 645 // The locale string "सान" appears only in brx.txt: 646 assertFormatSingle( 647 "Interesting Data Fallback 1", 648 "measure-unit/duration-day unit-width-full-name", 649 "unit/day unit-width-full-name", 650 NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.FULL_NAME), 651 ULocale.forLanguageTag("brx"), 652 5.43, 653 "5.43 सान"); 654 655 // Requires following the alias from unitsNarrow to unitsShort: 656 assertFormatSingle( 657 "Interesting Data Fallback 2", 658 "measure-unit/duration-day unit-width-narrow", 659 "unit/day unit-width-narrow", 660 NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.NARROW), 661 ULocale.forLanguageTag("brx"), 662 5.43, 663 "5.43 d"); 664 665 // en_001.txt has a unitsNarrow/area/square-meter table, but table does not contain the OTHER unit, 666 // requiring fallback to the root. 667 assertFormatSingle( 668 "Interesting Data Fallback 3", 669 "measure-unit/area-square-meter unit-width-narrow", 670 "unit/square-meter unit-width-narrow", 671 NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW), 672 ULocale.forLanguageTag("en-GB"), 673 5.43, 674 "5.43m²"); 675 676 // Try accessing a narrow unit directly from root. 677 assertFormatSingle( 678 "Interesting Data Fallback 4", 679 "measure-unit/area-square-meter unit-width-narrow", 680 "unit/square-meter unit-width-narrow", 681 NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW), 682 ULocale.forLanguageTag("root"), 683 5.43, 684 "5.43 m²"); 685 686 // es_US has "{0}°" for unitsNarrow/temperature/FAHRENHEIT. 687 // NOTE: This example is in the documentation. 688 assertFormatSingle( 689 "MeasureUnit Difference between Narrow and Short (Narrow Version)", 690 "measure-unit/temperature-fahrenheit unit-width-narrow", 691 "unit/fahrenheit unit-width-narrow", 692 NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.NARROW), 693 ULocale.forLanguageTag("es-US"), 694 5.43, 695 "5.43°"); 696 697 assertFormatSingle( 698 "MeasureUnit Difference between Narrow and Short (Short Version)", 699 "measure-unit/temperature-fahrenheit unit-width-short", 700 "unit/fahrenheit unit-width-short", 701 NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.SHORT), 702 ULocale.forLanguageTag("es-US"), 703 5.43, 704 "5.43 °F"); 705 706 assertFormatSingle( 707 "MeasureUnit form without {0} in CLDR pattern", 708 "measure-unit/temperature-kelvin unit-width-full-name", 709 "unit/kelvin unit-width-full-name", 710 NumberFormatter.with().unit(MeasureUnit.KELVIN).unitWidth(UnitWidth.FULL_NAME), 711 ULocale.forLanguageTag("es-MX"), 712 1, 713 "kelvin"); 714 715 assertFormatSingle( 716 "MeasureUnit form without {0} in CLDR pattern and wide base form", 717 "measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name", 718 "unit/kelvin .00000000000000000000 unit-width-full-name", 719 NumberFormatter.with() 720 .precision(Precision.fixedFraction(20)) 721 .unit(MeasureUnit.KELVIN) 722 .unitWidth(UnitWidth.FULL_NAME), 723 ULocale.forLanguageTag("es-MX"), 724 1, 725 "kelvin"); 726 727 assertFormatSingle( 728 "Person unit not in short form", 729 "measure-unit/duration-year-person unit-width-full-name", 730 "unit/year-person unit-width-full-name", 731 NumberFormatter.with() 732 .unit(MeasureUnit.YEAR_PERSON) 733 .unitWidth(UnitWidth.FULL_NAME), 734 ULocale.forLanguageTag("es-MX"), 735 5, 736 "5 a\u00F1os"); 737 738 assertFormatSingle( 739 "Hubble Constant", 740 "unit/kilometer-per-megaparsec-second", 741 "unit/kilometer-per-megaparsec-second", 742 NumberFormatter.with() 743 .unit(MeasureUnit.forIdentifier("kilometer-per-megaparsec-second")), 744 new ULocale("en"), 745 74, // Approximate 2019-03-18 measurement 746 "74 km/Mpc⋅sec"); 747 748 assertFormatSingle( 749 "Mixed unit", 750 "unit/yard-and-foot-and-inch", 751 "unit/yard-and-foot-and-inch", 752 NumberFormatter.with() 753 .unit(MeasureUnit.forIdentifier("yard-and-foot-and-inch")), 754 new ULocale("en-US"), 755 3.65, 756 "3 yd, 1 ft, 11.4 in"); 757 758 assertFormatSingle( 759 "Mixed unit, Scientific", 760 "unit/yard-and-foot-and-inch E0", 761 "unit/yard-and-foot-and-inch E0", 762 NumberFormatter.with() 763 .unit(MeasureUnit.forIdentifier("yard-and-foot-and-inch")) 764 .notation(Notation.scientific()), 765 new ULocale("en-US"), 766 3.65, 767 "3 yd, 1 ft, 1.14E1 in"); 768 769 assertFormatSingle( 770 "Mixed Unit (Narrow Version)", 771 "unit/tonne-and-kilogram-and-gram unit-width-narrow", 772 "unit/tonne-and-kilogram-and-gram unit-width-narrow", 773 NumberFormatter.with() 774 .unit(MeasureUnit.forIdentifier("tonne-and-kilogram-and-gram")) 775 .unitWidth(UnitWidth.NARROW), 776 new ULocale("en-US"), 777 4.28571, 778 "4t 285kg 710g"); 779 780 assertFormatSingle( 781 "Mixed Unit (Short Version)", 782 "unit/tonne-and-kilogram-and-gram unit-width-short", 783 "unit/tonne-and-kilogram-and-gram unit-width-short", 784 NumberFormatter.with() 785 .unit(MeasureUnit.forIdentifier("tonne-and-kilogram-and-gram")) 786 .unitWidth(UnitWidth.SHORT), 787 new ULocale("en-US"), 788 4.28571, 789 "4 t, 285 kg, 710 g"); 790 791 assertFormatSingle( 792 "Mixed Unit (Full Name Version)", 793 "unit/tonne-and-kilogram-and-gram unit-width-full-name", 794 "unit/tonne-and-kilogram-and-gram unit-width-full-name", 795 NumberFormatter.with() 796 .unit(MeasureUnit.forIdentifier("tonne-and-kilogram-and-gram")) 797 .unitWidth(UnitWidth.FULL_NAME), 798 new ULocale("en-US"), 799 4.28571, 800 "4 metric tons, 285 kilograms, 710 grams"); 801 802 assertFormatSingle( 803 "Mixed Unit (Not Sorted) [metric]", 804 "unit/gram-and-kilogram unit-width-full-name", 805 "unit/gram-and-kilogram unit-width-full-name", 806 NumberFormatter.with() 807 .unit(MeasureUnit.forIdentifier("gram-and-kilogram")) 808 .unitWidth(UnitWidth.FULL_NAME), 809 new ULocale("en-US"), 810 4.28571, 811 "285.71 grams, 4 kilograms"); 812 813 assertFormatSingle( 814 "Mixed Unit (Not Sorted) [imperial]", 815 "unit/inch-and-yard-and-foot unit-width-full-name", 816 "unit/inch-and-yard-and-foot unit-width-full-name", 817 NumberFormatter.with() 818 .unit(MeasureUnit.forIdentifier("inch-and-yard-and-foot")) 819 .unitWidth(UnitWidth.FULL_NAME), 820 new ULocale("en-US"), 821 4.28571, 822 "10.28556 inches, 4 yards, 0 feet"); 823 824 assertFormatSingle( 825 "Mixed Unit (Not Sorted) [imperial full]", 826 "unit/inch-and-yard-and-foot unit-width-full-name", 827 "unit/inch-and-yard-and-foot unit-width-full-name", 828 NumberFormatter.with() 829 .unit(MeasureUnit.forIdentifier("inch-and-yard-and-foot")) 830 .unitWidth(UnitWidth.FULL_NAME), 831 new ULocale("en-US"), 832 4.38571, 833 "1.88556 inches, 4 yards, 1 foot"); 834 835 assertFormatSingle( 836 "Mixed Unit (Not Sorted) [imperial full integers]", 837 "unit/inch-and-yard-and-foot @# unit-width-full-name", 838 "unit/inch-and-yard-and-foot @# unit-width-full-name", 839 NumberFormatter.with() 840 .unit(MeasureUnit.forIdentifier("inch-and-yard-and-foot")) 841 .unitWidth(UnitWidth.FULL_NAME) 842 .precision(Precision.maxSignificantDigits(2)), 843 new ULocale("en-US"), 844 4.36112, 845 "1 inch, 4 yards, 1 foot"); 846 847 assertFormatSingle( 848 "Mixed Unit (Not Sorted) [imperial full] with `And` in the end", 849 "unit/inch-and-yard-and-foot unit-width-full-name", 850 "unit/inch-and-yard-and-foot unit-width-full-name", 851 NumberFormatter.with() 852 .unit(MeasureUnit.forIdentifier("inch-and-yard-and-foot")) 853 .unitWidth(UnitWidth.FULL_NAME), 854 new ULocale("fr-FR"), 855 4.38571, 856 "1,88556\u00A0pouce, 4\u00A0yards et 1\u00A0pied"); 857 858 assertFormatSingle( 859 "Mixed unit, Scientific [Not in Order]", 860 "unit/foot-and-inch-and-yard E0", 861 "unit/foot-and-inch-and-yard E0", 862 NumberFormatter.with() 863 .unit(MeasureUnit.forIdentifier("foot-and-inch-and-yard")) 864 .notation(Notation.scientific()), 865 new ULocale("en-US"), 866 3.65, 867 "1 ft, 1.14E1 in, 3 yd"); 868 869 assertFormatSingle( 870 "Testing \"1 foot 12 inches\"", 871 "unit/foot-and-inch @### unit-width-full-name", 872 "unit/foot-and-inch @### unit-width-full-name", 873 NumberFormatter.with() 874 .unit(MeasureUnit.forIdentifier("foot-and-inch")) 875 .precision(Precision.maxSignificantDigits(4)) 876 .unitWidth(UnitWidth.FULL_NAME), 877 new ULocale("en-US"), 878 1.9999, 879 "2 feet, 0 inches"); 880 881 assertFormatSingle( 882 "Negative numbers: temperature", 883 "measure-unit/temperature-celsius", 884 "unit/celsius", 885 NumberFormatter.with().unit(MeasureUnit.forIdentifier("celsius")), 886 new ULocale("nl-NL"), 887 -6.5, 888 "-6,5°C"); 889 890 assertFormatSingle( 891 "Negative numbers: time", 892 "unit/hour-and-minute-and-second", 893 "unit/hour-and-minute-and-second", 894 NumberFormatter.with().unit(MeasureUnit.forIdentifier("hour-and-minute-and-second")), 895 new ULocale("de-DE"), 896 -1.24, 897 "-1 Std., 14 Min. und 24 Sek."); 898 899 assertFormatSingle( 900 "Zero out the unit field", 901 "", 902 "", 903 NumberFormatter.with().unit(MeasureUnit.KELVIN).unit(NoUnit.BASE), 904 new ULocale("en"), 905 100, 906 "100"); 907 908 // TODO: desired behaviour for this "pathological" case? 909 // Since this is pointless, we don't test that its behaviour doesn't change. 910 // As of January 2021, the produced result has a missing sign: 23.5 Kelvin 911 // is "23 Kelvin and -272.65 degrees Celsius": 912 // assertFormatSingle( 913 // "Meaningless: kelvin-and-celcius", 914 // "unit/kelvin-and-celsius", 915 // "unit/kelvin-and-celsius", 916 // NumberFormatter.with().unit(MeasureUnit.forIdentifier("kelvin-and-celsius")), 917 // new ULocale("en"), 918 // 23.5, 919 // "23 K, 272.65°C"); 920 921 assertFormatSingle( 922 "Measured -Inf", 923 "measure-unit/electric-ampere", 924 "unit/ampere", 925 NumberFormatter.with().unit(MeasureUnit.AMPERE), 926 new ULocale("en"), 927 Double.NEGATIVE_INFINITY, 928 "-∞ A"); 929 930 assertFormatSingle( 931 "Measured NaN", 932 "measure-unit/temperature-celsius", 933 "unit/celsius", 934 NumberFormatter.with().unit(MeasureUnit.forIdentifier("celsius")), 935 new ULocale("en"), 936 Double.NaN, 937 "NaN°C"); 938 } 939 940 @Test unitCompoundMeasure()941 public void unitCompoundMeasure() { 942 assertFormatDescending( 943 "Meters Per Second Short (unit that simplifies) and perUnit method", 944 "measure-unit/length-meter per-measure-unit/duration-second", 945 "unit/meter-per-second", 946 NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND), 947 ULocale.ENGLISH, 948 "87,650 m/s", 949 "8,765 m/s", 950 "876.5 m/s", 951 "87.65 m/s", 952 "8.765 m/s", 953 "0.8765 m/s", 954 "0.08765 m/s", 955 "0.008765 m/s", 956 "0 m/s"); 957 958 assertFormatDescending( 959 "Meters Per Second Short, built-in m/s", 960 "measure-unit/speed-meter-per-second", 961 "unit/meter-per-second", 962 NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND), 963 ULocale.ENGLISH, 964 "87,650 m/s", 965 "8,765 m/s", 966 "876.5 m/s", 967 "87.65 m/s", 968 "8.765 m/s", 969 "0.8765 m/s", 970 "0.08765 m/s", 971 "0.008765 m/s", 972 "0 m/s"); 973 974 assertFormatDescending( 975 "Pounds Per Square Mile Short (secondary unit has per-format)", 976 "measure-unit/mass-pound per-measure-unit/area-square-mile", 977 "unit/pound-per-square-mile", 978 NumberFormatter.with().unit(MeasureUnit.POUND).perUnit(MeasureUnit.SQUARE_MILE), 979 ULocale.ENGLISH, 980 "87,650 lb/mi²", 981 "8,765 lb/mi²", 982 "876.5 lb/mi²", 983 "87.65 lb/mi²", 984 "8.765 lb/mi²", 985 "0.8765 lb/mi²", 986 "0.08765 lb/mi²", 987 "0.008765 lb/mi²", 988 "0 lb/mi²"); 989 990 assertFormatDescending( 991 "Joules Per Furlong Short (unit with no simplifications or special patterns)", 992 "measure-unit/energy-joule per-measure-unit/length-furlong", 993 "unit/joule-per-furlong", 994 NumberFormatter.with().unit(MeasureUnit.JOULE).perUnit(MeasureUnit.FURLONG), 995 ULocale.ENGLISH, 996 "87,650 J/fur", 997 "8,765 J/fur", 998 "876.5 J/fur", 999 "87.65 J/fur", 1000 "8.765 J/fur", 1001 "0.8765 J/fur", 1002 "0.08765 J/fur", 1003 "0.008765 J/fur", 1004 "0 J/fur"); 1005 1006 assertFormatDescending( 1007 "Joules Per Furlong Short with unit identifier via API", 1008 "measure-unit/energy-joule per-measure-unit/length-furlong", 1009 "unit/joule-per-furlong", 1010 NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")), 1011 ULocale.ENGLISH, 1012 "87,650 J/fur", 1013 "8,765 J/fur", 1014 "876.5 J/fur", 1015 "87.65 J/fur", 1016 "8.765 J/fur", 1017 "0.8765 J/fur", 1018 "0.08765 J/fur", 1019 "0.008765 J/fur", 1020 "0 J/fur"); 1021 1022 assertFormatDescending( 1023 "Pounds per Square Inch: composed", 1024 "measure-unit/force-pound-force per-measure-unit/area-square-inch", 1025 "unit/pound-force-per-square-inch", 1026 NumberFormatter.with().unit(MeasureUnit.POUND_FORCE).perUnit(MeasureUnit.SQUARE_INCH), 1027 ULocale.ENGLISH, 1028 "87,650 psi", 1029 "8,765 psi", 1030 "876.5 psi", 1031 "87.65 psi", 1032 "8.765 psi", 1033 "0.8765 psi", 1034 "0.08765 psi", 1035 "0.008765 psi", 1036 "0 psi"); 1037 1038 assertFormatDescending( 1039 "Pounds per Square Inch: built-in", 1040 "measure-unit/force-pound-force per-measure-unit/area-square-inch", 1041 "unit/pound-force-per-square-inch", 1042 NumberFormatter.with().unit(MeasureUnit.POUND_PER_SQUARE_INCH), 1043 ULocale.ENGLISH, 1044 "87,650 psi", 1045 "8,765 psi", 1046 "876.5 psi", 1047 "87.65 psi", 1048 "8.765 psi", 1049 "0.8765 psi", 1050 "0.08765 psi", 1051 "0.008765 psi", 1052 "0 psi"); 1053 1054 assertFormatSingle( 1055 "m/s/s simplifies to m/s^2", 1056 "measure-unit/speed-meter-per-second per-measure-unit/duration-second", 1057 "unit/meter-per-square-second", 1058 NumberFormatter.with() 1059 .unit(MeasureUnit.METER_PER_SECOND) 1060 .perUnit(MeasureUnit.SECOND), 1061 new ULocale("en-GB"), 1062 2.4, 1063 "2.4 m/s\u00B2"); 1064 1065 assertFormatSingle( 1066 "Negative numbers: acceleration", 1067 "measure-unit/acceleration-meter-per-square-second", 1068 "unit/meter-per-second-second", 1069 NumberFormatter.with().unit(MeasureUnit.forIdentifier("meter-per-pow2-second")), 1070 new ULocale("af-ZA"), 1071 -9.81, 1072 "-9,81 m/s\u00B2"); 1073 1074 // Testing the rejection of invalid specifications 1075 1076 // If .unit() is not given a built-in type, .perUnit() is not allowed 1077 // (because .unit is now flexible enough to handle compound units, 1078 // .perUnit() is supported for backward compatibility). 1079 LocalizedNumberFormatter nf = NumberFormatter.with() 1080 .unit(MeasureUnit.forIdentifier("furlong-pascal")) 1081 .perUnit(MeasureUnit.METER) 1082 .locale(new ULocale("en-GB")); 1083 1084 try { 1085 nf.format(2.4d); 1086 fail("Expected failure for unit/furlong-pascal per-unit/length-meter, got: " + 1087 nf.format(2.4d) + "."); 1088 } catch (UnsupportedOperationException e) { 1089 // Pass 1090 } 1091 1092 // .perUnit() may only be passed a built-in type, or something that 1093 // combines to a built-in type together with .unit(). 1094 nf = NumberFormatter.with() 1095 .unit(MeasureUnit.FURLONG) 1096 .perUnit(MeasureUnit.forIdentifier("square-second")) 1097 .locale(new ULocale("en-GB")); 1098 try { 1099 nf.format(2.4d); 1100 fail("Expected failure, got: " + nf.format(2.4d) + "."); 1101 } catch (UnsupportedOperationException e) { 1102 // pass 1103 } 1104 // As above, "square-second" is not a built-in type, however this time, 1105 // meter-per-square-second is a built-in type. 1106 assertFormatSingle( 1107 "meter per square-second works as a composed unit", 1108 "measure-unit/speed-meter-per-second per-measure-unit/duration-second", 1109 "unit/meter-per-square-second", 1110 NumberFormatter.with() 1111 .unit(MeasureUnit.METER) 1112 .perUnit(MeasureUnit.forIdentifier("square-second")), 1113 new ULocale("en-GB"), 1114 2.4, 1115 "2.4 m/s\u00B2"); 1116 } 1117 1118 @Test unitArbitraryMeasureUnits()1119 public void unitArbitraryMeasureUnits() { 1120 // TODO: fix after data bug is resolved? See CLDR-14510. 1121 // assertFormatSingle( 1122 // "Binary unit prefix: kibibyte", 1123 // "unit/kibibyte", 1124 // "unit/kibibyte", 1125 // NumberFormatter.with().unit(MeasureUnit.forIdentifier("kibibyte")), 1126 // new ULocale("en-GB"), 1127 // 2.4, 1128 // "2.4 KiB"); 1129 1130 assertFormatSingle("Binary unit prefix: kibibyte full-name", 1131 "unit/kibibyte unit-width-full-name", "unit/kibibyte unit-width-full-name", 1132 NumberFormatter.with() 1133 .unit(MeasureUnit.forIdentifier("kibibyte")) 1134 .unitWidth(UnitWidth.FULL_NAME), 1135 new ULocale("en-GB"), 2.4, "2.4 kibibytes"); 1136 1137 assertFormatSingle("Binary unit prefix: kibibyte full-name", 1138 "unit/kibibyte unit-width-full-name", "unit/kibibyte unit-width-full-name", 1139 NumberFormatter.with() 1140 .unit(MeasureUnit.forIdentifier("kibibyte")) 1141 .unitWidth(UnitWidth.FULL_NAME), 1142 new ULocale("de"), 2.4, "2,4 Kibibyte"); 1143 1144 assertFormatSingle("Binary prefix for non-digital units: kibimeter", "unit/kibimeter", 1145 "unit/kibimeter", 1146 NumberFormatter.with().unit(MeasureUnit.forIdentifier("kibimeter")), 1147 new ULocale("en-GB"), 2.4, "2.4 Kim"); 1148 1149 assertFormatSingle( 1150 "Extra-large prefix: exabyte", 1151 "unit/exabyte", 1152 "unit/exabyte", 1153 NumberFormatter.with() 1154 .unit(MeasureUnit.forIdentifier("exabyte")), 1155 new ULocale("en-GB"), 1156 2.4, 1157 "2.4 Ebyte"); 1158 1159 assertFormatSingle( 1160 "Extra-large prefix: exabyte (full-name)", 1161 "unit/exabyte unit-width-full-name", 1162 "unit/exabyte unit-width-full-name", 1163 NumberFormatter.with() 1164 .unit(MeasureUnit.forIdentifier("exabyte")) 1165 .unitWidth(UnitWidth.FULL_NAME), 1166 new ULocale("en-GB"), 1167 2.4, 1168 "2.4 exabytes"); 1169 1170 assertFormatSingle("SI prefix falling back to root: microohm", "unit/microohm", "unit/microohm", 1171 NumberFormatter.with().unit(MeasureUnit.forIdentifier("microohm")), 1172 new ULocale("de-CH"), 2.4, "2.4 μΩ"); 1173 1174 assertFormatSingle("de-CH fallback to de: microohm unit-width-full-name", 1175 "unit/microohm unit-width-full-name", "unit/microohm unit-width-full-name", 1176 NumberFormatter.with() 1177 .unit(MeasureUnit.forIdentifier("microohm")) 1178 .unitWidth(UnitWidth.FULL_NAME), 1179 new ULocale("de-CH"), 2.4, "2.4\u00A0Mikroohm"); 1180 1181 assertFormatSingle("No prefixes, 'times' pattern: joule-furlong", "unit/joule-furlong", 1182 "unit/joule-furlong", 1183 NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-furlong")), 1184 new ULocale("en"), 2.4, "2.4 J⋅fur"); 1185 1186 assertFormatSingle("No numeratorUnitString: per-second", "unit/per-second", "unit/per-second", 1187 NumberFormatter.with().unit(MeasureUnit.forIdentifier("per-second")), 1188 new ULocale("de-CH"), 2.4, "2.4/s"); 1189 1190 assertFormatSingle("No numeratorUnitString: per-second unit-width-full-name", 1191 "unit/per-second unit-width-full-name", 1192 "unit/per-second unit-width-full-name", 1193 NumberFormatter.with() 1194 .unit(MeasureUnit.forIdentifier("per-second")) 1195 .unitWidth(UnitWidth.FULL_NAME), 1196 new ULocale("de-CH"), 2.4, "2.4 pro Sekunde"); 1197 1198 assertFormatSingle( 1199 "Prefix in the denominator: nanogram-per-picobarrel", "unit/nanogram-per-picobarrel", 1200 "unit/nanogram-per-picobarrel", 1201 NumberFormatter.with().unit(MeasureUnit.forIdentifier("nanogram-per-picobarrel")), 1202 new ULocale("en-ZA"), 2.4, "2,4 ng/pbbl"); 1203 1204 assertFormatSingle("Prefix in the denominator: nanogram-per-picobarrel unit-width-full-name", 1205 "unit/nanogram-per-picobarrel unit-width-full-name", 1206 "unit/nanogram-per-picobarrel unit-width-full-name", 1207 NumberFormatter.with() 1208 .unit(MeasureUnit.forIdentifier("nanogram-per-picobarrel")) 1209 .unitWidth(UnitWidth.FULL_NAME), 1210 new ULocale("en-ZA"), 2.4, "2,4 nanograms per picobarrel"); 1211 1212 // Valid MeasureUnit, but unformattable, because we only have patterns for 1213 // pow2 and pow3 at this time: 1214 LocalizedNumberFormatter lnf = NumberFormatter.with() 1215 .unit(MeasureUnit.forIdentifier("pow4-mile")) 1216 .unitWidth(UnitWidth.FULL_NAME) 1217 .locale(new ULocale("en-ZA")); 1218 try { 1219 lnf.format(1); 1220 fail("Expected failure for pow4-mile, got: " + lnf.format(1) + "."); 1221 } catch (UnsupportedOperationException e) { 1222 // pass 1223 } 1224 1225 assertFormatSingle( 1226 "kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", 1227 "unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", 1228 "unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", 1229 NumberFormatter.with() 1230 .unit(MeasureUnit.forIdentifier("kibijoule-foot-per-cubic-gigafurlong-square-second")) 1231 .unitWidth(UnitWidth.FULL_NAME), 1232 new ULocale("en-ZA"), 2.4, "2,4 kibijoule-feet per cubic gigafurlong-square second"); 1233 1234 assertFormatSingle( 1235 "kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", 1236 "unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", 1237 "unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name", 1238 NumberFormatter.with() 1239 .unit(MeasureUnit.forIdentifier("kibijoule-foot-per-cubic-gigafurlong-square-second")) 1240 .unitWidth(UnitWidth.FULL_NAME), 1241 new ULocale("de-CH"), 2.4, "2.4\u00A0Kibijoule⋅Fuss pro Kubikgigafurlong⋅Quadratsekunde"); 1242 1243 // TODO(ICU-21504): We want to be able to format this, but "100-kilometer" 1244 // is not yet supported when it's not part of liter-per-100-kilometer: 1245 // Actually now in CLDR 40 this is supported directly in data, so change test. 1246 assertFormatSingle( 1247 "kilowatt-hour-per-100-kilometer unit-width-full-name", 1248 "unit/kilowatt-hour-per-100-kilometer unit-width-full-name", 1249 "unit/kilowatt-hour-per-100-kilometer unit-width-full-name", 1250 NumberFormatter.with() 1251 .unit(MeasureUnit.forIdentifier("kilowatt-hour-per-100-kilometer")) 1252 .unitWidth(UnitWidth.FULL_NAME), 1253 new ULocale("en-ZA"), 2.4, "2,4 kilowatt-hours per 100 kilometres"); 1254 } 1255 1256 // TODO: merge these tests into NumberSkeletonTest.java instead of here: 1257 @Test unitSkeletons()1258 public void unitSkeletons() { 1259 Object[][] cases = { 1260 {"old-form built-in compound unit", // 1261 "measure-unit/speed-meter-per-second", // 1262 "unit/meter-per-second"}, 1263 1264 {"old-form compound construction, converts to built-in", // 1265 "measure-unit/length-meter per-measure-unit/duration-second", // 1266 "unit/meter-per-second"}, 1267 1268 {"old-form compound construction which does not simplify to a built-in", // 1269 "measure-unit/energy-joule per-measure-unit/length-meter", // 1270 "unit/joule-per-meter"}, 1271 1272 {"old-form compound-compound ugliness resolves neatly", // 1273 "measure-unit/speed-meter-per-second per-measure-unit/duration-second", // 1274 "unit/meter-per-square-second"}, 1275 1276 {"short-form built-in units stick with the built-in", // 1277 "unit/meter-per-second", // 1278 "unit/meter-per-second"}, 1279 1280 {"short-form compound units stay as is", // 1281 "unit/square-meter-per-square-meter", // 1282 "unit/square-meter-per-square-meter"}, 1283 1284 {"short-form compound units stay as is", // 1285 "unit/joule-per-furlong", // 1286 "unit/joule-per-furlong"}, 1287 1288 {"short-form that doesn't consist of built-in units", // 1289 "unit/hectometer-per-second", // 1290 "unit/hectometer-per-second"}, 1291 1292 {"short-form that doesn't consist of built-in units", // 1293 "unit/meter-per-hectosecond", // 1294 "unit/meter-per-hectosecond"}, 1295 1296 {"percent compound skeletons handled correctly", // 1297 "unit/percent-per-meter", // 1298 "unit/percent-per-meter"}, 1299 1300 {"permille compound skeletons handled correctly", // 1301 "measure-unit/concentr-permille per-measure-unit/length-meter", // 1302 "unit/permille-per-meter"}, 1303 1304 {"percent simple unit is not actually considered a unit", // 1305 "unit/percent", // 1306 "percent"}, 1307 1308 {"permille simple unit is not actually considered a unit", // 1309 "measure-unit/concentr-permille", // 1310 "permille"}, 1311 1312 {"Round-trip example from icu-units#35", // 1313 "unit/kibijoule-per-furlong", // 1314 "unit/kibijoule-per-furlong"}, 1315 }; 1316 for (Object[] cas : cases) { 1317 String msg = (String)cas[0]; 1318 String inputSkeleton = (String)cas[1]; 1319 String normalizedSkeleton = (String)cas[2]; 1320 UnlocalizedNumberFormatter nf = NumberFormatter.forSkeleton(inputSkeleton); 1321 assertEquals(msg, normalizedSkeleton, nf.toSkeleton()); 1322 } 1323 1324 Object[][] failCases = { 1325 {"Parsing measure-unit/* results in failure if not built-in unit", 1326 "measure-unit/hectometer", // 1327 true, // 1328 false}, 1329 1330 {"Parsing per-measure-unit/* results in failure if not built-in unit", 1331 "measure-unit/meter per-measure-unit/hectosecond", // 1332 true, // 1333 false}, 1334 1335 {"\"currency/EUR measure-unit/length-meter\" fails, conflicting skeleton.", 1336 "currency/EUR measure-unit/length-meter", // 1337 true, // 1338 false}, 1339 1340 {"\"measure-unit/length-meter currency/EUR\" fails, conflicting skeleton.", 1341 "measure-unit/length-meter currency/EUR", // 1342 true, // 1343 false}, 1344 1345 {"\"currency/EUR per-measure-unit/meter\" fails, conflicting skeleton.", 1346 "currency/EUR per-measure-unit/length-meter", // 1347 true, // 1348 false}, 1349 }; 1350 for (Object[] cas : failCases) { 1351 String msg = (String)cas[0]; 1352 String inputSkeleton = (String)cas[1]; 1353 boolean forSkeletonExpectFailure = (boolean)cas[2]; 1354 boolean toSkeletonExpectFailure = (boolean)cas[3]; 1355 UnlocalizedNumberFormatter nf = null; 1356 try { 1357 nf = NumberFormatter.forSkeleton(inputSkeleton); 1358 if (forSkeletonExpectFailure) { 1359 fail("forSkeleton() should have failed: " + msg); 1360 } 1361 } catch (Exception e) { 1362 if (!forSkeletonExpectFailure) { 1363 fail("forSkeleton() should not have failed: " + msg); 1364 } 1365 continue; 1366 } 1367 try { 1368 nf.toSkeleton(); 1369 if (toSkeletonExpectFailure) { 1370 fail("toSkeleton() should have failed: " + msg); 1371 } 1372 } catch (Exception e) { 1373 if (!toSkeletonExpectFailure) { 1374 fail("toSkeleton() should not have failed: " + msg); 1375 } 1376 } 1377 } 1378 1379 assertEquals( // 1380 ".unit(METER_PER_SECOND) normalization", // 1381 "unit/meter-per-second", // 1382 NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND).toSkeleton()); 1383 assertEquals( // 1384 ".unit(METER).perUnit(SECOND) normalization", // 1385 "unit/meter-per-second", 1386 NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND).toSkeleton()); 1387 assertEquals( // 1388 ".unit(MeasureUnit.forIdentifier(\"hectometer\")) normalization", // 1389 "unit/hectometer", 1390 NumberFormatter.with().unit(MeasureUnit.forIdentifier("hectometer")).toSkeleton()); 1391 assertEquals( // 1392 ".unit(MeasureUnit.forIdentifier(\"hectometer\")) normalization", // 1393 "unit/meter-per-hectosecond", 1394 NumberFormatter.with() 1395 .unit(MeasureUnit.METER) 1396 .perUnit(MeasureUnit.forIdentifier("hectosecond")) 1397 .toSkeleton()); 1398 1399 assertEquals( // 1400 ".unit(CURRENCY) produces a currency/CURRENCY skeleton", // 1401 "currency/GBP", // 1402 NumberFormatter.with().unit(GBP).toSkeleton()); 1403 1404 // .unit(CURRENCY).perUnit(ANYTHING) is not supported. 1405 try { 1406 NumberFormatter.with().unit(GBP).perUnit(MeasureUnit.METER).toSkeleton(); 1407 fail("should give an error, unit(currency) with perUnit() is invalid."); 1408 } catch (UnsupportedOperationException e) { 1409 // Pass 1410 } 1411 } 1412 1413 @Test unitUsage()1414 public void unitUsage() { 1415 UnlocalizedNumberFormatter unloc_formatter; 1416 LocalizedNumberFormatter formatter; 1417 FormattedNumber formattedNum; 1418 String uTestCase; 1419 1420 try { 1421 NumberFormatter.with().usage("road").locale(ULocale.ENGLISH).format(1); 1422 fail("should give an error, usage() without unit() is invalid"); 1423 } catch (IllegalIcuArgumentException e) { 1424 // Pass 1425 } 1426 1427 unloc_formatter = NumberFormatter.with().usage("road").unit(MeasureUnit.METER); 1428 1429 uTestCase = "unitUsage() en-ZA road"; 1430 formatter = unloc_formatter.locale(new ULocale("en-ZA")); 1431 formattedNum = formatter.format(321d); 1432 1433 1434 assertTrue( 1435 uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"", 1436 MeasureUnit.METER.equals(formattedNum.getOutputUnit())); 1437 assertEquals(uTestCase, "300 m", formattedNum.toString()); 1438 { 1439 final Object[][] expectedFieldPositions = { 1440 {NumberFormat.Field.INTEGER, 0, 3}, 1441 {NumberFormat.Field.MEASURE_UNIT, 4, 5} 1442 }; 1443 1444 assertNumberFieldPositions( 1445 uTestCase + " field positions", 1446 formattedNum, 1447 expectedFieldPositions); 1448 } 1449 1450 assertFormatDescendingBig( 1451 uTestCase, 1452 "measure-unit/length-meter usage/road", 1453 "unit/meter usage/road", 1454 unloc_formatter, 1455 new ULocale("en-ZA"), 1456 "87\u00A0650 km", 1457 "8\u00A0765 km", 1458 "876 km", // 6.5 rounds down, 7.5 rounds up. 1459 "88 km", 1460 "8,8 km", 1461 "900 m", 1462 "90 m", 1463 "9 m", 1464 "0 m"); 1465 1466 uTestCase = "unitUsage() en-GB road"; 1467 formatter = unloc_formatter.locale(new ULocale("en-GB")); 1468 formattedNum = formatter.format(321d); 1469 assertTrue( 1470 uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"", 1471 MeasureUnit.YARD.equals(formattedNum.getOutputUnit())); 1472 assertEquals(uTestCase, "350 yd", formattedNum.toString()); 1473 { 1474 final Object[][] expectedFieldPositions = { 1475 {NumberFormat.Field.INTEGER, 0, 3}, 1476 {NumberFormat.Field.MEASURE_UNIT, 4, 6}}; 1477 assertNumberFieldPositions( 1478 (uTestCase + " field positions"), 1479 formattedNum, 1480 expectedFieldPositions); 1481 } 1482 1483 assertFormatDescendingBig( 1484 uTestCase, 1485 "measure-unit/length-meter usage/road", 1486 "unit/meter usage/road", 1487 unloc_formatter, 1488 new ULocale("en-GB"), 1489 "54,463 mi", 1490 "5,446 mi", 1491 "545 mi", 1492 "54 mi", 1493 "5.4 mi", 1494 "0.54 mi", 1495 "100 yd", 1496 "10 yd", 1497 "0 yd"); 1498 1499 uTestCase = "unitUsage() en-US road"; 1500 formatter = unloc_formatter.locale(new ULocale("en-US")); 1501 formattedNum = formatter.format(321d); 1502 assertTrue( 1503 uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"", 1504 MeasureUnit.FOOT == formattedNum.getOutputUnit()); 1505 assertEquals(uTestCase, "1,050 ft", formattedNum.toString()); 1506 { 1507 final Object[][] expectedFieldPositions = { 1508 {NumberFormat.Field.GROUPING_SEPARATOR, 1, 2}, 1509 {NumberFormat.Field.INTEGER, 0, 5}, 1510 {NumberFormat.Field.MEASURE_UNIT, 6, 8}}; 1511 assertNumberFieldPositions( 1512 uTestCase + " field positions", 1513 formattedNum, 1514 expectedFieldPositions); 1515 } 1516 assertFormatDescendingBig( 1517 uTestCase, 1518 "measure-unit/length-meter usage/road", 1519 "unit/meter usage/road", 1520 unloc_formatter, 1521 new ULocale("en-US"), 1522 "54,463 mi", 1523 "5,446 mi", 1524 "545 mi", 1525 "54 mi", 1526 "5.4 mi", 1527 "0.54 mi", 1528 "300 ft", 1529 "30 ft", 1530 "0 ft"); 1531 1532 unloc_formatter = NumberFormatter.with().usage("person").unit(MeasureUnit.KILOGRAM); 1533 uTestCase = "unitUsage() en-GB person"; 1534 formatter = unloc_formatter.locale(new ULocale("en-GB")); 1535 formattedNum = formatter.format(80d); 1536 assertTrue( 1537 uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"", 1538 MeasureUnit.forIdentifier("stone-and-pound").equals(formattedNum.getOutputUnit())); 1539 assertEquals(uTestCase, "12 st, 8.4 lb", formattedNum.toString()); 1540 { 1541 final Object[][] expectedFieldPositions = { 1542 // // Desired output: TODO(icu-units#67) 1543 // {NumberFormat.Field.INTEGER, 0, 2}, 1544 // {NumberFormat.Field.MEASURE_UNIT, 3, 5}, 1545 // {NumberFormat.ULISTFMT_LITERAL_FIELD, 5, 6}, 1546 // {NumberFormat.Field.INTEGER, 7, 8}, 1547 // {NumberFormat.DECIMAL_SEPARATOR_FIELD, 8, 9}, 1548 // {NumberFormat.FRACTION_FIELD, 9, 10}, 1549 // {NumberFormat.Field.MEASURE_UNIT, 11, 13}}; 1550 1551 // Current output: rather no fields than wrong fields 1552 {NumberFormat.Field.INTEGER, 7, 8}, 1553 {NumberFormat.Field.DECIMAL_SEPARATOR, 8, 9}, 1554 {NumberFormat.Field.FRACTION, 9, 10}, 1555 }; 1556 1557 assertNumberFieldPositions( 1558 uTestCase + " field positions", 1559 formattedNum, 1560 expectedFieldPositions); 1561 } 1562 assertFormatDescending( 1563 uTestCase, 1564 "measure-unit/mass-kilogram usage/person", 1565 "unit/kilogram usage/person", 1566 unloc_formatter, 1567 new ULocale("en-GB"), 1568 "13,802 st, 7.2 lb", 1569 "1,380 st, 3.5 lb", 1570 "138 st, 0.35 lb", 1571 "13 st, 11 lb", 1572 "1 st, 5.3 lb", 1573 "1 lb, 15 oz", 1574 "0 lb, 3.1 oz", 1575 "0 lb, 0.31 oz", 1576 "0 lb, 0 oz"); 1577 1578 assertFormatDescending( 1579 uTestCase, 1580 "usage/person unit-width-narrow measure-unit/mass-kilogram", 1581 "usage/person unit-width-narrow unit/kilogram", 1582 unloc_formatter.unitWidth(UnitWidth.NARROW), 1583 new ULocale("en-GB"), 1584 "13,802st 7.2lb", 1585 "1,380st 3.5lb", 1586 "138st 0.35lb", 1587 "13st 11lb", 1588 "1st 5.3lb", 1589 "1lb 15oz", 1590 "0lb 3.1oz", 1591 "0lb 0.31oz", 1592 "0lb 0oz"); 1593 1594 assertFormatDescending( 1595 uTestCase, 1596 "usage/person unit-width-short measure-unit/mass-kilogram", 1597 "usage/person unit-width-short unit/kilogram", 1598 unloc_formatter.unitWidth(UnitWidth.SHORT), 1599 new ULocale("en-GB"), 1600 "13,802 st, 7.2 lb", 1601 "1,380 st, 3.5 lb", 1602 "138 st, 0.35 lb", 1603 "13 st, 11 lb", 1604 "1 st, 5.3 lb", 1605 "1 lb, 15 oz", 1606 "0 lb, 3.1 oz", 1607 "0 lb, 0.31 oz", 1608 "0 lb, 0 oz"); 1609 1610 assertFormatDescending( 1611 uTestCase, 1612 "usage/person unit-width-full-name measure-unit/mass-kilogram", 1613 "usage/person unit-width-full-name unit/kilogram", 1614 unloc_formatter.unitWidth(UnitWidth.FULL_NAME), 1615 new ULocale("en-GB"), 1616 "13,802 stone, 7.2 pounds", 1617 "1,380 stone, 3.5 pounds", 1618 "138 stone, 0.35 pounds", 1619 "13 stone, 11 pounds", 1620 "1 stone, 5.3 pounds", 1621 "1 pound, 15 ounces", 1622 "0 pounds, 3.1 ounces", 1623 "0 pounds, 0.31 ounces", 1624 "0 pounds, 0 ounces"); 1625 1626 assertFormatDescendingBig( 1627 "Scientific notation with Usage: possible when using a reasonable Precision", 1628 "scientific @### usage/default measure-unit/area-square-meter unit-width-full-name", 1629 "scientific @### usage/default unit/square-meter unit-width-full-name", 1630 NumberFormatter.with() 1631 .unit(MeasureUnit.SQUARE_METER) 1632 .usage("default") 1633 .notation(Notation.scientific()) 1634 .precision(Precision.minMaxSignificantDigits(1, 4)) 1635 .unitWidth(UnitWidth.FULL_NAME), 1636 new ULocale("en-ZA"), 1637 "8,765E1 square kilometres", 1638 "8,765E0 square kilometres", 1639 "8,765E1 hectares", 1640 "8,765E0 hectares", 1641 "8,765E3 square metres", 1642 "8,765E2 square metres", 1643 "8,765E1 square metres", 1644 "8,765E0 square metres", 1645 "0E0 square centimetres"); 1646 1647 // TODO(icu-units#132): Java BigDecimal does not support Inf and NaN, so 1648 // we get a misleading "0" out of this: 1649 assertFormatSingle( 1650 "Negative Infinity with Unit Preferences", 1651 "measure-unit/area-acre usage/default", 1652 "unit/acre usage/default", 1653 NumberFormatter.with().unit(MeasureUnit.ACRE).usage("default"), 1654 ULocale.ENGLISH, 1655 Double.NEGATIVE_INFINITY, 1656 // "-∞ km²"); 1657 "0 cm²"); 1658 1659 // TODO(icu-units#132): Java BigDecimal does not support Inf and NaN, so 1660 // we get a misleading "0" out of this: 1661 assertFormatSingle( 1662 "NaN with Unit Preferences", 1663 "measure-unit/area-acre usage/default", 1664 "unit/acre usage/default", 1665 NumberFormatter.with().unit(MeasureUnit.ACRE).usage("default"), 1666 ULocale.ENGLISH, 1667 Double.NaN, 1668 // "NaN cm²"); 1669 "0 cm²"); 1670 1671 assertFormatSingle( 1672 "Negative numbers: minute-and-second", 1673 "measure-unit/duration-second usage/media", 1674 "unit/second usage/media", 1675 NumberFormatter.with().unit(MeasureUnit.SECOND).usage("media"), 1676 new ULocale("nl-NL"), 1677 -77.7, 1678 "-1 min, 18 sec"); 1679 1680 assertFormatSingle( 1681 "Negative numbers: media seconds", 1682 "measure-unit/duration-second usage/media", 1683 "unit/second usage/media", 1684 NumberFormatter.with().unit(MeasureUnit.SECOND).usage("media"), 1685 new ULocale("nl-NL"), 1686 -2.7, 1687 "-2,7 sec"); 1688 1689 // TODO(icu-units#132): Java BigDecimal does not support Inf and NaN, so 1690 // we get a misleading "0" out of this: 1691 assertFormatSingle( 1692 "NaN minute-and-second", 1693 "measure-unit/duration-second usage/media", 1694 "unit/second usage/media", 1695 NumberFormatter.with().unit(MeasureUnit.SECOND).usage("media"), 1696 new ULocale("nl-NL"), 1697 Double.NaN, 1698 // "NaN sec"); 1699 "0 sec"); 1700 1701 // TODO(icu-units#132): Java BigDecimal does not support Inf and NaN, so 1702 // we get a misleading "0" out of this: 1703 assertFormatSingle( 1704 "NaN meter-and-centimeter", 1705 "measure-unit/length-meter usage/person-height", 1706 "unit/meter usage/person-height", 1707 NumberFormatter.with().unit(MeasureUnit.METER).usage("person-height"), 1708 new ULocale("sv-SE"), 1709 Double.NaN, 1710 // "0 m, NaN cm"); 1711 "0 m, 0 cm"); 1712 1713 assertFormatSingle( 1714 "Rounding Mode propagates: rounding down", 1715 "usage/road measure-unit/length-centimeter rounding-mode-floor", 1716 "usage/road unit/centimeter rounding-mode-floor", 1717 NumberFormatter.with() 1718 .unit(MeasureUnit.forIdentifier("centimeter")) 1719 .usage("road") 1720 .roundingMode(RoundingMode.FLOOR), 1721 new ULocale("en-ZA"), 1722 34500, 1723 "300 m"); 1724 1725 assertFormatSingle( 1726 "Rounding Mode propagates: rounding up", 1727 "usage/road measure-unit/length-centimeter rounding-mode-ceiling", 1728 "usage/road unit/centimeter rounding-mode-ceiling", 1729 NumberFormatter.with() 1730 .unit(MeasureUnit.forIdentifier("centimeter")) 1731 .usage("road") 1732 .roundingMode(RoundingMode.CEILING), 1733 new ULocale("en-ZA"), 1734 30500, 1735 "350 m"); 1736 1737 assertFormatSingle("Fuel consumption: inverted units", 1738 "unit/liter-per-100-kilometer usage/vehicle-fuel", 1739 "unit/liter-per-100-kilometer usage/vehicle-fuel", 1740 NumberFormatter.with() 1741 .unit(MeasureUnit.forIdentifier("liter-per-100-kilometer")) 1742 .usage("vehicle-fuel"), 1743 new ULocale("en-US"), // 1744 6.6, // 1745 "36 mpg"); 1746 1747 // // TODO(ICU-21988): determine desired behaviour. Commented out for now 1748 // // to not enforce undesirable behaviour 1749 // assertFormatSingle("Fuel consumption: inverted units, divide-by-zero, en-US", 1750 // "unit/liter-per-100-kilometer usage/vehicle-fuel", 1751 // "unit/liter-per-100-kilometer usage/vehicle-fuel", 1752 // NumberFormatter.with() 1753 // .unit(MeasureUnit.forIdentifier("liter-per-100-kilometer")) 1754 // .usage("vehicle-fuel"), 1755 // new ULocale("en-US"), // 1756 // 0, // 1757 // "0 mpg"); 1758 1759 // // TODO(ICU-21988): determine desired behaviour. Commented out for now 1760 // // to not enforce undesirable behaviour 1761 // assertFormatSingle("Fuel consumption: inverted units, divide-by-zero, en-ZA", 1762 // "unit/mile-per-gallon usage/vehicle-fuel", 1763 // "unit/mile-per-gallon usage/vehicle-fuel", 1764 // NumberFormatter.with() 1765 // .unit(MeasureUnit.forIdentifier("mile-per-gallon")) 1766 // .usage("vehicle-fuel"), 1767 // new ULocale("en-ZA"), // 1768 // 0, // 1769 // "0 mpg"); 1770 1771 // // TODO(ICU-21988): Once we support Inf as input: 1772 // assertFormatSingle("Fuel consumption: inverted units, divide-by-inf", 1773 // "unit/mile-per-gallon usage/vehicle-fuel", 1774 // "unit/mile-per-gallon usage/vehicle-fuel", 1775 // NumberFormatter.with() 1776 // .unit(MeasureUnit.forIdentifier("mile-per-gallon")) 1777 // .usage("vehicle-fuel"), 1778 // new ULocale("de-CH"), // 1779 // INFINITY_GOES_HERE, // 1780 // "0 mpg"); 1781 1782 // Test calling .usage("") or .usage(null) should unset the existing usage. 1783 // First: without usage 1784 assertFormatSingle("Rounding Mode propagates: rounding up", 1785 "measure-unit/length-centimeter rounding-mode-ceiling", 1786 "unit/centimeter rounding-mode-ceiling", 1787 NumberFormatter.with() 1788 .unit(MeasureUnit.forIdentifier("centimeter")) 1789 .roundingMode(RoundingMode.CEILING), 1790 new ULocale("en-US"), 1791 3048, 1792 "3,048 cm"); 1793 1794 // Second: with "road" usage 1795 assertFormatSingle("Rounding Mode propagates: rounding up", 1796 "usage/road measure-unit/length-centimeter rounding-mode-ceiling", 1797 "usage/road unit/centimeter rounding-mode-ceiling", 1798 NumberFormatter.with() 1799 .unit(MeasureUnit.forIdentifier("centimeter")) 1800 .usage("road") 1801 .roundingMode(RoundingMode.CEILING), 1802 new ULocale("en-US"), 1803 3048, 1804 "100 ft"); 1805 1806 // Third: with "road" usage, then the usage unsetted by calling .usage("") 1807 assertFormatSingle("Rounding Mode propagates: rounding up", 1808 "measure-unit/length-centimeter rounding-mode-ceiling", 1809 "unit/centimeter rounding-mode-ceiling", 1810 NumberFormatter.with() 1811 .unit(MeasureUnit.forIdentifier("centimeter")) 1812 .usage("road") 1813 .roundingMode(RoundingMode.CEILING) 1814 .usage(""), // unset 1815 new ULocale("en-US"), 1816 3048, 1817 "3,048 cm"); 1818 1819 // Fourth: with "road" usage, then the usage unsetted by calling .usage(nul) 1820 assertFormatSingle("Rounding Mode propagates: rounding up", 1821 "measure-unit/length-centimeter rounding-mode-ceiling", 1822 "unit/centimeter rounding-mode-ceiling", 1823 NumberFormatter.with() 1824 .unit(MeasureUnit.forIdentifier("centimeter")) 1825 .usage("road") 1826 .roundingMode(RoundingMode.CEILING) 1827 .usage(null), // unset 1828 new ULocale("en-US"), 1829 3048, 1830 "3,048 cm"); 1831 1832 assertFormatSingle("kilometer-per-liter match the correct category", 1833 "unit/kilometer-per-liter usage/default", 1834 "unit/kilometer-per-liter usage/default", 1835 NumberFormatter.with() 1836 .unit(MeasureUnit.forIdentifier("kilometer-per-liter")) 1837 .usage("default"), 1838 new ULocale("en-US"), 1839 1, 1840 "100 L/100 km"); 1841 1842 assertFormatSingle("gallon-per-mile match the correct category", 1843 "unit/gallon-per-mile usage/default", 1844 "unit/gallon-per-mile usage/default", 1845 NumberFormatter.with() 1846 .unit(MeasureUnit.forIdentifier("gallon-per-mile")) 1847 .usage("default"), 1848 new ULocale("en-US"), 1849 1, 1850 "235 L/100 km"); 1851 1852 assertFormatSingle("psi match the correct category", 1853 "unit/megapascal usage/default", 1854 "unit/megapascal usage/default", 1855 NumberFormatter.with() 1856 .unit(MeasureUnit.forIdentifier("megapascal")) 1857 .usage("default"), 1858 new ULocale("en-US"), 1859 1, 1860 "145 psi"); 1861 1862 assertFormatSingle("millibar match the correct category", 1863 "unit/millibar usage/default", 1864 "unit/millibar usage/default", 1865 NumberFormatter.with() 1866 .unit(MeasureUnit.forIdentifier("millibar")) 1867 .usage("default"), 1868 new ULocale("en-US"), 1869 1, 1870 "0.015 psi"); 1871 1872 assertFormatSingle("pound-force-per-square-inch match the correct category", 1873 "unit/pound-force-per-square-inch usage/default", 1874 "unit/pound-force-per-square-inch usage/default", 1875 NumberFormatter.with() 1876 .unit(MeasureUnit.forIdentifier("pound-force-per-square-inch")) 1877 .usage("default"), 1878 new ULocale("en-US"), 1879 1, 1880 "1 psi"); 1881 1882 assertFormatSingle("inch-ofhg match the correct category", 1883 "unit/inch-ofhg usage/default", 1884 "unit/inch-ofhg usage/default", 1885 NumberFormatter.with() 1886 .unit(MeasureUnit.forIdentifier("inch-ofhg")) 1887 .usage("default"), 1888 new ULocale("en-US"), 1889 1, 1890 "0.49 psi"); 1891 1892 assertFormatSingle("millimeter-ofhg match the correct category", 1893 "unit/millimeter-ofhg usage/default", 1894 "unit/millimeter-ofhg usage/default", 1895 NumberFormatter.with() 1896 .unit(MeasureUnit.forIdentifier("millimeter-ofhg")) 1897 .usage("default"), 1898 new ULocale("en-US"), 1899 1, 1900 "0.019 psi"); 1901 1902 // ICU-22105 1903 assertFormatSingle("negative temperature conversion", 1904 "measure-unit/temperature-celsius unit-width-short usage/default", 1905 "measure-unit/temperature-celsius unit-width-short usage/default", 1906 NumberFormatter.with() 1907 .unit(MeasureUnit.forIdentifier("celsius")) 1908 .usage("default") 1909 .unitWidth(UnitWidth.SHORT), 1910 new ULocale("en-US"), 1911 -1, 1912 "30°F"); 1913 } 1914 1915 @Test unitUsageErrorCodes()1916 public void unitUsageErrorCodes() { 1917 UnlocalizedNumberFormatter unloc_formatter; 1918 1919 try { 1920 NumberFormatter.forSkeleton("unit/foobar"); 1921 fail("should give an error, because foobar is an invalid unit"); 1922 } catch (SkeletonSyntaxException e) { 1923 // Pass 1924 } 1925 1926 unloc_formatter = NumberFormatter.forSkeleton("usage/foobar"); 1927 // This does not give an error, because usage is not looked up yet. 1928 //status.errIfFailureAndReset("Expected behaviour: no immediate error for invalid usage"); 1929 1930 try { 1931 // Lacking a unit results in a failure. The skeleton is "incomplete", but we 1932 // support adding the unit via the fluent API, so it is not an error until 1933 // we build the formatting pipeline itself. 1934 unloc_formatter.locale(new ULocale("en-GB")).format(1); 1935 fail("should throw IllegalArgumentException"); 1936 } catch (IllegalArgumentException e) { 1937 // Pass 1938 } 1939 1940 // Adding the unit as part of the fluent chain leads to success. 1941 unloc_formatter.unit(MeasureUnit.METER).locale(new ULocale("en-GB")).format(1); /* No Exception should be thrown */ 1942 1943 // Setting unit to the "base dimensionless unit" is like clearing unit. 1944 unloc_formatter = NumberFormatter.with().unit(NoUnit.BASE).usage("default"); 1945 try { 1946 unloc_formatter.locale(new ULocale("en-GB")).format(1); 1947 fail("should throw IllegalArgumentException"); 1948 } catch (IllegalArgumentException e) { 1949 // Pass 1950 } 1951 } 1952 1953 1954 // Tests for the "skeletons" field in unitPreferenceData, as well as precision 1955 // and notation overrides. 1956 @Test unitUsageSkeletons()1957 public void unitUsageSkeletons() { 1958 assertFormatSingle( 1959 "Default >300m road preference skeletons round to 50m", 1960 "usage/road measure-unit/length-meter", 1961 "usage/road unit/meter", 1962 NumberFormatter.with().unit(MeasureUnit.METER).usage("road"), 1963 new ULocale("en-ZA"), 1964 321, 1965 "300 m"); 1966 1967 assertFormatSingle( 1968 "Precision can be overridden: override takes precedence", 1969 "usage/road measure-unit/length-meter @#", 1970 "usage/road unit/meter @#", 1971 NumberFormatter.with() 1972 .unit(MeasureUnit.METER) 1973 .usage("road") 1974 .precision(Precision.maxSignificantDigits(2)), 1975 new ULocale("en-ZA"), 1976 321, 1977 "320 m"); 1978 1979 assertFormatSingle( 1980 "Compact notation with Usage: bizarre, but possible (short)", 1981 "compact-short usage/road measure-unit/length-meter", 1982 "compact-short usage/road unit/meter", 1983 NumberFormatter.with() 1984 .unit(MeasureUnit.METER) 1985 .usage("road") 1986 .notation(Notation.compactShort()), 1987 new ULocale("en-ZA"), 1988 987654321L, 1989 "988K km"); 1990 1991 assertFormatSingle( 1992 "Compact notation with Usage: bizarre, but possible (short, precision override)", 1993 "compact-short usage/road measure-unit/length-meter @#", 1994 "compact-short usage/road unit/meter @#", 1995 NumberFormatter.with() 1996 .unit(MeasureUnit.METER) 1997 .usage("road") 1998 .notation(Notation.compactShort()) 1999 .precision(Precision.maxSignificantDigits(2)), 2000 new ULocale("en-ZA"), 2001 987654321L, 2002 "990K km"); 2003 2004 assertFormatSingle( 2005 "Compact notation with Usage: unusual but possible (long)", 2006 "compact-long usage/road measure-unit/length-meter @#", 2007 "compact-long usage/road unit/meter @#", 2008 NumberFormatter.with() 2009 .unit(MeasureUnit.METER) 2010 .usage("road") 2011 .notation(Notation.compactLong()) 2012 .precision(Precision.maxSignificantDigits(2)), 2013 new ULocale("en-ZA"), 2014 987654321, 2015 "990 thousand km"); 2016 2017 assertFormatSingle( 2018 "Compact notation with Usage: unusual but possible (long, precision override)", 2019 "compact-long usage/road measure-unit/length-meter @#", 2020 "compact-long usage/road unit/meter @#", 2021 NumberFormatter.with() 2022 .unit(MeasureUnit.METER) 2023 .usage("road") 2024 .notation(Notation.compactLong()) 2025 .precision(Precision.maxSignificantDigits(2)), 2026 new ULocale("en-ZA"), 2027 987654321, 2028 "990 thousand km"); 2029 2030 assertFormatSingle( 2031 "Scientific notation, not recommended, requires precision override for road", 2032 "scientific usage/road measure-unit/length-meter", 2033 "scientific usage/road unit/meter", 2034 NumberFormatter.with() 2035 .unit(MeasureUnit.METER) 2036 .usage("road") 2037 .notation(Notation.scientific()), 2038 new ULocale("en-ZA"), 2039 321.45, 2040 // Rounding to the nearest "50" is not exponent-adjusted in scientific notation: 2041 "0E2 m"); 2042 2043 assertFormatSingle( 2044 "Scientific notation with Usage: possible when using a reasonable Precision", 2045 "scientific usage/road measure-unit/length-meter @###", 2046 "scientific usage/road unit/meter @###", 2047 NumberFormatter.with() 2048 .unit(MeasureUnit.METER) 2049 .usage("road") 2050 .notation(Notation.scientific()) 2051 .precision(Precision.maxSignificantDigits(4)), 2052 new ULocale("en-ZA"), 2053 321.45, // 0.45 rounds down, 0.55 rounds up. 2054 "3,214E2 m"); 2055 2056 assertFormatSingle( 2057 "Scientific notation with Usage: possible when using a reasonable Precision", 2058 "scientific usage/default measure-unit/length-astronomical-unit unit-width-full-name", 2059 "scientific usage/default unit/astronomical-unit unit-width-full-name", 2060 NumberFormatter.with() 2061 .unit(MeasureUnit.forIdentifier("astronomical-unit")) 2062 .usage("default") 2063 .notation(Notation.scientific()) 2064 .unitWidth(UnitWidth.FULL_NAME), 2065 new ULocale("en-ZA"), 2066 1e20, 2067 "1,5E28 kilometres"); 2068 } 2069 2070 2071 @Test unitCurrency()2072 public void unitCurrency() { 2073 assertFormatDescending( 2074 "Currency", 2075 "currency/GBP", 2076 "currency/GBP", 2077 NumberFormatter.with().unit(GBP), 2078 ULocale.ENGLISH, 2079 "£87,650.00", 2080 "£8,765.00", 2081 "£876.50", 2082 "£87.65", 2083 "£8.76", 2084 "£0.88", 2085 "£0.09", 2086 "£0.01", 2087 "£0.00"); 2088 2089 assertFormatDescending( 2090 "Currency ISO", 2091 "currency/GBP unit-width-iso-code", 2092 "currency/GBP unit-width-iso-code", 2093 NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.ISO_CODE), 2094 ULocale.ENGLISH, 2095 "GBP 87,650.00", 2096 "GBP 8,765.00", 2097 "GBP 876.50", 2098 "GBP 87.65", 2099 "GBP 8.76", 2100 "GBP 0.88", 2101 "GBP 0.09", 2102 "GBP 0.01", 2103 "GBP 0.00"); 2104 2105 assertFormatDescending( 2106 "Currency Long Name", 2107 "currency/GBP unit-width-full-name", 2108 "currency/GBP unit-width-full-name", 2109 NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.FULL_NAME), 2110 ULocale.ENGLISH, 2111 "87,650.00 British pounds", 2112 "8,765.00 British pounds", 2113 "876.50 British pounds", 2114 "87.65 British pounds", 2115 "8.76 British pounds", 2116 "0.88 British pounds", 2117 "0.09 British pounds", 2118 "0.01 British pounds", 2119 "0.00 British pounds"); 2120 2121 assertFormatDescending( 2122 "Currency Hidden", 2123 "currency/GBP unit-width-hidden", 2124 "currency/GBP unit-width-hidden", 2125 NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.HIDDEN), 2126 ULocale.ENGLISH, 2127 "87,650.00", 2128 "8,765.00", 2129 "876.50", 2130 "87.65", 2131 "8.76", 2132 "0.88", 2133 "0.09", 2134 "0.01", 2135 "0.00"); 2136 2137 assertFormatSingleMeasure( 2138 "Currency with CurrencyAmount Input", 2139 "", 2140 "", 2141 NumberFormatter.with(), 2142 ULocale.ENGLISH, 2143 new CurrencyAmount(5.43, GBP), 2144 "£5.43"); 2145 2146 assertFormatSingle( 2147 "Currency Long Name from Pattern Syntax", 2148 null, 2149 null, 2150 NumberFormatter.fromDecimalFormat( 2151 PatternStringParser.parseToProperties("0 ¤¤¤"), 2152 DecimalFormatSymbols.getInstance(ULocale.ENGLISH), 2153 null).unit(GBP), 2154 ULocale.ENGLISH, 2155 1234567.89, 2156 "1234568 British pounds"); 2157 2158 assertFormatSingle( 2159 "Currency with Negative Sign", 2160 "currency/GBP", 2161 "currency/GBP", 2162 NumberFormatter.with().unit(GBP), 2163 ULocale.ENGLISH, 2164 -9876543.21, 2165 "-£9,876,543.21"); 2166 2167 // The full currency symbol is not shown in NARROW format. 2168 // NOTE: This example is in the documentation. 2169 assertFormatSingle( 2170 "Currency Difference between Narrow and Short (Narrow Version)", 2171 "currency/USD unit-width-narrow", 2172 "currency/USD unit-width-narrow", 2173 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.NARROW), 2174 ULocale.forLanguageTag("en-CA"), 2175 5.43, 2176 "US$5.43"); 2177 2178 assertFormatSingle( 2179 "Currency Difference between Narrow and Short (Short Version)", 2180 "currency/USD unit-width-short", 2181 "currency/USD unit-width-short", 2182 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), 2183 ULocale.forLanguageTag("en-CA"), 2184 5.43, 2185 "US$5.43"); 2186 2187 assertFormatSingle( 2188 "Currency Difference between Formal and Short (Formal Version)", 2189 "currency/TWD unit-width-formal", 2190 "currency/TWD unit-width-formal", 2191 NumberFormatter.with().unit(TWD).unitWidth(UnitWidth.FORMAL), 2192 ULocale.forLanguageTag("zh-TW"), 2193 5.43, 2194 "NT$5.43"); 2195 2196 assertFormatSingle( 2197 "Currency Difference between Formal and Short (Short Version)", 2198 "currency/TWD unit-width-short", 2199 "currency/TWD unit-width-short", 2200 NumberFormatter.with().unit(TWD).unitWidth(UnitWidth.SHORT), 2201 ULocale.forLanguageTag("zh-TW"), 2202 5.43, 2203 "$5.43"); 2204 2205 assertFormatSingle( 2206 "Currency Difference between Variant and Short (Formal Version)", 2207 "currency/TRY unit-width-variant", 2208 "currency/TRY unit-width-variant", 2209 NumberFormatter.with().unit(TRY).unitWidth(UnitWidth.VARIANT), 2210 ULocale.forLanguageTag("tr-TR"), 2211 5.43, 2212 "TL\u00A05,43"); 2213 2214 assertFormatSingle( 2215 "Currency Difference between Variant and Short (Short Version)", 2216 "currency/TRY unit-width-short", 2217 "currency/TRY unit-width-short", 2218 NumberFormatter.with().unit(TRY).unitWidth(UnitWidth.SHORT), 2219 ULocale.forLanguageTag("tr-TR"), 2220 5.43, 2221 "₺5,43"); 2222 2223 assertFormatSingle( 2224 "Currency-dependent format (Control)", 2225 "currency/USD unit-width-short", 2226 "currency/USD unit-width-short", 2227 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), 2228 ULocale.forLanguageTag("ca"), 2229 444444.55, 2230 "444.444,55 USD"); 2231 2232 assertFormatSingle( 2233 "Currency-dependent format (Test)", 2234 "currency/ESP unit-width-short", 2235 "currency/ESP unit-width-short", 2236 NumberFormatter.with().unit(ESP).unitWidth(UnitWidth.SHORT), 2237 ULocale.forLanguageTag("ca"), 2238 444444.55, 2239 "₧ 444.445"); 2240 2241 assertFormatSingle( 2242 "Currency-dependent symbols (Control)", 2243 "currency/USD unit-width-short", 2244 "currency/USD unit-width-short", 2245 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT), 2246 ULocale.forLanguageTag("pt-PT"), 2247 444444.55, 2248 "444 444,55 US$"); 2249 2250 // NOTE: This is a bit of a hack on CLDR's part. They set the currency symbol to U+200B (zero- 2251 // width space), and they set the decimal separator to the $ symbol. 2252 assertFormatSingle( 2253 "Currency-dependent symbols (Test Short)", 2254 "currency/PTE unit-width-short", 2255 "currency/PTE unit-width-short", 2256 NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.SHORT), 2257 ULocale.forLanguageTag("pt-PT"), 2258 444444.55, 2259 "444,444$55 \u200B"); 2260 2261 assertFormatSingle( 2262 "Currency-dependent symbols (Test Narrow)", 2263 "currency/PTE unit-width-narrow", 2264 "currency/PTE unit-width-narrow", 2265 NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.NARROW), 2266 ULocale.forLanguageTag("pt-PT"), 2267 444444.55, 2268 "444,444$55 \u200B"); 2269 2270 assertFormatSingle( 2271 "Currency-dependent symbols (Test ISO Code)", 2272 "currency/PTE unit-width-iso-code", 2273 "currency/PTE unit-width-iso-code", 2274 NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.ISO_CODE), 2275 ULocale.forLanguageTag("pt-PT"), 2276 444444.55, 2277 "444,444$55 PTE"); 2278 2279 assertFormatSingle( 2280 "Plural form depending on visible digits (ICU-20499)", 2281 "currency/RON unit-width-full-name", 2282 "currency/RON unit-width-full-name", 2283 NumberFormatter.with().unit(RON).unitWidth(UnitWidth.FULL_NAME), 2284 ULocale.forLanguageTag("ro-RO"), 2285 24, 2286 "24,00 lei românești"); 2287 2288 assertFormatSingle( 2289 "Currency spacing in suffix (ICU-20954)", 2290 "currency/CNY", 2291 "currency/CNY", 2292 NumberFormatter.with().unit(CNY), 2293 ULocale.forLanguageTag("lu"), 2294 123.12, 2295 "123,12 CN¥"); 2296 2297 // de-CH has currency pattern "¤ #,##0.00;¤-#,##0.00" 2298 assertFormatSingle( 2299 "Sign position on negative number with pattern spacing", 2300 "currency/RON", 2301 "currency/RON", 2302 NumberFormatter.with().unit(RON), 2303 ULocale.forLanguageTag("de-CH"), 2304 -123.12, 2305 "RON-123.12"); 2306 2307 // TODO(ICU-21420): Move the sign to the inside of the number 2308 assertFormatSingle( 2309 "Sign position on negative number with currency spacing", 2310 "currency/RON", 2311 "currency/RON", 2312 NumberFormatter.with().unit(RON), 2313 ULocale.forLanguageTag("en"), 2314 -123.12, 2315 "-RON 123.12"); 2316 } 2317 2318 public static class UnitInflectionTestCase { 2319 public final String unitIdentifier; 2320 public final String locale; 2321 public final String unitDisplayCase; 2322 public final double value; 2323 public final String expected; 2324 UnitInflectionTestCase(String unitIdentifier, String locale, String unitDisplayCase, double value, String expected)2325 UnitInflectionTestCase(String unitIdentifier, 2326 String locale, 2327 String unitDisplayCase, 2328 double value, 2329 String expected) { 2330 this.unitIdentifier = unitIdentifier; 2331 this.locale = locale; 2332 this.unitDisplayCase = unitDisplayCase; 2333 this.value = value; 2334 this.expected = expected; 2335 } 2336 runTest(UnlocalizedNumberFormatter unf, String skeleton)2337 public void runTest(UnlocalizedNumberFormatter unf, String skeleton) { 2338 MeasureUnit mu = MeasureUnit.forIdentifier(unitIdentifier); 2339 String skel; 2340 if (this.unitDisplayCase == null || this.unitDisplayCase.isEmpty()) { 2341 unf = unf.unit(mu).unitDisplayCase(""); 2342 skel = "unit/" + unitIdentifier + " " + skeleton; 2343 } else { 2344 unf = unf.unit(mu).unitDisplayCase(this.unitDisplayCase); 2345 // No skeleton support for unitDisplayCase yet. 2346 skel = null; 2347 } 2348 assertFormatSingle("\"" + skeleton + "\", locale=\"" + this.locale + "\", case=\"" + 2349 (this.unitDisplayCase != null ? this.unitDisplayCase : "") + 2350 "\", value=" + this.value, 2351 skel, skel, unf, new ULocale(this.locale), this.value, this.expected); 2352 } 2353 runTestWithDisplayOptions(UnlocalizedNumberFormatter unf, String skeleton)2354 public void runTestWithDisplayOptions(UnlocalizedNumberFormatter unf, String skeleton) { 2355 MeasureUnit mu = MeasureUnit.forIdentifier(unitIdentifier); 2356 String skel; 2357 2358 DisplayOptions.Builder displayOptionsBuilder = DisplayOptions.builder(); 2359 if (this.unitDisplayCase == null || this.unitDisplayCase.isEmpty()) { 2360 DisplayOptions displayOptions = displayOptionsBuilder.build(); 2361 unf = unf.unit(mu).displayOptions(displayOptions); 2362 skel = "unit/" + unitIdentifier + " " + skeleton; 2363 } else { 2364 DisplayOptions displayOptions = displayOptionsBuilder.setGrammaticalCase( 2365 GrammaticalCase.fromIdentifier(this.unitDisplayCase)).build(); 2366 unf = unf.unit(mu).displayOptions(displayOptions); 2367 // No skeleton support for unitDisplayCase yet. 2368 skel = null; 2369 } 2370 assertFormatSingle("\"" + skeleton + "\", locale=\"" + this.locale + "\", case=\"" + 2371 (this.unitDisplayCase != null ? this.unitDisplayCase : "") + 2372 "\", value=" + this.value, 2373 skel, skel, unf, new ULocale(this.locale), this.value, this.expected); 2374 } 2375 } 2376 2377 @Test unitInflections()2378 public void unitInflections() { 2379 UnlocalizedNumberFormatter unf; 2380 String skeleton; 2381 2382 { 2383 // Simple inflected form test - test case based on the example in CLDR's 2384 // grammaticalFeatures.xml 2385 unf = NumberFormatter.with().unitWidth(UnitWidth.FULL_NAME); 2386 skeleton = "unit-width-full-name"; 2387 final UnitInflectionTestCase percentCases[] = { 2388 new UnitInflectionTestCase("percent", "ru", null, 10, "10 процентов"), // many 2389 new UnitInflectionTestCase("percent", "ru", "genitive", 10, "10 процентов"), // many 2390 new UnitInflectionTestCase("percent", "ru", null, 33, "33 процента"), // few 2391 new UnitInflectionTestCase("percent", "ru", "genitive", 33, "33 процентов"), // few 2392 new UnitInflectionTestCase("percent", "ru", null, 1, "1 процент"), // one 2393 new UnitInflectionTestCase("percent", "ru", "genitive", 1, "1 процента"), // one 2394 }; 2395 for (UnitInflectionTestCase t : percentCases) { 2396 t.runTest(unf, skeleton); 2397 t.runTestWithDisplayOptions(unf, skeleton); 2398 } 2399 } 2400 { 2401 // General testing of inflection rules 2402 unf = NumberFormatter.with().unitWidth(UnitWidth.FULL_NAME); 2403 skeleton = "unit-width-full-name"; 2404 final UnitInflectionTestCase testCases[] = { 2405 // Check up on the basic values that the compound patterns below 2406 // are derived from: 2407 new UnitInflectionTestCase("meter", "de", null, 1, "1 Meter"), 2408 new UnitInflectionTestCase("meter", "de", "genitive", 1, "1 Meters"), 2409 new UnitInflectionTestCase("meter", "de", null, 2, "2 Meter"), 2410 new UnitInflectionTestCase("meter", "de", "dative", 2, "2 Metern"), 2411 new UnitInflectionTestCase("mile", "de", null, 1, "1 Meile"), 2412 new UnitInflectionTestCase("mile", "de", null, 2, "2 Meilen"), 2413 new UnitInflectionTestCase("day", "de", null, 1, "1 Tag"), 2414 new UnitInflectionTestCase("day", "de", "genitive", 1, "1 Tages"), 2415 new UnitInflectionTestCase("day", "de", null, 2, "2 Tage"), 2416 new UnitInflectionTestCase("day", "de", "dative", 2, "2 Tagen"), 2417 new UnitInflectionTestCase("decade", "de", null, 1, "1\u00A0Jahrzehnt"), 2418 new UnitInflectionTestCase("decade", "de", null, 2, "2\u00A0Jahrzehnte"), 2419 2420 // Testing de "per" rules: 2421 // <deriveComponent feature="case" structure="per" value0="compound" value1="accusative"/> 2422 // <deriveComponent feature="plural" structure="per" value0="compound" value1="one"/> 2423 // per-patterns use accusative, but since the accusative form 2424 // matches the nominative form, we're not effectively testing value1 2425 // in the "case & per" rule above. 2426 2427 // We have a perUnitPattern for "day" in de, so "per" rules are not 2428 // applied for these: 2429 new UnitInflectionTestCase("meter-per-day", "de", null, 1, "1 Meter pro Tag"), 2430 new UnitInflectionTestCase("meter-per-day", "de", "genitive", 1, "1 Meters pro Tag"), 2431 new UnitInflectionTestCase("meter-per-day", "de", null, 2, "2 Meter pro Tag"), 2432 new UnitInflectionTestCase("meter-per-day", "de", "dative", 2, "2 Metern pro Tag"), 2433 2434 // testing code path that falls back to "root" grammaticalFeatures 2435 // but does not inflect: 2436 new UnitInflectionTestCase("meter-per-day", "af", null, 1, "1 meter per dag"), 2437 new UnitInflectionTestCase("meter-per-day", "af", "dative", 1, "1 meter per dag"), 2438 2439 // Decade does not have a perUnitPattern at this time (CLDR 39 / ICU 2440 // 69), so we can test for the correct form of the per part: 2441 // Fragile test cases: these cases will break when whitespace is more 2442 // consistently applied. 2443 new UnitInflectionTestCase("parsec-per-decade", "de", null, 1, 2444 "1\u00A0Parsec pro Jahrzehnt"), 2445 new UnitInflectionTestCase("parsec-per-decade", "de", "genitive", 1, 2446 "1 Parsec pro Jahrzehnt"), 2447 new UnitInflectionTestCase("parsec-per-decade", "de", null, 2, 2448 "2\u00A0Parsec pro Jahrzehnt"), 2449 new UnitInflectionTestCase("parsec-per-decade", "de", "dative", 2, 2450 "2 Parsec pro Jahrzehnt"), 2451 2452 // Testing de "times", "power" and "prefix" rules: 2453 // 2454 // <deriveComponent feature="plural" structure="times" value0="one" value1="compound"/> 2455 // <deriveComponent feature="case" structure="times" value0="nominative" value1="compound"/> 2456 // 2457 // <deriveComponent feature="plural" structure="prefix" value0="one" value1="compound"/> 2458 // <deriveComponent feature="case" structure="prefix" value0="nominative" value1="compound"/> 2459 // 2460 // Prefixes in German don't change with plural or case, so these 2461 // tests can't test value0 of the following two rules: 2462 // <deriveComponent feature="plural" structure="power" value0="one" value1="compound"/> 2463 // <deriveComponent feature="case" structure="power" value0="nominative" value1="compound"/> 2464 2465 new UnitInflectionTestCase("square-decimeter-dekameter", "de", null, 1, 2466 "1 Dekameter⋅Quadratdezimeter"), 2467 new UnitInflectionTestCase("square-decimeter-dekameter", "de", "genitive", 1, 2468 "1 Dekameter⋅Quadratdezimeter"), 2469 new UnitInflectionTestCase("square-decimeter-dekameter", "de", null, 2, 2470 "2 Dekameter⋅Quadratdezimeter"), 2471 new UnitInflectionTestCase("square-decimeter-dekameter", "de", "dative", 2, 2472 "2 Dekameter⋅Quadratdezimeter"), 2473 // Feminine "Meile" better demonstrates singular-vs-plural form: 2474 new UnitInflectionTestCase("cubic-mile-dekamile", "de", null, 1, 2475 "1 Dekameile⋅Kubikmeile"), 2476 new UnitInflectionTestCase("cubic-mile-dekamile", "de", null, 2, 2477 "2 Dekameile⋅Kubikmeilen"), 2478 2479 // French handles plural "times" and "power" structures differently: 2480 // plural form impacts all "numerator" units (denominator remains 2481 // singular like German), and "pow2" prefixes have different forms 2482 // <deriveComponent feature="plural" structure="times" value0="compound" 2483 // value1="compound"/> 2484 // <deriveComponent feature="plural" structure="power" value0="compound" 2485 // value1="compound"/> 2486 new UnitInflectionTestCase("square-decimeter-square-second", "fr", null, 1, 2487 "1\u00A0décimètre carré-seconde carrée"), 2488 new UnitInflectionTestCase("square-decimeter-square-second", "fr", null, 2, 2489 "2\u00A0décimètres carrés-secondes carrées"), 2490 }; 2491 for (UnitInflectionTestCase t : testCases) { 2492 t.runTest(unf, skeleton); 2493 t.runTestWithDisplayOptions(unf, skeleton); 2494 } 2495 } 2496 { 2497 // Testing inflection of mixed units: 2498 unf = NumberFormatter.with().unitWidth(UnitWidth.FULL_NAME); 2499 skeleton = "unit-width-full-name"; 2500 final UnitInflectionTestCase meterPerDayCases[] = { 2501 new UnitInflectionTestCase("meter", "de", null, 1, "1 Meter"), 2502 new UnitInflectionTestCase("meter", "de", "genitive", 1, "1 Meters"), 2503 new UnitInflectionTestCase("meter", "de", "dative", 2, "2 Metern"), 2504 new UnitInflectionTestCase("centimeter", "de", null, 1, "1 Zentimeter"), 2505 new UnitInflectionTestCase("centimeter", "de", "genitive", 1, "1 Zentimeters"), 2506 new UnitInflectionTestCase("centimeter", "de", "dative", 10, "10 Zentimetern"), 2507 // TODO(CLDR-14582): check that these inflections are correct, and 2508 // whether CLDR needs any rules for them (presumably CLDR spec 2509 // should mention it, if it's a consistent rule): 2510 new UnitInflectionTestCase("meter-and-centimeter", "de", null, 1.01, 2511 "1 Meter, 1 Zentimeter"), 2512 new UnitInflectionTestCase("meter-and-centimeter", "de", "genitive", 1.01, 2513 "1 Meters, 1 Zentimeters"), 2514 new UnitInflectionTestCase("meter-and-centimeter", "de", "genitive", 1.1, 2515 "1 Meters, 10 Zentimeter"), 2516 new UnitInflectionTestCase("meter-and-centimeter", "de", "dative", 1.1, 2517 "1 Meter, 10 Zentimetern"), 2518 new UnitInflectionTestCase("meter-and-centimeter", "de", "dative", 2.1, 2519 "2 Metern, 10 Zentimetern"), 2520 }; 2521 for (UnitInflectionTestCase t : meterPerDayCases) { 2522 t.runTest(unf, skeleton); 2523 t.runTestWithDisplayOptions(unf, skeleton); 2524 } 2525 } 2526 // TODO: add a usage case that selects between preferences with different 2527 // genders (e.g. year, month, day, hour). 2528 // TODO: look at "↑↑↑" cases: check that inheritance is done right. 2529 } 2530 2531 @Test unitNounClass()2532 public void unitNounClass() { 2533 class TestCase { 2534 public String locale; 2535 public String unitIdentifier; 2536 public NounClass expectedNounClass; 2537 2538 public TestCase(String locale, String unitIdentifier, NounClass expectedNounClass) { 2539 this.locale = locale; 2540 this.unitIdentifier = unitIdentifier; 2541 this.expectedNounClass = expectedNounClass; 2542 } 2543 } 2544 2545 TestCase cases[] = { 2546 new TestCase("de", "inch", NounClass.MASCULINE), // 2547 new TestCase("de", "yard", NounClass.NEUTER), // 2548 new TestCase("de", "meter", NounClass.MASCULINE), // 2549 new TestCase("de", "liter", NounClass.MASCULINE), // 2550 new TestCase("de", "second", NounClass.FEMININE), // 2551 new TestCase("de", "minute", NounClass.FEMININE), // 2552 new TestCase("de", "hour", NounClass.FEMININE), // 2553 new TestCase("de", "day", NounClass.MASCULINE), // 2554 new TestCase("de", "year", NounClass.NEUTER), // 2555 new TestCase("de", "gram", NounClass.NEUTER), // 2556 new TestCase("de", "watt", NounClass.NEUTER), // 2557 new TestCase("de", "bit", NounClass.NEUTER), // 2558 new TestCase("de", "byte", NounClass.NEUTER), // 2559 2560 new TestCase("fr", "inch", NounClass.MASCULINE), // 2561 new TestCase("fr", "yard", NounClass.MASCULINE), // 2562 new TestCase("fr", "meter", NounClass.MASCULINE), // 2563 new TestCase("fr", "liter", NounClass.MASCULINE), // 2564 new TestCase("fr", "second", NounClass.FEMININE), // 2565 new TestCase("fr", "minute", NounClass.FEMININE), // 2566 new TestCase("fr", "hour", NounClass.FEMININE), // 2567 new TestCase("fr", "day", NounClass.MASCULINE), // 2568 new TestCase("fr", "year", NounClass.MASCULINE), // 2569 new TestCase("fr", "gram", NounClass.MASCULINE), // 2570 2571 // grammaticalFeatures deriveCompound "per" rule takes the gender of the 2572 // numerator unit: 2573 new TestCase("de", "meter-per-hour", NounClass.MASCULINE), 2574 new TestCase("fr", "meter-per-hour", NounClass.MASCULINE), 2575 new TestCase("af", "meter-per-hour", NounClass.UNDEFINED), // ungendered language 2576 2577 // French "times" takes gender from first value, German takes the 2578 // second. Prefix and power does not have impact on gender for these 2579 // languages: 2580 new TestCase("de", "square-decimeter-square-second", NounClass.FEMININE), 2581 new TestCase("fr", "square-decimeter-square-second", NounClass.MASCULINE), 2582 2583 // TODO(icu-units#149): percent and permille bypasses 2584 // LongNameHandler when unitWidth is not FULL_NAME: 2585 // // Gender of per-second might be that of percent? TODO(icu-units#28) 2586 // new TestCase("de", "percent", NounClass.NEUTER), // 2587 // new TestCase("fr", "percent", NounClass.MASCULINE), // 2588 2589 // Built-in units whose simple units lack gender in the CLDR data file 2590 new TestCase("de", "kilopascal", NounClass.NEUTER), // 2591 new TestCase("fr", "kilopascal", NounClass.MASCULINE), // 2592 // new TestCase("de", "pascal", NounClass.UNDEFINED), // 2593 // new TestCase("fr", "pascal", NounClass.UNDEFINED), // 2594 2595 // Built-in units that lack gender in the CLDR data file 2596 // new TestCase("de", "revolution", NounClass.UNDEFINED), // 2597 // new TestCase("de", "radian", NounClass.UNDEFINED), // 2598 // new TestCase("de", "arc-minute", NounClass.UNDEFINED), // 2599 // new TestCase("de", "arc-second", NounClass.UNDEFINED), // 2600 new TestCase("de", "square-yard", NounClass.NEUTER), // COMPOUND 2601 new TestCase("de", "square-inch", NounClass.MASCULINE), // COMPOUND 2602 // new TestCase("de", "dunam", NounClass.UNDEFINED), // 2603 // new TestCase("de", "karat", NounClass.UNDEFINED), // 2604 // new TestCase("de", "milligram-ofglucose-per-deciliter", NounClass.UNDEFINED), // COMPOUND, ofglucose 2605 // new TestCase("de", "millimole-per-liter", NounClass.UNDEFINED), // COMPOUND, mole 2606 // new TestCase("de", "permillion", NounClass.UNDEFINED), // 2607 // new TestCase("de", "permille", NounClass.UNDEFINED), // 2608 // new TestCase("de", "permyriad", NounClass.UNDEFINED), // 2609 // new TestCase("de", "mole", NounClass.UNDEFINED), // 2610 new TestCase("de", "liter-per-kilometer", NounClass.MASCULINE), // COMPOUND 2611 new TestCase("de", "petabyte", NounClass.NEUTER), // PREFIX 2612 new TestCase("de", "terabit", NounClass.NEUTER), // PREFIX 2613 // new TestCase("de", "century", NounClass.UNDEFINED), // 2614 // new TestCase("de", "decade", NounClass.UNDEFINED), // 2615 new TestCase("de", "millisecond", NounClass.FEMININE), // PREFIX 2616 new TestCase("de", "microsecond", NounClass.FEMININE), // PREFIX 2617 new TestCase("de", "nanosecond", NounClass.FEMININE), // PREFIX 2618 // new TestCase("de", "ampere", NounClass.UNDEFINED), // 2619 // new TestCase("de", "milliampere", NounClass.UNDEFINED), // PREFIX, ampere 2620 // new TestCase("de", "ohm", NounClass.UNDEFINED), // 2621 // new TestCase("de", "calorie", NounClass.UNDEFINED), // 2622 // new TestCase("de", "kilojoule", NounClass.UNDEFINED), // PREFIX, joule 2623 // new TestCase("de", "joule", NounClass.UNDEFINED), // 2624 new TestCase("de", "kilowatt-hour", NounClass.FEMININE), // COMPOUND 2625 // new TestCase("de", "electronvolt", NounClass.UNDEFINED), // 2626 // new TestCase("de", "british-thermal-unit", NounClass.UNDEFINED), // 2627 // new TestCase("de", "therm-us", NounClass.UNDEFINED), // 2628 // new TestCase("de", "pound-force", NounClass.UNDEFINED), // 2629 // new TestCase("de", "newton", NounClass.UNDEFINED), // 2630 // new TestCase("de", "gigahertz", NounClass.UNDEFINED), // PREFIX, hertz 2631 // new TestCase("de", "megahertz", NounClass.UNDEFINED), // PREFIX, hertz 2632 // new TestCase("de", "kilohertz", NounClass.UNDEFINED), // PREFIX, hertz 2633 // new TestCase("de", "hertz", NounClass.UNDEFINED), // PREFIX, hertz 2634 // new TestCase("de", "em", NounClass.UNDEFINED), // 2635 // new TestCase("de", "pixel", NounClass.UNDEFINED), // 2636 // new TestCase("de", "megapixel", NounClass.UNDEFINED), // 2637 // new TestCase("de", "pixel-per-centimeter", NounClass.UNDEFINED), // COMPOUND, pixel 2638 // new TestCase("de", "pixel-per-inch", NounClass.UNDEFINED), // COMPOUND, pixel 2639 // new TestCase("de", "dot-per-centimeter", NounClass.UNDEFINED), // COMPOUND, dot 2640 // new TestCase("de", "dot-per-inch", NounClass.UNDEFINED), // COMPOUND, dot 2641 // new TestCase("de", "dot", NounClass.UNDEFINED), // 2642 // new TestCase("de", "earth-radius", NounClass.UNDEFINED), // 2643 new TestCase("de", "decimeter", NounClass.MASCULINE), // PREFIX 2644 new TestCase("de", "micrometer", NounClass.MASCULINE), // PREFIX 2645 new TestCase("de", "nanometer", NounClass.MASCULINE), // PREFIX 2646 // new TestCase("de", "light-year", NounClass.UNDEFINED), // 2647 // new TestCase("de", "astronomical-unit", NounClass.UNDEFINED), // 2648 // new TestCase("de", "furlong", NounClass.UNDEFINED), // 2649 // new TestCase("de", "fathom", NounClass.UNDEFINED), // 2650 // new TestCase("de", "nautical-mile", NounClass.UNDEFINED), // 2651 // new TestCase("de", "mile-scandinavian", NounClass.UNDEFINED), // 2652 // new TestCase("de", "point", NounClass.UNDEFINED), // 2653 // new TestCase("de", "lux", NounClass.UNDEFINED), // 2654 // new TestCase("de", "candela", NounClass.UNDEFINED), // 2655 // new TestCase("de", "lumen", NounClass.UNDEFINED), // 2656 // new TestCase("de", "tonne", NounClass.UNDEFINED), // 2657 new TestCase("de", "microgram", NounClass.NEUTER), // PREFIX 2658 // new TestCase("de", "ton", NounClass.UNDEFINED), // 2659 // new TestCase("de", "stone", NounClass.UNDEFINED), // 2660 // new TestCase("de", "ounce-troy", NounClass.UNDEFINED), // 2661 // new TestCase("de", "carat", NounClass.UNDEFINED), // 2662 new TestCase("de", "gigawatt", NounClass.NEUTER), // PREFIX 2663 new TestCase("de", "milliwatt", NounClass.NEUTER), // PREFIX 2664 // new TestCase("de", "horsepower", NounClass.UNDEFINED), // 2665 // new TestCase("de", "millimeter-ofhg", NounClass.UNDEFINED), // 2666 // new TestCase("de", "pound-force-per-square-inch", NounClass.UNDEFINED), // COMPOUND, pound-force 2667 // new TestCase("de", "inch-ofhg", NounClass.UNDEFINED), // 2668 // new TestCase("de", "bar", NounClass.UNDEFINED), // 2669 // new TestCase("de", "millibar", NounClass.UNDEFINED), // PREFIX, bar 2670 // new TestCase("de", "atmosphere", NounClass.UNDEFINED), // 2671 // new TestCase("de", "pascal", NounClass.UNDEFINED), // PREFIX, kilopascal? neuter? 2672 // new TestCase("de", "hectopascal", NounClass.UNDEFINED), // PREFIX, pascal, neuter? 2673 // new TestCase("de", "megapascal", NounClass.UNDEFINED), // PREFIX, pascal, neuter? 2674 // new TestCase("de", "knot", NounClass.UNDEFINED), // 2675 new TestCase("de", "pound-force-foot", NounClass.MASCULINE), // COMPOUND 2676 new TestCase("de", "newton-meter", NounClass.MASCULINE), // COMPOUND 2677 new TestCase("de", "cubic-kilometer", NounClass.MASCULINE), // POWER 2678 new TestCase("de", "cubic-yard", NounClass.NEUTER), // POWER 2679 new TestCase("de", "cubic-inch", NounClass.MASCULINE), // POWER 2680 new TestCase("de", "megaliter", NounClass.MASCULINE), // PREFIX 2681 new TestCase("de", "hectoliter", NounClass.MASCULINE), // PREFIX 2682 // new TestCase("de", "pint-metric", NounClass.UNDEFINED), // 2683 // new TestCase("de", "cup-metric", NounClass.UNDEFINED), // 2684 new TestCase("de", "acre-foot", NounClass.MASCULINE), // COMPOUND 2685 // new TestCase("de", "bushel", NounClass.UNDEFINED), // 2686 // new TestCase("de", "barrel", NounClass.UNDEFINED), // 2687 // Units missing gender in German also misses gender in French: 2688 // new TestCase("fr", "revolution", NounClass.UNDEFINED), // 2689 // new TestCase("fr", "radian", NounClass.UNDEFINED), // 2690 // new TestCase("fr", "arc-minute", NounClass.UNDEFINED), // 2691 // new TestCase("fr", "arc-second", NounClass.UNDEFINED), // 2692 new TestCase("fr", "square-yard", NounClass.MASCULINE), // COMPOUND 2693 new TestCase("fr", "square-inch", NounClass.MASCULINE), // COMPOUND 2694 // new TestCase("fr", "dunam", NounClass.UNDEFINED), // 2695 // new TestCase("fr", "karat", NounClass.UNDEFINED), // 2696 new TestCase("fr", "milligram-ofglucose-per-deciliter", NounClass.MASCULINE), // COMPOUND 2697 // new TestCase("fr", "millimole-per-liter", NounClass.UNDEFINED), // COMPOUND, mole 2698 // new TestCase("fr", "permillion", NounClass.UNDEFINED), // 2699 // new TestCase("fr", "permille", NounClass.UNDEFINED), // 2700 // new TestCase("fr", "permyriad", NounClass.UNDEFINED), // 2701 // new TestCase("fr", "mole", NounClass.UNDEFINED), // 2702 new TestCase("fr", "liter-per-kilometer", NounClass.MASCULINE), // COMPOUND 2703 // new TestCase("fr", "petabyte", NounClass.UNDEFINED), // PREFIX 2704 // new TestCase("fr", "terabit", NounClass.UNDEFINED), // PREFIX 2705 // new TestCase("fr", "century", NounClass.UNDEFINED), // 2706 // new TestCase("fr", "decade", NounClass.UNDEFINED), // 2707 new TestCase("fr", "millisecond", NounClass.FEMININE), // PREFIX 2708 new TestCase("fr", "microsecond", NounClass.FEMININE), // PREFIX 2709 new TestCase("fr", "nanosecond", NounClass.FEMININE), // PREFIX 2710 // new TestCase("fr", "ampere", NounClass.UNDEFINED), // 2711 // new TestCase("fr", "milliampere", NounClass.UNDEFINED), // PREFIX, ampere 2712 // new TestCase("fr", "ohm", NounClass.UNDEFINED), // 2713 // new TestCase("fr", "calorie", NounClass.UNDEFINED), // 2714 // new TestCase("fr", "kilojoule", NounClass.UNDEFINED), // PREFIX, joule 2715 // new TestCase("fr", "joule", NounClass.UNDEFINED), // 2716 // new TestCase("fr", "kilowatt-hour", NounClass.UNDEFINED), // COMPOUND 2717 // new TestCase("fr", "electronvolt", NounClass.UNDEFINED), // 2718 // new TestCase("fr", "british-thermal-unit", NounClass.UNDEFINED), // 2719 // new TestCase("fr", "therm-us", NounClass.UNDEFINED), // 2720 // new TestCase("fr", "pound-force", NounClass.UNDEFINED), // 2721 // new TestCase("fr", "newton", NounClass.UNDEFINED), // 2722 // new TestCase("fr", "gigahertz", NounClass.UNDEFINED), // PREFIX, hertz 2723 // new TestCase("fr", "megahertz", NounClass.UNDEFINED), // PREFIX, hertz 2724 // new TestCase("fr", "kilohertz", NounClass.UNDEFINED), // PREFIX, hertz 2725 // new TestCase("fr", "hertz", NounClass.UNDEFINED), // PREFIX, hertz 2726 // new TestCase("fr", "em", NounClass.UNDEFINED), // 2727 // new TestCase("fr", "pixel", NounClass.UNDEFINED), // 2728 // new TestCase("fr", "megapixel", NounClass.UNDEFINED), // 2729 // new TestCase("fr", "pixel-per-centimeter", NounClass.UNDEFINED), // COMPOUND, pixel 2730 // new TestCase("fr", "pixel-per-inch", NounClass.UNDEFINED), // COMPOUND, pixel 2731 // new TestCase("fr", "dot-per-centimeter", NounClass.UNDEFINED), // COMPOUND, dot 2732 // new TestCase("fr", "dot-per-inch", NounClass.UNDEFINED), // COMPOUND, dot 2733 // new TestCase("fr", "dot", NounClass.UNDEFINED), // 2734 // new TestCase("fr", "earth-radius", NounClass.UNDEFINED), // 2735 new TestCase("fr", "decimeter", NounClass.MASCULINE), // PREFIX 2736 new TestCase("fr", "micrometer", NounClass.MASCULINE), // PREFIX 2737 new TestCase("fr", "nanometer", NounClass.MASCULINE), // PREFIX 2738 // new TestCase("fr", "light-year", NounClass.UNDEFINED), // 2739 // new TestCase("fr", "astronomical-unit", NounClass.UNDEFINED), // 2740 // new TestCase("fr", "furlong", NounClass.UNDEFINED), // 2741 // new TestCase("fr", "fathom", NounClass.UNDEFINED), // 2742 // new TestCase("fr", "nautical-mile", NounClass.UNDEFINED), // 2743 // new TestCase("fr", "mile-scandinavian", NounClass.UNDEFINED), // 2744 // new TestCase("fr", "point", NounClass.UNDEFINED), // 2745 // new TestCase("fr", "lux", NounClass.UNDEFINED), // 2746 // new TestCase("fr", "candela", NounClass.UNDEFINED), // 2747 // new TestCase("fr", "lumen", NounClass.UNDEFINED), // 2748 // new TestCase("fr", "tonne", NounClass.UNDEFINED), // 2749 new TestCase("fr", "microgram", NounClass.MASCULINE), // PREFIX 2750 // new TestCase("fr", "ton", NounClass.UNDEFINED), // 2751 // new TestCase("fr", "stone", NounClass.UNDEFINED), // 2752 // new TestCase("fr", "ounce-troy", NounClass.UNDEFINED), // 2753 // new TestCase("fr", "carat", NounClass.UNDEFINED), // 2754 // new TestCase("fr", "gigawatt", NounClass.UNDEFINED), // PREFIX 2755 // new TestCase("fr", "milliwatt", NounClass.UNDEFINED), // 2756 // new TestCase("fr", "horsepower", NounClass.UNDEFINED), // 2757 new TestCase("fr", "millimeter-ofhg", NounClass.MASCULINE), // 2758 // new TestCase("fr", "pound-force-per-square-inch", NounClass.UNDEFINED), // COMPOUND, pound-force 2759 new TestCase("fr", "inch-ofhg", NounClass.MASCULINE), // 2760 // new TestCase("fr", "bar", NounClass.UNDEFINED), // 2761 // new TestCase("fr", "millibar", NounClass.UNDEFINED), // PREFIX, bar 2762 // new TestCase("fr", "atmosphere", NounClass.UNDEFINED), // 2763 // new TestCase("fr", "pascal", NounClass.UNDEFINED), // PREFIX, kilopascal? 2764 // new TestCase("fr", "hectopascal", NounClass.UNDEFINED), // PREFIX, pascal 2765 // new TestCase("fr", "megapascal", NounClass.UNDEFINED), // PREFIX, pascal 2766 // new TestCase("fr", "knot", NounClass.UNDEFINED), // 2767 // new TestCase("fr", "pound-force-foot", NounClass.UNDEFINED), // 2768 // new TestCase("fr", "newton-meter", NounClass.UNDEFINED), // 2769 new TestCase("fr", "cubic-kilometer", NounClass.MASCULINE), // POWER 2770 new TestCase("fr", "cubic-yard", NounClass.MASCULINE), // POWER 2771 new TestCase("fr", "cubic-inch", NounClass.MASCULINE), // POWER 2772 new TestCase("fr", "megaliter", NounClass.MASCULINE), // PREFIX 2773 new TestCase("fr", "hectoliter", NounClass.MASCULINE), // PREFIX 2774 // new TestCase("fr", "pint-metric", NounClass.UNDEFINED), // 2775 // new TestCase("fr", "cup-metric", NounClass.UNDEFINED), // 2776 new TestCase("fr", "acre-foot", NounClass.FEMININE), // COMPOUND 2777 // new TestCase("fr", "bushel", NounClass.UNDEFINED), // 2778 // new TestCase("fr", "barrel", NounClass.UNDEFINED), // 2779 // Some more French units missing gender: 2780 // new TestCase("fr", "degree", NounClass.UNDEFINED), // 2781 new TestCase("fr", "square-meter", NounClass.MASCULINE), // COMPOUND 2782 // new TestCase("fr", "terabyte", NounClass.UNDEFINED), // PREFIX, byte 2783 // new TestCase("fr", "gigabyte", NounClass.UNDEFINED), // PREFIX, byte 2784 // new TestCase("fr", "gigabit", NounClass.UNDEFINED), // PREFIX, bit 2785 // new TestCase("fr", "megabyte", NounClass.UNDEFINED), // PREFIX, byte 2786 // new TestCase("fr", "megabit", NounClass.UNDEFINED), // PREFIX, bit 2787 // new TestCase("fr", "kilobyte", NounClass.UNDEFINED), // PREFIX, byte 2788 // new TestCase("fr", "kilobit", NounClass.UNDEFINED), // PREFIX, bit 2789 // new TestCase("fr", "byte", NounClass.UNDEFINED), // 2790 // new TestCase("fr", "bit", NounClass.UNDEFINED), // 2791 // new TestCase("fr", "volt", NounClass.UNDEFINED), // 2792 new TestCase("fr", "cubic-meter", NounClass.MASCULINE), // POWER 2793 2794 // gender-lacking builtins within compound units 2795 new TestCase("de", "newton-meter-per-second", NounClass.MASCULINE), 2796 2797 // TODO(ICU-21494): determine whether list genders behave as follows, 2798 // and implement proper getListGender support (covering more than just 2799 // two genders): 2800 // // gender rule for lists of people: de "neutral", fr "maleTaints" 2801 // new TestCase("de", "day-and-hour-and-minute", NounClass.NEUTER), 2802 // new TestCase("de", "hour-and-minute", NounClass.FEMININE), 2803 // new TestCase("fr", "day-and-hour-and-minute", NounClass.MASCULINE), 2804 // new TestCase("fr", "hour-and-minute", NounClass.FEMININE), 2805 }; 2806 2807 LocalizedNumberFormatter formatter; 2808 FormattedNumber fn; 2809 for (TestCase t : cases) { 2810 formatter = NumberFormatter.with() 2811 .unit(MeasureUnit.forIdentifier(t.unitIdentifier)) 2812 .locale(new ULocale(t.locale)); 2813 fn = formatter.format(1.1); 2814 assertEquals("Testing noun classes with default width, unit: " + t.unitIdentifier + 2815 ", locale: " + t.locale, 2816 t.expectedNounClass, fn.getNounClass()); 2817 2818 formatter = NumberFormatter.with() 2819 .unit(MeasureUnit.forIdentifier(t.unitIdentifier)) 2820 .unitWidth(UnitWidth.FULL_NAME) 2821 .locale(new ULocale(t.locale)); 2822 fn = formatter.format(1.1); 2823 assertEquals("Testing noun classes with UnitWidth.FULL_NAME, unit: " + t.unitIdentifier + 2824 ", locale: " + t.locale, 2825 t.expectedNounClass, fn.getNounClass()); 2826 } 2827 2828 // Make sure getGender does not return garbage for genderless languages 2829 formatter = NumberFormatter.with().locale(ULocale.ENGLISH); 2830 fn = formatter.format(1.1); 2831 assertEquals("getNounClass for not supported language", NounClass.UNDEFINED, fn.getNounClass()); 2832 2833 } 2834 2835 @Test unitGender()2836 public void unitGender() { 2837 class TestCase { 2838 public String locale; 2839 public String unitIdentifier; 2840 public String expectedGender; 2841 2842 public TestCase(String locale, String unitIdentifier, String expectedGender) { 2843 this.locale = locale; 2844 this.unitIdentifier = unitIdentifier; 2845 this.expectedGender = expectedGender; 2846 } 2847 } 2848 2849 TestCase cases[] = { 2850 new TestCase("de", "inch", "masculine"), // 2851 new TestCase("de", "yard", "neuter"), // 2852 new TestCase("de", "meter", "masculine"), // 2853 new TestCase("de", "liter", "masculine"), // 2854 new TestCase("de", "second", "feminine"), // 2855 new TestCase("de", "minute", "feminine"), // 2856 new TestCase("de", "hour", "feminine"), // 2857 new TestCase("de", "day", "masculine"), // 2858 new TestCase("de", "year", "neuter"), // 2859 new TestCase("de", "gram", "neuter"), // 2860 new TestCase("de", "watt", "neuter"), // 2861 new TestCase("de", "bit", "neuter"), // 2862 new TestCase("de", "byte", "neuter"), // 2863 2864 new TestCase("fr", "inch", "masculine"), // 2865 new TestCase("fr", "yard", "masculine"), // 2866 new TestCase("fr", "meter", "masculine"), // 2867 new TestCase("fr", "liter", "masculine"), // 2868 new TestCase("fr", "second", "feminine"), // 2869 new TestCase("fr", "minute", "feminine"), // 2870 new TestCase("fr", "hour", "feminine"), // 2871 new TestCase("fr", "day", "masculine"), // 2872 new TestCase("fr", "year", "masculine"), // 2873 new TestCase("fr", "gram", "masculine"), // 2874 2875 // grammaticalFeatures deriveCompound "per" rule takes the gender of the 2876 // numerator unit: 2877 new TestCase("de", "meter-per-hour", "masculine"), 2878 new TestCase("fr", "meter-per-hour", "masculine"), 2879 new TestCase("af", "meter-per-hour", ""), // ungendered language 2880 2881 // French "times" takes gender from first value, German takes the 2882 // second. Prefix and power does not have impact on gender for these 2883 // languages: 2884 new TestCase("de", "square-decimeter-square-second", "feminine"), 2885 new TestCase("fr", "square-decimeter-square-second", "masculine"), 2886 2887 // TODO(icu-units#149): percent and permille bypasses 2888 // LongNameHandler when unitWidth is not FULL_NAME: 2889 // // Gender of per-second might be that of percent? TODO(icu-units#28) 2890 // new TestCase("de", "percent", "neuter"), // 2891 // new TestCase("fr", "percent", "masculine"), // 2892 2893 // Built-in units whose simple units lack gender in the CLDR data file 2894 new TestCase("de", "kilopascal", "neuter"), // 2895 new TestCase("fr", "kilopascal", "masculine"), // 2896 // new TestCase("de", "pascal", ""), // 2897 // new TestCase("fr", "pascal", ""), // 2898 2899 // Built-in units that lack gender in the CLDR data file 2900 // new TestCase("de", "revolution", ""), // 2901 // new TestCase("de", "radian", ""), // 2902 // new TestCase("de", "arc-minute", ""), // 2903 // new TestCase("de", "arc-second", ""), // 2904 new TestCase("de", "square-yard", "neuter"), // COMPOUND 2905 new TestCase("de", "square-inch", "masculine"), // COMPOUND 2906 // new TestCase("de", "dunam", ""), // 2907 // new TestCase("de", "karat", ""), // 2908 // new TestCase("de", "milligram-ofglucose-per-deciliter", ""), // COMPOUND, ofglucose 2909 // new TestCase("de", "millimole-per-liter", ""), // COMPOUND, mole 2910 // new TestCase("de", "permillion", ""), // 2911 // new TestCase("de", "permille", ""), // 2912 // new TestCase("de", "permyriad", ""), // 2913 // new TestCase("de", "mole", ""), // 2914 new TestCase("de", "liter-per-kilometer", "masculine"), // COMPOUND 2915 new TestCase("de", "petabyte", "neuter"), // PREFIX 2916 new TestCase("de", "terabit", "neuter"), // PREFIX 2917 // new TestCase("de", "century", ""), // 2918 // new TestCase("de", "decade", ""), // 2919 new TestCase("de", "millisecond", "feminine"), // PREFIX 2920 new TestCase("de", "microsecond", "feminine"), // PREFIX 2921 new TestCase("de", "nanosecond", "feminine"), // PREFIX 2922 // new TestCase("de", "ampere", ""), // 2923 // new TestCase("de", "milliampere", ""), // PREFIX, ampere 2924 // new TestCase("de", "ohm", ""), // 2925 // new TestCase("de", "calorie", ""), // 2926 // new TestCase("de", "kilojoule", ""), // PREFIX, joule 2927 // new TestCase("de", "joule", ""), // 2928 new TestCase("de", "kilowatt-hour", "feminine"), // COMPOUND 2929 // new TestCase("de", "electronvolt", ""), // 2930 // new TestCase("de", "british-thermal-unit", ""), // 2931 // new TestCase("de", "therm-us", ""), // 2932 // new TestCase("de", "pound-force", ""), // 2933 // new TestCase("de", "newton", ""), // 2934 // new TestCase("de", "gigahertz", ""), // PREFIX, hertz 2935 // new TestCase("de", "megahertz", ""), // PREFIX, hertz 2936 // new TestCase("de", "kilohertz", ""), // PREFIX, hertz 2937 // new TestCase("de", "hertz", ""), // PREFIX, hertz 2938 // new TestCase("de", "em", ""), // 2939 // new TestCase("de", "pixel", ""), // 2940 // new TestCase("de", "megapixel", ""), // 2941 // new TestCase("de", "pixel-per-centimeter", ""), // COMPOUND, pixel 2942 // new TestCase("de", "pixel-per-inch", ""), // COMPOUND, pixel 2943 // new TestCase("de", "dot-per-centimeter", ""), // COMPOUND, dot 2944 // new TestCase("de", "dot-per-inch", ""), // COMPOUND, dot 2945 // new TestCase("de", "dot", ""), // 2946 // new TestCase("de", "earth-radius", ""), // 2947 new TestCase("de", "decimeter", "masculine"), // PREFIX 2948 new TestCase("de", "micrometer", "masculine"), // PREFIX 2949 new TestCase("de", "nanometer", "masculine"), // PREFIX 2950 // new TestCase("de", "light-year", ""), // 2951 // new TestCase("de", "astronomical-unit", ""), // 2952 // new TestCase("de", "furlong", ""), // 2953 // new TestCase("de", "fathom", ""), // 2954 // new TestCase("de", "nautical-mile", ""), // 2955 // new TestCase("de", "mile-scandinavian", ""), // 2956 // new TestCase("de", "point", ""), // 2957 // new TestCase("de", "lux", ""), // 2958 // new TestCase("de", "candela", ""), // 2959 // new TestCase("de", "lumen", ""), // 2960 // new TestCase("de", "tonne", ""), // 2961 new TestCase("de", "microgram", "neuter"), // PREFIX 2962 // new TestCase("de", "ton", ""), // 2963 // new TestCase("de", "stone", ""), // 2964 // new TestCase("de", "ounce-troy", ""), // 2965 // new TestCase("de", "carat", ""), // 2966 new TestCase("de", "gigawatt", "neuter"), // PREFIX 2967 new TestCase("de", "milliwatt", "neuter"), // PREFIX 2968 // new TestCase("de", "horsepower", ""), // 2969 // new TestCase("de", "millimeter-ofhg", ""), // 2970 // new TestCase("de", "pound-force-per-square-inch", ""), // COMPOUND, pound-force 2971 // new TestCase("de", "inch-ofhg", ""), // 2972 // new TestCase("de", "bar", ""), // 2973 // new TestCase("de", "millibar", ""), // PREFIX, bar 2974 // new TestCase("de", "atmosphere", ""), // 2975 // new TestCase("de", "pascal", ""), // PREFIX, kilopascal? neuter? 2976 // new TestCase("de", "hectopascal", ""), // PREFIX, pascal, neuter? 2977 // new TestCase("de", "megapascal", ""), // PREFIX, pascal, neuter? 2978 // new TestCase("de", "knot", ""), // 2979 new TestCase("de", "pound-force-foot", "masculine"), // COMPOUND 2980 new TestCase("de", "newton-meter", "masculine"), // COMPOUND 2981 new TestCase("de", "cubic-kilometer", "masculine"), // POWER 2982 new TestCase("de", "cubic-yard", "neuter"), // POWER 2983 new TestCase("de", "cubic-inch", "masculine"), // POWER 2984 new TestCase("de", "megaliter", "masculine"), // PREFIX 2985 new TestCase("de", "hectoliter", "masculine"), // PREFIX 2986 // new TestCase("de", "pint-metric", ""), // 2987 // new TestCase("de", "cup-metric", ""), // 2988 new TestCase("de", "acre-foot", "masculine"), // COMPOUND 2989 // new TestCase("de", "bushel", ""), // 2990 // new TestCase("de", "barrel", ""), // 2991 // Units missing gender in German also misses gender in French: 2992 // new TestCase("fr", "revolution", ""), // 2993 // new TestCase("fr", "radian", ""), // 2994 // new TestCase("fr", "arc-minute", ""), // 2995 // new TestCase("fr", "arc-second", ""), // 2996 new TestCase("fr", "square-yard", "masculine"), // COMPOUND 2997 new TestCase("fr", "square-inch", "masculine"), // COMPOUND 2998 // new TestCase("fr", "dunam", ""), // 2999 // new TestCase("fr", "karat", ""), // 3000 new TestCase("fr", "milligram-ofglucose-per-deciliter", "masculine"), // COMPOUND 3001 // new TestCase("fr", "millimole-per-liter", ""), // COMPOUND, mole 3002 // new TestCase("fr", "permillion", ""), // 3003 // new TestCase("fr", "permille", ""), // 3004 // new TestCase("fr", "permyriad", ""), // 3005 // new TestCase("fr", "mole", ""), // 3006 new TestCase("fr", "liter-per-kilometer", "masculine"), // COMPOUND 3007 // new TestCase("fr", "petabyte", ""), // PREFIX 3008 // new TestCase("fr", "terabit", ""), // PREFIX 3009 // new TestCase("fr", "century", ""), // 3010 // new TestCase("fr", "decade", ""), // 3011 new TestCase("fr", "millisecond", "feminine"), // PREFIX 3012 new TestCase("fr", "microsecond", "feminine"), // PREFIX 3013 new TestCase("fr", "nanosecond", "feminine"), // PREFIX 3014 // new TestCase("fr", "ampere", ""), // 3015 // new TestCase("fr", "milliampere", ""), // PREFIX, ampere 3016 // new TestCase("fr", "ohm", ""), // 3017 // new TestCase("fr", "calorie", ""), // 3018 // new TestCase("fr", "kilojoule", ""), // PREFIX, joule 3019 // new TestCase("fr", "joule", ""), // 3020 // new TestCase("fr", "kilowatt-hour", ""), // COMPOUND 3021 // new TestCase("fr", "electronvolt", ""), // 3022 // new TestCase("fr", "british-thermal-unit", ""), // 3023 // new TestCase("fr", "therm-us", ""), // 3024 // new TestCase("fr", "pound-force", ""), // 3025 // new TestCase("fr", "newton", ""), // 3026 // new TestCase("fr", "gigahertz", ""), // PREFIX, hertz 3027 // new TestCase("fr", "megahertz", ""), // PREFIX, hertz 3028 // new TestCase("fr", "kilohertz", ""), // PREFIX, hertz 3029 // new TestCase("fr", "hertz", ""), // PREFIX, hertz 3030 // new TestCase("fr", "em", ""), // 3031 // new TestCase("fr", "pixel", ""), // 3032 // new TestCase("fr", "megapixel", ""), // 3033 // new TestCase("fr", "pixel-per-centimeter", ""), // COMPOUND, pixel 3034 // new TestCase("fr", "pixel-per-inch", ""), // COMPOUND, pixel 3035 // new TestCase("fr", "dot-per-centimeter", ""), // COMPOUND, dot 3036 // new TestCase("fr", "dot-per-inch", ""), // COMPOUND, dot 3037 // new TestCase("fr", "dot", ""), // 3038 // new TestCase("fr", "earth-radius", ""), // 3039 new TestCase("fr", "decimeter", "masculine"), // PREFIX 3040 new TestCase("fr", "micrometer", "masculine"), // PREFIX 3041 new TestCase("fr", "nanometer", "masculine"), // PREFIX 3042 // new TestCase("fr", "light-year", ""), // 3043 // new TestCase("fr", "astronomical-unit", ""), // 3044 // new TestCase("fr", "furlong", ""), // 3045 // new TestCase("fr", "fathom", ""), // 3046 // new TestCase("fr", "nautical-mile", ""), // 3047 // new TestCase("fr", "mile-scandinavian", ""), // 3048 // new TestCase("fr", "point", ""), // 3049 // new TestCase("fr", "lux", ""), // 3050 // new TestCase("fr", "candela", ""), // 3051 // new TestCase("fr", "lumen", ""), // 3052 // new TestCase("fr", "tonne", ""), // 3053 new TestCase("fr", "microgram", "masculine"), // PREFIX 3054 // new TestCase("fr", "ton", ""), // 3055 // new TestCase("fr", "stone", ""), // 3056 // new TestCase("fr", "ounce-troy", ""), // 3057 // new TestCase("fr", "carat", ""), // 3058 // new TestCase("fr", "gigawatt", ""), // PREFIX 3059 // new TestCase("fr", "milliwatt", ""), // 3060 // new TestCase("fr", "horsepower", ""), // 3061 new TestCase("fr", "millimeter-ofhg", "masculine"), // 3062 // new TestCase("fr", "pound-force-per-square-inch", ""), // COMPOUND, pound-force 3063 new TestCase("fr", "inch-ofhg", "masculine"), // 3064 // new TestCase("fr", "bar", ""), // 3065 // new TestCase("fr", "millibar", ""), // PREFIX, bar 3066 // new TestCase("fr", "atmosphere", ""), // 3067 // new TestCase("fr", "pascal", ""), // PREFIX, kilopascal? 3068 // new TestCase("fr", "hectopascal", ""), // PREFIX, pascal 3069 // new TestCase("fr", "megapascal", ""), // PREFIX, pascal 3070 // new TestCase("fr", "knot", ""), // 3071 // new TestCase("fr", "pound-force-foot", ""), // 3072 // new TestCase("fr", "newton-meter", ""), // 3073 new TestCase("fr", "cubic-kilometer", "masculine"), // POWER 3074 new TestCase("fr", "cubic-yard", "masculine"), // POWER 3075 new TestCase("fr", "cubic-inch", "masculine"), // POWER 3076 new TestCase("fr", "megaliter", "masculine"), // PREFIX 3077 new TestCase("fr", "hectoliter", "masculine"), // PREFIX 3078 // new TestCase("fr", "pint-metric", ""), // 3079 // new TestCase("fr", "cup-metric", ""), // 3080 new TestCase("fr", "acre-foot", "feminine"), // COMPOUND 3081 // new TestCase("fr", "bushel", ""), // 3082 // new TestCase("fr", "barrel", ""), // 3083 // Some more French units missing gender: 3084 // new TestCase("fr", "degree", ""), // 3085 new TestCase("fr", "square-meter", "masculine"), // COMPOUND 3086 // new TestCase("fr", "terabyte", ""), // PREFIX, byte 3087 // new TestCase("fr", "gigabyte", ""), // PREFIX, byte 3088 // new TestCase("fr", "gigabit", ""), // PREFIX, bit 3089 // new TestCase("fr", "megabyte", ""), // PREFIX, byte 3090 // new TestCase("fr", "megabit", ""), // PREFIX, bit 3091 // new TestCase("fr", "kilobyte", ""), // PREFIX, byte 3092 // new TestCase("fr", "kilobit", ""), // PREFIX, bit 3093 // new TestCase("fr", "byte", ""), // 3094 // new TestCase("fr", "bit", ""), // 3095 // new TestCase("fr", "volt", ""), // 3096 new TestCase("fr", "cubic-meter", "masculine"), // POWER 3097 3098 // gender-lacking builtins within compound units 3099 new TestCase("de", "newton-meter-per-second", "masculine"), 3100 3101 // TODO(ICU-21494): determine whether list genders behave as follows, 3102 // and implement proper getListGender support (covering more than just 3103 // two genders): 3104 // // gender rule for lists of people: de "neutral", fr "maleTaints" 3105 // new TestCase("de", "day-and-hour-and-minute", "neuter"), 3106 // new TestCase("de", "hour-and-minute", "feminine"), 3107 // new TestCase("fr", "day-and-hour-and-minute", "masculine"), 3108 // new TestCase("fr", "hour-and-minute", "feminine"), 3109 }; 3110 3111 LocalizedNumberFormatter formatter; 3112 FormattedNumber fn; 3113 for (TestCase t : cases) { 3114 formatter = NumberFormatter.with() 3115 .unit(MeasureUnit.forIdentifier(t.unitIdentifier)) 3116 .locale(new ULocale(t.locale)); 3117 fn = formatter.format(1.1); 3118 assertEquals("Testing gender with default width, unit: " + t.unitIdentifier + 3119 ", locale: " + t.locale, 3120 t.expectedGender, fn.getGender()); 3121 3122 formatter = NumberFormatter.with() 3123 .unit(MeasureUnit.forIdentifier(t.unitIdentifier)) 3124 .unitWidth(UnitWidth.FULL_NAME) 3125 .locale(new ULocale(t.locale)); 3126 fn = formatter.format(1.1); 3127 assertEquals("Testing gender with UnitWidth.FULL_NAME, unit: " + t.unitIdentifier + 3128 ", locale: " + t.locale, 3129 t.expectedGender, fn.getGender()); 3130 } 3131 3132 // Make sure getGender does not return garbage for genderless languages 3133 formatter = NumberFormatter.with().locale(ULocale.ENGLISH); 3134 fn = formatter.format(1.1); 3135 assertEquals("getGender for a genderless language", "", fn.getGender()); 3136 } 3137 3138 @Test unitNotConvertible()3139 public void unitNotConvertible() { 3140 final double randomNumber = 1234; 3141 3142 try { 3143 NumberFormatter.with() 3144 .unit(MeasureUnit.forIdentifier("meter-and-liter")) 3145 .locale(new ULocale("en_US")) 3146 .format(randomNumber); 3147 } catch (Exception e) { 3148 assertEquals("error must be thrown", "class com.ibm.icu.impl.IllegalIcuArgumentException", e.getClass().toString()); 3149 } 3150 3151 try { 3152 NumberFormatter.with() 3153 .unit(MeasureUnit.forIdentifier("month-and-week")) 3154 .locale(new ULocale("en_US")) 3155 .format(randomNumber); 3156 } catch (Exception e) { 3157 assertEquals("error must be thrown", "class com.ibm.icu.impl.IllegalIcuArgumentException", e.getClass().toString()); 3158 } 3159 3160 try { 3161 NumberFormatter.with() 3162 .unit(MeasureUnit.forIdentifier("day-and-hour")) 3163 .locale(new ULocale("en_US")) 3164 .format(2.5); 3165 } catch (Exception e) { 3166 // No errors. 3167 assert false; 3168 } 3169 3170 } 3171 3172 @Test unitPercent()3173 public void unitPercent() { 3174 assertFormatDescending( 3175 "Percent", 3176 "percent", 3177 "%", 3178 NumberFormatter.with().unit(NoUnit.PERCENT), 3179 ULocale.ENGLISH, 3180 "87,650%", 3181 "8,765%", 3182 "876.5%", 3183 "87.65%", 3184 "8.765%", 3185 "0.8765%", 3186 "0.08765%", 3187 "0.008765%", 3188 "0%"); 3189 3190 assertFormatDescending( 3191 "Permille", 3192 "permille", 3193 "permille", 3194 NumberFormatter.with().unit(NoUnit.PERMILLE), 3195 ULocale.ENGLISH, 3196 "87,650‰", 3197 "8,765‰", 3198 "876.5‰", 3199 "87.65‰", 3200 "8.765‰", 3201 "0.8765‰", 3202 "0.08765‰", 3203 "0.008765‰", 3204 "0‰"); 3205 3206 assertFormatSingle( 3207 "NoUnit Base", 3208 "base-unit", 3209 "", 3210 NumberFormatter.with().unit(NoUnit.BASE), 3211 ULocale.ENGLISH, 3212 51423, 3213 "51,423"); 3214 3215 assertFormatSingle( 3216 "Percent with Negative Sign", 3217 "percent", 3218 "%", 3219 NumberFormatter.with().unit(NoUnit.PERCENT), 3220 ULocale.ENGLISH, 3221 -98.7654321, 3222 "-98.765432%"); 3223 3224 // ICU-20923 3225 assertFormatDescendingBig( 3226 "Compact Percent", 3227 "compact-short percent", 3228 "K %", 3229 NumberFormatter.with() 3230 .notation(Notation.compactShort()) 3231 .unit(NoUnit.PERCENT), 3232 ULocale.ENGLISH, 3233 "88M%", 3234 "8.8M%", 3235 "876K%", 3236 "88K%", 3237 "8.8K%", 3238 "876%", 3239 "88%", 3240 "8.8%", 3241 "0%"); 3242 3243 // ICU-20923 3244 assertFormatDescendingBig( 3245 "Compact Percent with Scale", 3246 "compact-short percent scale/100", 3247 "K %x100", 3248 NumberFormatter.with() 3249 .notation(Notation.compactShort()) 3250 .unit(NoUnit.PERCENT) 3251 .scale(Scale.powerOfTen(2)), 3252 ULocale.ENGLISH, 3253 "8.8B%", 3254 "876M%", 3255 "88M%", 3256 "8.8M%", 3257 "876K%", 3258 "88K%", 3259 "8.8K%", 3260 "876%", 3261 "0%"); 3262 3263 // ICU-20923 3264 assertFormatDescendingBig( 3265 "Compact Percent Long Name", 3266 "compact-short percent unit-width-full-name", 3267 "K % unit-width-full-name", 3268 NumberFormatter.with() 3269 .notation(Notation.compactShort()) 3270 .unit(NoUnit.PERCENT) 3271 .unitWidth(UnitWidth.FULL_NAME), 3272 ULocale.ENGLISH, 3273 "88M percent", 3274 "8.8M percent", 3275 "876K percent", 3276 "88K percent", 3277 "8.8K percent", 3278 "876 percent", 3279 "88 percent", 3280 "8.8 percent", 3281 "0 percent"); 3282 3283 assertFormatSingle( 3284 "Per Percent", 3285 "measure-unit/length-meter per-measure-unit/concentr-percent unit-width-full-name", 3286 "measure-unit/length-meter per-measure-unit/concentr-percent unit-width-full-name", 3287 NumberFormatter.with() 3288 .unit(MeasureUnit.METER) 3289 .perUnit(MeasureUnit.PERCENT) 3290 .unitWidth(UnitWidth.FULL_NAME), 3291 ULocale.ENGLISH, 3292 50, 3293 "50 meters per percent"); 3294 3295 } 3296 3297 @Test roundingFraction()3298 public void roundingFraction() { 3299 assertFormatDescending( 3300 "Integer", 3301 "precision-integer", 3302 ".", 3303 NumberFormatter.with().precision(Precision.integer()), 3304 ULocale.ENGLISH, 3305 "87,650", 3306 "8,765", 3307 "876", 3308 "88", 3309 "9", 3310 "1", 3311 "0", 3312 "0", 3313 "0"); 3314 3315 assertFormatDescending( 3316 "Fixed Fraction", 3317 ".000", 3318 ".000", 3319 NumberFormatter.with().precision(Precision.fixedFraction(3)), 3320 ULocale.ENGLISH, 3321 "87,650.000", 3322 "8,765.000", 3323 "876.500", 3324 "87.650", 3325 "8.765", 3326 "0.876", 3327 "0.088", 3328 "0.009", 3329 "0.000"); 3330 3331 assertFormatDescending( 3332 "Min Fraction", 3333 ".0*", 3334 ".0+", 3335 NumberFormatter.with().precision(Precision.minFraction(1)), 3336 ULocale.ENGLISH, 3337 "87,650.0", 3338 "8,765.0", 3339 "876.5", 3340 "87.65", 3341 "8.765", 3342 "0.8765", 3343 "0.08765", 3344 "0.008765", 3345 "0.0"); 3346 3347 assertFormatDescending( 3348 "Max Fraction", 3349 ".#", 3350 ".#", 3351 NumberFormatter.with().precision(Precision.maxFraction(1)), 3352 ULocale.ENGLISH, 3353 "87,650", 3354 "8,765", 3355 "876.5", 3356 "87.6", 3357 "8.8", 3358 "0.9", 3359 "0.1", 3360 "0", 3361 "0"); 3362 3363 assertFormatDescending( 3364 "Min/Max Fraction", 3365 ".0##", 3366 ".0##", 3367 NumberFormatter.with().precision(Precision.minMaxFraction(1, 3)), 3368 ULocale.ENGLISH, 3369 "87,650.0", 3370 "8,765.0", 3371 "876.5", 3372 "87.65", 3373 "8.765", 3374 "0.876", 3375 "0.088", 3376 "0.009", 3377 "0.0"); 3378 3379 assertFormatSingle( 3380 "Hide If Whole A", 3381 ".00/w", 3382 ".00/w", 3383 NumberFormatter.with().precision(Precision.fixedFraction(2) 3384 .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)), 3385 ULocale.ENGLISH, 3386 1.2, 3387 "1.20"); 3388 3389 assertFormatSingle( 3390 "Hide If Whole B", 3391 ".00/w", 3392 ".00/w", 3393 NumberFormatter.with().precision(Precision.fixedFraction(2) 3394 .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)), 3395 ULocale.ENGLISH, 3396 1, 3397 "1"); 3398 3399 assertFormatSingle( 3400 "Hide If Whole with Rounding Mode A (ICU-21881)", 3401 ".00/w rounding-mode-floor", 3402 ".00/w rounding-mode-floor", 3403 NumberFormatter.with().precision(Precision.fixedFraction(2) 3404 .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)) 3405 .roundingMode(RoundingMode.FLOOR), 3406 ULocale.ENGLISH, 3407 3.009, 3408 "3"); 3409 3410 assertFormatSingle( 3411 "Hide If Whole with Rounding Mode B (ICU-21881)", 3412 ".00/w rounding-mode-half-up", 3413 ".00/w rounding-mode-half-up", 3414 NumberFormatter.with().precision(Precision.fixedFraction(2) 3415 .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)) 3416 .roundingMode(RoundingMode.HALF_UP), 3417 ULocale.ENGLISH, 3418 3.001, 3419 "3"); 3420 } 3421 3422 @Test roundingFigures()3423 public void roundingFigures() { 3424 assertFormatSingle( 3425 "Fixed Significant", 3426 "@@@", 3427 "@@@", 3428 NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)), 3429 ULocale.ENGLISH, 3430 -98, 3431 "-98.0"); 3432 3433 assertFormatSingle( 3434 "Fixed Significant Rounding", 3435 "@@@", 3436 "@@@", 3437 NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)), 3438 ULocale.ENGLISH, 3439 -98.7654321, 3440 "-98.8"); 3441 3442 assertFormatSingle( 3443 "Fixed Significant at rounding boundary", 3444 "@@@", 3445 "@@@", 3446 NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)), 3447 ULocale.ENGLISH, 3448 9.999, 3449 "10.0"); 3450 3451 assertFormatSingle( 3452 "Fixed Significant Zero", 3453 "@@@", 3454 "@@@", 3455 NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)), 3456 ULocale.ENGLISH, 3457 0, 3458 "0.00"); 3459 3460 assertFormatSingle( 3461 "Min Significant", 3462 "@@*", 3463 "@@+", 3464 NumberFormatter.with().precision(Precision.minSignificantDigits(2)), 3465 ULocale.ENGLISH, 3466 -9, 3467 "-9.0"); 3468 3469 assertFormatSingle( 3470 "Max Significant", 3471 "@###", 3472 "@###", 3473 NumberFormatter.with().precision(Precision.maxSignificantDigits(4)), 3474 ULocale.ENGLISH, 3475 98.7654321, 3476 "98.77"); 3477 3478 assertFormatSingle( 3479 "Min/Max Significant", 3480 "@@@#", 3481 "@@@#", 3482 NumberFormatter.with().precision(Precision.minMaxSignificantDigits(3, 4)), 3483 ULocale.ENGLISH, 3484 9.99999, 3485 "10.0"); 3486 3487 assertFormatSingle( 3488 "Fixed Significant on zero with zero integer width", 3489 "@ integer-width/*", 3490 "@ integer-width/+", 3491 NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(0)), 3492 ULocale.ENGLISH, 3493 0, 3494 "0"); 3495 3496 assertFormatSingle( 3497 "Fixed Significant on zero with lots of integer width", 3498 "@ integer-width/+000", 3499 "@ 000", 3500 NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(3)), 3501 ULocale.ENGLISH, 3502 0, 3503 "000"); 3504 } 3505 3506 @Test roundingFractionFigures()3507 public void roundingFractionFigures() { 3508 assertFormatDescending( 3509 "Basic Significant", // for comparison 3510 "@#", 3511 "@#", 3512 NumberFormatter.with().precision(Precision.maxSignificantDigits(2)), 3513 ULocale.ENGLISH, 3514 "88,000", 3515 "8,800", 3516 "880", 3517 "88", 3518 "8.8", 3519 "0.88", 3520 "0.088", 3521 "0.0088", 3522 "0"); 3523 3524 assertFormatDescending( 3525 "FracSig minMaxFrac minSig", 3526 ".0#/@@@*", 3527 ".0#/@@@+", 3528 NumberFormatter.with().precision(Precision.minMaxFraction(1, 2).withMinDigits(3)), 3529 ULocale.ENGLISH, 3530 "87,650.0", 3531 "8,765.0", 3532 "876.5", 3533 "87.65", 3534 "8.76", 3535 "0.876", // minSig beats maxFrac 3536 "0.0876", // minSig beats maxFrac 3537 "0.00876", // minSig beats maxFrac 3538 "0.0"); 3539 3540 assertFormatDescending( 3541 "FracSig minMaxFrac maxSig A", 3542 ".0##/@#", 3543 ".0##/@#", 3544 NumberFormatter.with().precision(Precision.minMaxFraction(1, 3).withMaxDigits(2)), 3545 ULocale.ENGLISH, 3546 "88,000.0", // maxSig beats maxFrac 3547 "8,800.0", // maxSig beats maxFrac 3548 "880.0", // maxSig beats maxFrac 3549 "88.0", // maxSig beats maxFrac 3550 "8.8", // maxSig beats maxFrac 3551 "0.88", // maxSig beats maxFrac 3552 "0.088", 3553 "0.009", 3554 "0.0"); 3555 3556 assertFormatDescending( 3557 "FracSig minMaxFrac maxSig B", 3558 ".00/@#", 3559 ".00/@#", 3560 NumberFormatter.with().precision(Precision.fixedFraction(2).withMaxDigits(2)), 3561 ULocale.ENGLISH, 3562 "88,000.00", // maxSig beats maxFrac 3563 "8,800.00", // maxSig beats maxFrac 3564 "880.00", // maxSig beats maxFrac 3565 "88.00", // maxSig beats maxFrac 3566 "8.80", // maxSig beats maxFrac 3567 "0.88", 3568 "0.09", 3569 "0.01", 3570 "0.00"); 3571 3572 assertFormatDescending( 3573 "FracSig minFrac maxSig", 3574 ".0+/@#", 3575 ".0+/@#", 3576 NumberFormatter.with().precision(Precision.minFraction(1).withMaxDigits(2)), 3577 ULocale.ENGLISH, 3578 "88,000.0", 3579 "8,800.0", 3580 "880.0", 3581 "88.0", 3582 "8.8", 3583 "0.88", 3584 "0.088", 3585 "0.0088", 3586 "0.0"); 3587 3588 assertFormatSingle( 3589 "FracSig with trailing zeros A", 3590 ".00/@@@*", 3591 ".00/@@@+", 3592 NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)), 3593 ULocale.ENGLISH, 3594 0.1, 3595 "0.10"); 3596 3597 assertFormatSingle( 3598 "FracSig with trailing zeros B", 3599 ".00/@@@*", 3600 ".00/@@@+", 3601 NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)), 3602 ULocale.ENGLISH, 3603 0.0999999, 3604 "0.10"); 3605 3606 assertFormatDescending( 3607 "FracSig withSignificantDigits RELAXED", 3608 "precision-integer/@#r", 3609 "./@#r", 3610 NumberFormatter.with().precision(Precision.maxFraction(0) 3611 .withSignificantDigits(1, 2, RoundingPriority.RELAXED)), 3612 ULocale.ENGLISH, 3613 "87,650", 3614 "8,765", 3615 "876", 3616 "88", 3617 "8.8", 3618 "0.88", 3619 "0.088", 3620 "0.0088", 3621 "0"); 3622 3623 assertFormatDescending( 3624 "FracSig withSignificantDigits STRICT", 3625 "precision-integer/@#s", 3626 "./@#s", 3627 NumberFormatter.with().precision(Precision.maxFraction(0) 3628 .withSignificantDigits(1, 2, RoundingPriority.STRICT)), 3629 ULocale.ENGLISH, 3630 "88,000", 3631 "8,800", 3632 "880", 3633 "88", 3634 "9", 3635 "1", 3636 "0", 3637 "0", 3638 "0"); 3639 3640 assertFormatSingle( 3641 "FracSig withSignificantDigits Trailing Zeros RELAXED", 3642 ".0/@@@r", 3643 ".0/@@@r", 3644 NumberFormatter.with().precision(Precision.fixedFraction(1) 3645 .withSignificantDigits(3, 3, RoundingPriority.RELAXED)), 3646 ULocale.ENGLISH, 3647 1, 3648 "1.00"); 3649 3650 // Trailing zeros follow the strategy that was chosen: 3651 assertFormatSingle( 3652 "FracSig withSignificantDigits Trailing Zeros STRICT", 3653 ".0/@@@s", 3654 ".0/@@@s", 3655 NumberFormatter.with().precision(Precision.fixedFraction(1) 3656 .withSignificantDigits(3, 3, RoundingPriority.STRICT)), 3657 ULocale.ENGLISH, 3658 1, 3659 "1.0"); 3660 3661 assertFormatSingle( 3662 "FracSig withSignificantDigits at rounding boundary", 3663 "precision-integer/@@@s", 3664 "./@@@s", 3665 NumberFormatter.with().precision(Precision.fixedFraction(0) 3666 .withSignificantDigits(3, 3, RoundingPriority.STRICT)), 3667 ULocale.ENGLISH, 3668 9.99, 3669 "10"); 3670 3671 assertFormatSingle( 3672 "FracSig with Trailing Zero Display", 3673 ".00/@@@*/w", 3674 ".00/@@@+/w", 3675 NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3) 3676 .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)), 3677 ULocale.ENGLISH, 3678 1, 3679 "1"); 3680 } 3681 3682 @Test roundingOther()3683 public void roundingOther() { 3684 assertFormatDescending( 3685 "Rounding None", 3686 "precision-unlimited", 3687 ".+", 3688 NumberFormatter.with().precision(Precision.unlimited()), 3689 ULocale.ENGLISH, 3690 "87,650", 3691 "8,765", 3692 "876.5", 3693 "87.65", 3694 "8.765", 3695 "0.8765", 3696 "0.08765", 3697 "0.008765", 3698 "0"); 3699 3700 assertFormatDescending( 3701 "Increment", 3702 "precision-increment/0.5", 3703 "precision-increment/0.5", 3704 NumberFormatter.with().precision(Precision.increment(BigDecimal.valueOf(0.5))), 3705 ULocale.ENGLISH, 3706 "87,650.0", 3707 "8,765.0", 3708 "876.5", 3709 "87.5", 3710 "9.0", 3711 "1.0", 3712 "0.0", 3713 "0.0", 3714 "0.0"); 3715 3716 assertFormatDescending( 3717 "Increment with Min Fraction", 3718 "precision-increment/0.50", 3719 "precision-increment/0.50", 3720 NumberFormatter.with().precision(Precision.increment(new BigDecimal("0.50"))), 3721 ULocale.ENGLISH, 3722 "87,650.00", 3723 "8,765.00", 3724 "876.50", 3725 "87.50", 3726 "9.00", 3727 "1.00", 3728 "0.00", 3729 "0.00", 3730 "0.00"); 3731 3732 assertFormatDescending( 3733 "Strange Increment", 3734 "precision-increment/3.140", 3735 "precision-increment/3.140", 3736 NumberFormatter.with().precision(Precision.increment(new BigDecimal("3.140"))), 3737 ULocale.ENGLISH, 3738 "87,649.960", 3739 "8,763.740", 3740 "876.060", 3741 "87.920", 3742 "9.420", 3743 "0.000", 3744 "0.000", 3745 "0.000", 3746 "0.000"); 3747 3748 assertFormatDescending( 3749 "Medium nickel increment with rounding mode ceiling (ICU-21668)", 3750 "precision-increment/50 rounding-mode-ceiling", 3751 "precision-increment/50 rounding-mode-ceiling", 3752 NumberFormatter.with() 3753 .precision(Precision.increment(new BigDecimal("50"))) 3754 .roundingMode(RoundingMode.CEILING), 3755 ULocale.ENGLISH, 3756 "87,650", 3757 "8,800", 3758 "900", 3759 "100", 3760 "50", 3761 "50", 3762 "50", 3763 "50", 3764 "0"); 3765 3766 assertFormatDescending( 3767 "Large nickel increment with rounding mode up (ICU-21668)", 3768 "precision-increment/5000 rounding-mode-up", 3769 "precision-increment/5000 rounding-mode-up", 3770 NumberFormatter.with() 3771 .precision(Precision.increment(new BigDecimal("5000"))) 3772 .roundingMode(RoundingMode.UP), 3773 ULocale.ENGLISH, 3774 "90,000", 3775 "10,000", 3776 "5,000", 3777 "5,000", 3778 "5,000", 3779 "5,000", 3780 "5,000", 3781 "5,000", 3782 "0"); 3783 3784 assertFormatDescending( 3785 "Large dime increment with rounding mode up (ICU-21668)", 3786 "precision-increment/10000 rounding-mode-up", 3787 "precision-increment/10000 rounding-mode-up", 3788 NumberFormatter.with() 3789 .precision(Precision.increment(new BigDecimal("10000"))) 3790 .roundingMode(RoundingMode.UP), 3791 ULocale.ENGLISH, 3792 "90,000", 3793 "10,000", 3794 "10,000", 3795 "10,000", 3796 "10,000", 3797 "10,000", 3798 "10,000", 3799 "10,000", 3800 "0"); 3801 3802 assertFormatDescending( 3803 "Large non-nickel increment with rounding mode up (ICU-21668)", 3804 "precision-increment/15000 rounding-mode-up", 3805 "precision-increment/15000 rounding-mode-up", 3806 NumberFormatter.with() 3807 .precision(Precision.increment(new BigDecimal("15000"))) 3808 .roundingMode(RoundingMode.UP), 3809 ULocale.ENGLISH, 3810 "90,000", 3811 "15,000", 3812 "15,000", 3813 "15,000", 3814 "15,000", 3815 "15,000", 3816 "15,000", 3817 "15,000", 3818 "0"); 3819 3820 assertFormatDescending( 3821 "Increment Resolving to Power of 10", 3822 "precision-increment/0.010", 3823 "precision-increment/0.010", 3824 NumberFormatter.with().precision(Precision.increment(new BigDecimal("0.010"))), 3825 ULocale.ENGLISH, 3826 "87,650.000", 3827 "8,765.000", 3828 "876.500", 3829 "87.650", 3830 "8.760", 3831 "0.880", 3832 "0.090", 3833 "0.010", 3834 "0.000"); 3835 3836 assertFormatDescending( 3837 "Integer increment with trailing zeros (ICU-21654)", 3838 "precision-increment/50", 3839 "precision-increment/50", 3840 NumberFormatter.with().precision(Precision.increment(new BigDecimal("50"))), 3841 ULocale.ENGLISH, 3842 "87,650", 3843 "8,750", 3844 "900", 3845 "100", 3846 "0", 3847 "0", 3848 "0", 3849 "0", 3850 "0"); 3851 3852 assertFormatDescending( 3853 "Integer increment with minFraction (ICU-21654)", 3854 "precision-increment/5.0", 3855 "precision-increment/5.0", 3856 NumberFormatter.with().precision(Precision.increment(new BigDecimal("5.0"))), 3857 ULocale.ENGLISH, 3858 "87,650.0", 3859 "8,765.0", 3860 "875.0", 3861 "90.0", 3862 "10.0", 3863 "0.0", 3864 "0.0", 3865 "0.0", 3866 "0.0"); 3867 3868 assertFormatSingle( 3869 "Large integer increment", 3870 "precision-increment/24000000000000000000000", 3871 "precision-increment/24000000000000000000000", 3872 NumberFormatter.with().precision(Precision.increment(new BigDecimal("24e21"))), 3873 ULocale.ENGLISH, 3874 3.1e22, 3875 "24,000,000,000,000,000,000,000"); 3876 3877 assertFormatSingle( 3878 "Quarter rounding", 3879 "precision-increment/250", 3880 "precision-increment/250", 3881 NumberFormatter.with().precision(Precision.increment(new BigDecimal("250"))), 3882 ULocale.ENGLISH, 3883 700, 3884 "750"); 3885 3886 assertFormatSingle( 3887 "ECMA-402 limit", 3888 "precision-increment/.00000000000000000020", 3889 "precision-increment/.00000000000000000020", 3890 NumberFormatter.with().precision(Precision.increment(new BigDecimal("20e-20"))), 3891 ULocale.ENGLISH, 3892 333e-20, 3893 "0.00000000000000000340"); 3894 3895 assertFormatSingle( 3896 "ECMA-402 limit with increment = 1", 3897 "precision-increment/.00000000000000000001", 3898 "precision-increment/.00000000000000000001", 3899 NumberFormatter.with().precision(Precision.increment(new BigDecimal("1e-20"))), 3900 ULocale.ENGLISH, 3901 4321e-21, 3902 "0.00000000000000000432"); 3903 3904 assertFormatDescending( 3905 "Currency Standard", 3906 "currency/CZK precision-currency-standard", 3907 "currency/CZK precision-currency-standard", 3908 NumberFormatter.with().precision(Precision.currency(CurrencyUsage.STANDARD)).unit(CZK), 3909 ULocale.ENGLISH, 3910 "CZK 87,650.00", 3911 "CZK 8,765.00", 3912 "CZK 876.50", 3913 "CZK 87.65", 3914 "CZK 8.76", 3915 "CZK 0.88", 3916 "CZK 0.09", 3917 "CZK 0.01", 3918 "CZK 0.00"); 3919 3920 assertFormatDescending( 3921 "Currency Cash", 3922 "currency/CZK precision-currency-cash", 3923 "currency/CZK precision-currency-cash", 3924 NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH)).unit(CZK), 3925 ULocale.ENGLISH, 3926 "CZK 87,650", 3927 "CZK 8,765", 3928 "CZK 876", 3929 "CZK 88", 3930 "CZK 9", 3931 "CZK 1", 3932 "CZK 0", 3933 "CZK 0", 3934 "CZK 0"); 3935 3936 assertFormatDescending( 3937 "Currency Standard with Trailing Zero Display", 3938 "currency/CZK precision-currency-standard/w", 3939 "currency/CZK precision-currency-standard/w", 3940 NumberFormatter.with().precision( 3941 Precision.currency(CurrencyUsage.STANDARD) 3942 .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)) 3943 .unit(CZK), 3944 ULocale.ENGLISH, 3945 "CZK 87,650", 3946 "CZK 8,765", 3947 "CZK 876.50", 3948 "CZK 87.65", 3949 "CZK 8.76", 3950 "CZK 0.88", 3951 "CZK 0.09", 3952 "CZK 0.01", 3953 "CZK 0"); 3954 3955 assertFormatDescending( 3956 "Currency Cash with Nickel Rounding", 3957 "currency/CAD precision-currency-cash", 3958 "currency/CAD precision-currency-cash", 3959 NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH)).unit(CAD), 3960 ULocale.ENGLISH, 3961 "CA$87,650.00", 3962 "CA$8,765.00", 3963 "CA$876.50", 3964 "CA$87.65", 3965 "CA$8.75", 3966 "CA$0.90", 3967 "CA$0.10", 3968 "CA$0.00", 3969 "CA$0.00"); 3970 3971 assertFormatDescending( 3972 "Currency not in top-level fluent chain", 3973 "precision-integer", // calling .withCurrency() applies currency rounding rules immediately 3974 ".", 3975 NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH).withCurrency(CZK)), 3976 ULocale.ENGLISH, 3977 "87,650", 3978 "8,765", 3979 "876", 3980 "88", 3981 "9", 3982 "1", 3983 "0", 3984 "0", 3985 "0"); 3986 3987 // NOTE: Other tests cover the behavior of the other rounding modes. 3988 assertFormatDescending( 3989 "Rounding Mode CEILING", 3990 "precision-integer rounding-mode-ceiling", 3991 ". rounding-mode-ceiling", 3992 NumberFormatter.with().precision(Precision.integer()).roundingMode(RoundingMode.CEILING), 3993 ULocale.ENGLISH, 3994 "87,650", 3995 "8,765", 3996 "877", 3997 "88", 3998 "9", 3999 "1", 4000 "1", 4001 "1", 4002 "0"); 4003 4004 assertFormatSingle( 4005 "ICU-20974 Double.MIN_NORMAL", 4006 "scientific", 4007 "E0", 4008 NumberFormatter.with().notation(Notation.scientific()), 4009 ULocale.ENGLISH, 4010 Double.MIN_NORMAL, 4011 "2.225074E-308"); 4012 4013 assertFormatSingle( 4014 "ICU-20974 Double.MIN_VALUE", 4015 "scientific", 4016 "E0", 4017 NumberFormatter.with().notation(Notation.scientific()), 4018 ULocale.ENGLISH, 4019 Double.MIN_VALUE, 4020 "4.9E-324"); 4021 } 4022 4023 @Test roundingIncrementRegressionTest()4024 public void roundingIncrementRegressionTest() { 4025 ULocale locale = ULocale.ENGLISH; 4026 4027 for (int min_fraction_digits = 1; min_fraction_digits < 8; min_fraction_digits++) { 4028 // pattern is a snprintf pattern string like "precision-increment/%.5f" 4029 String pattern = String.format("precision-increment/%%.%df", min_fraction_digits); 4030 double increment = 0.05; 4031 for (int i = 0; i < 8 ; i++, increment *= 10.0) { 4032 BigDecimal bdIncrement; 4033 if (increment == 0.05 && min_fraction_digits == 1) { 4034 // Special case when the number of fraction digits is too low: 4035 bdIncrement = new BigDecimal("0.05"); 4036 } else { 4037 bdIncrement = BigDecimal.valueOf(increment).setScale(min_fraction_digits); 4038 } 4039 UnlocalizedNumberFormatter f = 4040 NumberFormatter.with().precision( 4041 Precision.increment(bdIncrement)); 4042 LocalizedNumberFormatter l = f.locale(locale); 4043 4044 String skeleton = f.toSkeleton(); 4045 4046 String message = String.format( 4047 "ICU-21654: Precision::increment(%.5f).withMinFraction(%d) '%s'\n", 4048 increment, min_fraction_digits, 4049 skeleton); 4050 4051 if (increment == 0.05 && min_fraction_digits == 1) { 4052 // Special case when the number of fraction digits is too low: 4053 // Precision::increment(0.05000).withMinFraction(1) 'precision-increment/0.05' 4054 assertEquals(message, "precision-increment/0.05", skeleton); 4055 } else { 4056 // All other cases: use the snprintf pattern computed above: 4057 // Precision::increment(0.50000).withMinFraction(1) 'precision-increment/0.5' 4058 // Precision::increment(5.00000).withMinFraction(1) 'precision-increment/5.0' 4059 // Precision::increment(50.00000).withMinFraction(1) 'precision-increment/50.0' 4060 // ... 4061 // Precision::increment(0.05000).withMinFraction(2) 'precision-increment/0.05' 4062 // Precision::increment(0.50000).withMinFraction(2) 'precision-increment/0.50' 4063 // Precision::increment(5.00000).withMinFraction(2) 'precision-increment/5.00' 4064 // ... 4065 4066 String expected = String.format(pattern, increment); 4067 assertEquals(message, expected, skeleton); 4068 } 4069 } 4070 } 4071 4072 String increment = NumberFormatter.with() 4073 .precision(Precision.increment(new BigDecimal("5000"))) 4074 .roundingMode(RoundingMode.UP) 4075 .locale(ULocale.ENGLISH) 4076 .format(5.625) 4077 .toString(); 4078 assertEquals("ICU-21668", "5,000", increment); 4079 } 4080 4081 static interface RoundingPriorityCheckFn { check(String name, String expected, Precision precision)4082 void check(String name, String expected, Precision precision); 4083 } 4084 4085 @Test roundingPriorityCoverageTest()4086 public void roundingPriorityCoverageTest() { 4087 String[][] cases = new String[][] { 4088 // Input, relaxed 0113, strict 0113, relaxed 1133, strict 1133 4089 { "0.9999", "1", "1", "1.00", "1.0" }, 4090 { "9.9999", "10", "10", "10.0", "10.0" }, 4091 { "99.999", "100", "100", "100.0", "100" }, 4092 { "999.99", "1000", "1000", "1000.0", "1000" }, 4093 4094 { "0", "0", "0", "0.00", "0.0" }, 4095 4096 { "9.876", "9.88", "9.9", "9.88", "9.9" }, 4097 { "9.001", "9", "9", "9.00", "9.0" }, 4098 }; 4099 for (String[] cas : cases) { 4100 final double input = Double.parseDouble(cas[0]); 4101 String expectedRelaxed0113 = cas[1]; 4102 String expectedStrict0113 = cas[2]; 4103 String expectedRelaxed1133 = cas[3]; 4104 String expectedStrict1133 = cas[4]; 4105 4106 Precision precisionRelaxed0113 = Precision.minMaxFraction(0, 1) 4107 .withSignificantDigits(1, 3, RoundingPriority.RELAXED); 4108 Precision precisionStrict0113 = Precision.minMaxFraction(0, 1) 4109 .withSignificantDigits(1, 3, RoundingPriority.STRICT); 4110 Precision precisionRelaxed1133 = Precision.minMaxFraction(1, 1) 4111 .withSignificantDigits(3, 3, RoundingPriority.RELAXED); 4112 Precision precisionStrict1133 = Precision.minMaxFraction(1, 1) 4113 .withSignificantDigits(3, 3, RoundingPriority.STRICT); 4114 4115 final String messageBase = cas[0]; 4116 4117 RoundingPriorityCheckFn checker = new RoundingPriorityCheckFn() { 4118 @Override 4119 public void check(String name, String expected, Precision precision) { 4120 assertEquals( 4121 messageBase + name, 4122 expected, 4123 NumberFormatter.withLocale(ULocale.ENGLISH) 4124 .precision(precision) 4125 .grouping(GroupingStrategy.OFF) 4126 .format(input) 4127 .toString() 4128 ); 4129 } 4130 }; 4131 4132 checker.check(" Relaxed 0113", expectedRelaxed0113, precisionRelaxed0113); 4133 4134 checker.check(" Strict 0113", expectedStrict0113, precisionStrict0113); 4135 4136 checker.check(" Relaxed 1133", expectedRelaxed1133, precisionRelaxed1133); 4137 4138 checker.check(" Strict 1133", expectedStrict1133, precisionStrict1133); 4139 } 4140 } 4141 4142 @Test grouping()4143 public void grouping() { 4144 assertFormatDescendingBig( 4145 "Western Grouping", 4146 "group-auto", 4147 "", 4148 NumberFormatter.with().grouping(GroupingStrategy.AUTO), 4149 ULocale.ENGLISH, 4150 "87,650,000", 4151 "8,765,000", 4152 "876,500", 4153 "87,650", 4154 "8,765", 4155 "876.5", 4156 "87.65", 4157 "8.765", 4158 "0"); 4159 4160 assertFormatDescendingBig( 4161 "Indic Grouping", 4162 "group-auto", 4163 "", 4164 NumberFormatter.with().grouping(GroupingStrategy.AUTO), 4165 new ULocale("en-IN"), 4166 "8,76,50,000", 4167 "87,65,000", 4168 "8,76,500", 4169 "87,650", 4170 "8,765", 4171 "876.5", 4172 "87.65", 4173 "8.765", 4174 "0"); 4175 4176 assertFormatDescendingBig( 4177 "Western Grouping, Min 2", 4178 "group-min2", 4179 ",?", 4180 NumberFormatter.with().grouping(GroupingStrategy.MIN2), 4181 ULocale.ENGLISH, 4182 "87,650,000", 4183 "8,765,000", 4184 "876,500", 4185 "87,650", 4186 "8765", 4187 "876.5", 4188 "87.65", 4189 "8.765", 4190 "0"); 4191 4192 assertFormatDescendingBig( 4193 "Indic Grouping, Min 2", 4194 "group-min2", 4195 ",?", 4196 NumberFormatter.with().grouping(GroupingStrategy.MIN2), 4197 new ULocale("en-IN"), 4198 "8,76,50,000", 4199 "87,65,000", 4200 "8,76,500", 4201 "87,650", 4202 "8765", 4203 "876.5", 4204 "87.65", 4205 "8.765", 4206 "0"); 4207 4208 assertFormatDescendingBig( 4209 "No Grouping", 4210 "group-off", 4211 ",_", 4212 NumberFormatter.with().grouping(GroupingStrategy.OFF), 4213 new ULocale("en-IN"), 4214 "87650000", 4215 "8765000", 4216 "876500", 4217 "87650", 4218 "8765", 4219 "876.5", 4220 "87.65", 4221 "8.765", 4222 "0"); 4223 4224 assertFormatDescendingBig( 4225 "Indic locale with THOUSANDS grouping", 4226 "group-thousands", 4227 "group-thousands", 4228 NumberFormatter.with().grouping(GroupingStrategy.THOUSANDS), 4229 new ULocale("en-IN"), 4230 "87,650,000", 4231 "8,765,000", 4232 "876,500", 4233 "87,650", 4234 "8,765", 4235 "876.5", 4236 "87.65", 4237 "8.765", 4238 "0"); 4239 4240 // NOTE: Polish is interesting because it has minimumGroupingDigits=2 in locale data 4241 // (Most locales have either 1 or 2) 4242 // If this test breaks due to data changes, find another locale that has minimumGroupingDigits. 4243 assertFormatDescendingBig( 4244 "Polish Grouping", 4245 "group-auto", 4246 "", 4247 NumberFormatter.with().grouping(GroupingStrategy.AUTO), 4248 new ULocale("pl"), 4249 "87 650 000", 4250 "8 765 000", 4251 "876 500", 4252 "87 650", 4253 "8765", 4254 "876,5", 4255 "87,65", 4256 "8,765", 4257 "0"); 4258 4259 assertFormatDescendingBig( 4260 "Polish Grouping, Min 2", 4261 "group-min2", 4262 ",?", 4263 NumberFormatter.with().grouping(GroupingStrategy.MIN2), 4264 new ULocale("pl"), 4265 "87 650 000", 4266 "8 765 000", 4267 "876 500", 4268 "87 650", 4269 "8765", 4270 "876,5", 4271 "87,65", 4272 "8,765", 4273 "0"); 4274 4275 assertFormatDescendingBig( 4276 "Polish Grouping, Always", 4277 "group-on-aligned", 4278 ",!", 4279 NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED), 4280 new ULocale("pl"), 4281 "87 650 000", 4282 "8 765 000", 4283 "876 500", 4284 "87 650", 4285 "8 765", 4286 "876,5", 4287 "87,65", 4288 "8,765", 4289 "0"); 4290 4291 // NOTE: en_US_POSIX is interesting because it has no grouping in the default currency format. 4292 // If this test breaks due to data changes, find another locale that has no default grouping. 4293 assertFormatDescendingBig( 4294 "en_US_POSIX Currency Grouping", 4295 "currency/USD group-auto", 4296 "currency/USD", 4297 NumberFormatter.with().grouping(GroupingStrategy.AUTO).unit(USD), 4298 new ULocale("en_US_POSIX"), 4299 "$ 87650000.00", 4300 "$ 8765000.00", 4301 "$ 876500.00", 4302 "$ 87650.00", 4303 "$ 8765.00", 4304 "$ 876.50", 4305 "$ 87.65", 4306 "$ 8.76", 4307 "$ 0.00"); 4308 4309 assertFormatDescendingBig( 4310 "en_US_POSIX Currency Grouping, Always", 4311 "currency/USD group-on-aligned", 4312 "currency/USD ,!", 4313 NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED).unit(USD), 4314 new ULocale("en_US_POSIX"), 4315 "$ 87,650,000.00", 4316 "$ 8,765,000.00", 4317 "$ 876,500.00", 4318 "$ 87,650.00", 4319 "$ 8,765.00", 4320 "$ 876.50", 4321 "$ 87.65", 4322 "$ 8.76", 4323 "$ 0.00"); 4324 4325 MacroProps macros = new MacroProps(); 4326 macros.grouping = Grouper.getInstance((short) 4, (short) 1, (short) 3); 4327 assertFormatDescendingBig( 4328 "Custom Grouping via Internal API", 4329 null, 4330 null, 4331 NumberFormatter.with().macros(macros), 4332 ULocale.ENGLISH, 4333 "8,7,6,5,0000", 4334 "8,7,6,5000", 4335 "876500", 4336 "87650", 4337 "8765", 4338 "876.5", 4339 "87.65", 4340 "8.765", 4341 "0"); 4342 } 4343 4344 @Test padding()4345 public void padding() { 4346 assertFormatDescending( 4347 "Padding", 4348 null, 4349 null, 4350 NumberFormatter.with().padding(Padder.none()), 4351 ULocale.ENGLISH, 4352 "87,650", 4353 "8,765", 4354 "876.5", 4355 "87.65", 4356 "8.765", 4357 "0.8765", 4358 "0.08765", 4359 "0.008765", 4360 "0"); 4361 4362 assertFormatDescending( 4363 "Padding", 4364 null, 4365 null, 4366 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)), 4367 ULocale.ENGLISH, 4368 "**87,650", 4369 "***8,765", 4370 "***876.5", 4371 "***87.65", 4372 "***8.765", 4373 "**0.8765", 4374 "*0.08765", 4375 "0.008765", 4376 "*******0"); 4377 4378 assertFormatDescending( 4379 "Padding with code points", 4380 null, 4381 null, 4382 NumberFormatter.with().padding(Padder.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)), 4383 ULocale.ENGLISH, 4384 "87,650", 4385 "8,765", 4386 "876.5", 4387 "87.65", 4388 "8.765", 4389 "0.8765", 4390 "0.08765", 4391 "0.008765", 4392 "0"); 4393 4394 assertFormatDescending( 4395 "Padding with wide digits", 4396 null, 4397 null, 4398 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)) 4399 .symbols(NumberingSystem.getInstanceByName("mathsanb")), 4400 ULocale.ENGLISH, 4401 "**,", 4402 "***,", 4403 "***.", 4404 "***.", 4405 "***.", 4406 "**.", 4407 "*.", 4408 ".", 4409 "*******"); 4410 4411 assertFormatDescending( 4412 "Padding with currency spacing", 4413 null, 4414 null, 4415 NumberFormatter.with().padding(Padder.codePoints('*', 10, PadPosition.AFTER_PREFIX)).unit(GBP) 4416 .unitWidth(UnitWidth.ISO_CODE), 4417 ULocale.ENGLISH, 4418 "GBP 87,650.00", 4419 "GBP 8,765.00", 4420 "GBP*876.50", 4421 "GBP**87.65", 4422 "GBP***8.76", 4423 "GBP***0.88", 4424 "GBP***0.09", 4425 "GBP***0.01", 4426 "GBP***0.00"); 4427 4428 assertFormatSingle( 4429 "Pad Before Prefix", 4430 null, 4431 null, 4432 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_PREFIX)), 4433 ULocale.ENGLISH, 4434 -88.88, 4435 "**-88.88"); 4436 4437 assertFormatSingle( 4438 "Pad After Prefix", 4439 null, 4440 null, 4441 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)), 4442 ULocale.ENGLISH, 4443 -88.88, 4444 "-**88.88"); 4445 4446 assertFormatSingle( 4447 "Pad Before Suffix", 4448 null, 4449 null, 4450 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_SUFFIX)) 4451 .unit(NoUnit.PERCENT), 4452 ULocale.ENGLISH, 4453 88.88, 4454 "88.88**%"); 4455 4456 assertFormatSingle( 4457 "Pad After Suffix", 4458 null, 4459 null, 4460 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_SUFFIX)) 4461 .unit(NoUnit.PERCENT), 4462 ULocale.ENGLISH, 4463 88.88, 4464 "88.88%**"); 4465 4466 assertFormatSingle( 4467 "Currency Spacing with Zero Digit Padding Broken", 4468 null, 4469 null, 4470 NumberFormatter.with().padding(Padder.codePoints('0', 12, PadPosition.AFTER_PREFIX)).unit(GBP) 4471 .unitWidth(UnitWidth.ISO_CODE), 4472 ULocale.ENGLISH, 4473 514.23, 4474 "GBP 000514.23"); // TODO: This is broken; it renders too wide (13 instead of 12). 4475 } 4476 4477 @Test integerWidth()4478 public void integerWidth() { 4479 assertFormatDescending( 4480 "Integer Width Default", 4481 "integer-width/+0", 4482 "0", 4483 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1)), 4484 ULocale.ENGLISH, 4485 "87,650", 4486 "8,765", 4487 "876.5", 4488 "87.65", 4489 "8.765", 4490 "0.8765", 4491 "0.08765", 4492 "0.008765", 4493 "0"); 4494 4495 assertFormatDescending( 4496 "Integer Width Zero Fill 0", 4497 "integer-width/*", 4498 "integer-width/+", 4499 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(0)), 4500 ULocale.ENGLISH, 4501 "87,650", 4502 "8,765", 4503 "876.5", 4504 "87.65", 4505 "8.765", 4506 ".8765", 4507 ".08765", 4508 ".008765", 4509 "0"); // see ICU-20844 4510 4511 assertFormatDescending( 4512 "Integer Width Zero Fill 3", 4513 "integer-width/+000", 4514 "000", 4515 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(3)), 4516 ULocale.ENGLISH, 4517 "87,650", 4518 "8,765", 4519 "876.5", 4520 "087.65", 4521 "008.765", 4522 "000.8765", 4523 "000.08765", 4524 "000.008765", 4525 "000"); 4526 4527 assertFormatDescending( 4528 "Integer Width Max 3", 4529 "integer-width/##0", 4530 "integer-width/##0", 4531 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1).truncateAt(3)), 4532 ULocale.ENGLISH, 4533 "650", 4534 "765", 4535 "876.5", 4536 "87.65", 4537 "8.765", 4538 "0.8765", 4539 "0.08765", 4540 "0.008765", 4541 "0"); 4542 4543 assertFormatDescending( 4544 "Integer Width Fixed 2", 4545 "integer-width/00", 4546 "integer-width/00", 4547 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)), 4548 ULocale.ENGLISH, 4549 "50", 4550 "65", 4551 "76.5", 4552 "87.65", 4553 "08.765", 4554 "00.8765", 4555 "00.08765", 4556 "00.008765", 4557 "00"); 4558 4559 assertFormatDescending( 4560 "Integer Width Compact", 4561 "compact-short integer-width/000", 4562 "K integer-width/000", 4563 NumberFormatter.with() 4564 .notation(Notation.compactShort()) 4565 .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)), 4566 ULocale.ENGLISH, 4567 "088K", 4568 "008.8K", 4569 "876", 4570 "088", 4571 "008.8", 4572 "000.88", 4573 "000.088", 4574 "000.0088", 4575 "000"); 4576 4577 assertFormatDescending( 4578 "Integer Width Scientific", 4579 "scientific integer-width/000", 4580 "E0 integer-width/000", 4581 NumberFormatter.with() 4582 .notation(Notation.scientific()) 4583 .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)), 4584 ULocale.ENGLISH, 4585 "008.765E4", 4586 "008.765E3", 4587 "008.765E2", 4588 "008.765E1", 4589 "008.765E0", 4590 "008.765E-1", 4591 "008.765E-2", 4592 "008.765E-3", 4593 "000E0"); 4594 4595 assertFormatDescending( 4596 "Integer Width Engineering", 4597 "engineering integer-width/000", 4598 "EE0 integer-width/000", 4599 NumberFormatter.with() 4600 .notation(Notation.engineering()) 4601 .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)), 4602 ULocale.ENGLISH, 4603 "087.65E3", 4604 "008.765E3", 4605 "876.5E0", 4606 "087.65E0", 4607 "008.765E0", 4608 "876.5E-3", 4609 "087.65E-3", 4610 "008.765E-3", 4611 "000E0"); 4612 4613 assertFormatSingle( 4614 "Integer Width Remove All A", 4615 "integer-width/00", 4616 "integer-width/00", 4617 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)), 4618 ULocale.ENGLISH, 4619 2500, 4620 "00"); 4621 4622 assertFormatSingle( 4623 "Integer Width Remove All B", 4624 "integer-width/00", 4625 "integer-width/00", 4626 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)), 4627 ULocale.ENGLISH, 4628 25000, 4629 "00"); 4630 4631 assertFormatSingle( 4632 "Integer Width Remove All B, Bytes Mode", 4633 "integer-width/00", 4634 "integer-width/00", 4635 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)), 4636 ULocale.ENGLISH, 4637 // Note: this double produces all 17 significant digits 4638 10000000000000002000.0, 4639 "00"); 4640 4641 assertFormatDescending( 4642 "Integer Width Double Zero (ICU-21590)", 4643 "integer-width-trunc", 4644 "integer-width-trunc", 4645 NumberFormatter.with() 4646 .integerWidth(IntegerWidth.zeroFillTo(0).truncateAt(0)), 4647 ULocale.ENGLISH, 4648 "0", 4649 "0", 4650 ".5", 4651 ".65", 4652 ".765", 4653 ".8765", 4654 ".08765", 4655 ".008765", 4656 "0"); 4657 4658 assertFormatDescending( 4659 "Integer Width Double Zero with minFraction (ICU-21590)", 4660 "integer-width-trunc .0*", 4661 "integer-width-trunc .0*", 4662 NumberFormatter.with() 4663 .integerWidth(IntegerWidth.zeroFillTo(0).truncateAt(0)) 4664 .precision(Precision.minFraction(1)), 4665 ULocale.ENGLISH, 4666 ".0", 4667 ".0", 4668 ".5", 4669 ".65", 4670 ".765", 4671 ".8765", 4672 ".08765", 4673 ".008765", 4674 ".0"); 4675 } 4676 4677 @Test symbols()4678 public void symbols() { 4679 assertFormatDescending( 4680 "French Symbols with Japanese Data 1", 4681 null, 4682 null, 4683 NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)), 4684 ULocale.JAPAN, 4685 "87\u202F650", 4686 "8\u202F765", 4687 "876,5", 4688 "87,65", 4689 "8,765", 4690 "0,8765", 4691 "0,08765", 4692 "0,008765", 4693 "0"); 4694 4695 assertFormatSingle( 4696 "French Symbols with Japanese Data 2", 4697 null, 4698 null, 4699 NumberFormatter.with().notation(Notation.compactShort()) 4700 .symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)), 4701 ULocale.JAPAN, 4702 12345, 4703 "1,2\u4E07"); 4704 4705 assertFormatDescending( 4706 "Latin Numbering System with Arabic Data", 4707 "currency/USD latin", 4708 "currency/USD latin", 4709 NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), 4710 new ULocale("ar"), 4711 "\u200F87,650.00 US$", 4712 "\u200F8,765.00 US$", 4713 "\u200F876.50 US$", 4714 "\u200F87.65 US$", 4715 "\u200F8.76 US$", 4716 "\u200F0.88 US$", 4717 "\u200F0.09 US$", 4718 "\u200F0.01 US$", 4719 "\u200F0.00 US$"); 4720 4721 assertFormatDescending( 4722 "Math Numbering System with French Data", 4723 "numbering-system/mathsanb", 4724 "numbering-system/mathsanb", 4725 NumberFormatter.with().symbols(NumberingSystem.getInstanceByName("mathsanb")), 4726 ULocale.FRENCH, 4727 "\u202f", 4728 "\u202f", 4729 ",", 4730 ",", 4731 ",", 4732 ",", 4733 ",", 4734 ",", 4735 ""); 4736 4737 assertFormatSingle( 4738 "Swiss Symbols (used in documentation)", 4739 null, 4740 null, 4741 NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("de-CH"))), 4742 ULocale.ENGLISH, 4743 12345.67, 4744 "12’345.67"); 4745 4746 assertFormatSingle( 4747 "Myanmar Symbols (used in documentation)", 4748 null, 4749 null, 4750 NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("my_MY"))), 4751 ULocale.ENGLISH, 4752 12345.67, 4753 "\u1041\u1042,\u1043\u1044\u1045.\u1046\u1047"); 4754 4755 // NOTE: Locale ar puts ¤ after the number in NS arab but before the number in NS latn. 4756 4757 assertFormatSingle( 4758 "Currency symbol should follow number in ar with NS latn", 4759 "currency/USD latin", 4760 "currency/USD latin", 4761 NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), 4762 new ULocale("ar"), 4763 12345.67, 4764 "\u200F12,345.67 US$"); 4765 4766 assertFormatSingle( 4767 "Currency symbol should follow number in ar@numbers=latn", 4768 "currency/USD", 4769 "currency/USD", 4770 NumberFormatter.with().unit(USD), 4771 new ULocale("ar@numbers=latn"), 4772 12345.67, 4773 "\u200F12,345.67 US$"); 4774 4775 assertFormatSingle( 4776 "Currency symbol should follow number in ar-EG with NS arab", 4777 "currency/USD", 4778 "currency/USD", 4779 NumberFormatter.with().unit(USD), 4780 new ULocale("ar-EG"), 4781 12345.67, 4782 "\u200F١٢٬٣٤٥٫٦٧ US$"); 4783 4784 assertFormatSingle( 4785 "Currency symbol should follow number in ar@numbers=arab", 4786 "currency/USD", 4787 "currency/USD", 4788 NumberFormatter.with().unit(USD), 4789 new ULocale("ar@numbers=arab"), 4790 12345.67, 4791 "\u200F١٢٬٣٤٥٫٦٧ US$"); 4792 4793 assertFormatSingle( 4794 "NumberingSystem in API should win over @numbers keyword", 4795 "currency/USD latin", 4796 "currency/USD latin", 4797 NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD), 4798 new ULocale("ar@numbers=arab"), 4799 12345.67, 4800 "\u200F12,345.67 US$"); 4801 4802 assertEquals("NumberingSystem in API should win over @numbers keyword in reverse order", 4803 "\u200F12,345.67 US$", 4804 NumberFormatter.withLocale(new ULocale("ar@numbers=arab")) 4805 .symbols(NumberingSystem.LATIN) 4806 .unit(USD) 4807 .format(12345.67) 4808 .toString()); 4809 4810 DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(new ULocale("de-CH")); 4811 UnlocalizedNumberFormatter f = NumberFormatter.with().symbols(symbols); 4812 symbols.setGroupingSeparatorString("!"); 4813 assertFormatSingle( 4814 "Symbols object should be copied", 4815 null, 4816 null, 4817 f, 4818 ULocale.ENGLISH, 4819 12345.67, 4820 "12’345.67"); 4821 4822 assertFormatSingle( 4823 "The last symbols setter wins", 4824 "latin", 4825 "latin", 4826 NumberFormatter.with().symbols(symbols).symbols(NumberingSystem.LATIN), 4827 ULocale.ENGLISH, 4828 12345.67, 4829 "12,345.67"); 4830 4831 assertFormatSingle( 4832 "The last symbols setter wins", 4833 null, 4834 null, 4835 NumberFormatter.with().symbols(NumberingSystem.LATIN).symbols(symbols), 4836 ULocale.ENGLISH, 4837 12345.67, 4838 "12!345.67"); 4839 } 4840 4841 @Test 4842 @Ignore("This feature is not currently available.") symbolsOverride()4843 public void symbolsOverride() { 4844 DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(ULocale.ENGLISH); 4845 dfs.setCurrencySymbol("@"); 4846 dfs.setInternationalCurrencySymbol("foo"); 4847 assertFormatSingle( 4848 "Custom Short Currency Symbol", 4849 "$XXX", 4850 "$XXX", 4851 NumberFormatter.with().unit(Currency.getInstance("XXX")).symbols(dfs), 4852 ULocale.ENGLISH, 4853 12.3, 4854 "@ 12.30"); 4855 } 4856 4857 @Test sign()4858 public void sign() { 4859 assertFormatSingle( 4860 "Sign Auto Positive", 4861 "sign-auto", 4862 "", 4863 NumberFormatter.with().sign(SignDisplay.AUTO), 4864 ULocale.ENGLISH, 4865 444444, 4866 "444,444"); 4867 4868 assertFormatSingle( 4869 "Sign Auto Negative", 4870 "sign-auto", 4871 "", 4872 NumberFormatter.with().sign(SignDisplay.AUTO), 4873 ULocale.ENGLISH, 4874 -444444, 4875 "-444,444"); 4876 4877 assertFormatSingle( 4878 "Sign Auto Zero", 4879 "sign-auto", 4880 "", 4881 NumberFormatter.with().sign(SignDisplay.AUTO), 4882 ULocale.ENGLISH, 4883 0, 4884 "0"); 4885 4886 assertFormatSingle( 4887 "Sign Always Positive", 4888 "sign-always", 4889 "+!", 4890 NumberFormatter.with().sign(SignDisplay.ALWAYS), 4891 ULocale.ENGLISH, 4892 444444, 4893 "+444,444"); 4894 4895 assertFormatSingle( 4896 "Sign Always Negative", 4897 "sign-always", 4898 "+!", 4899 NumberFormatter.with().sign(SignDisplay.ALWAYS), 4900 ULocale.ENGLISH, 4901 -444444, 4902 "-444,444"); 4903 4904 assertFormatSingle( 4905 "Sign Always Zero", 4906 "sign-always", 4907 "+!", 4908 NumberFormatter.with().sign(SignDisplay.ALWAYS), 4909 ULocale.ENGLISH, 4910 0, 4911 "+0"); 4912 4913 assertFormatSingle( 4914 "Sign Never Positive", 4915 "sign-never", 4916 "+_", 4917 NumberFormatter.with().sign(SignDisplay.NEVER), 4918 ULocale.ENGLISH, 4919 444444, 4920 "444,444"); 4921 4922 assertFormatSingle( 4923 "Sign Never Negative", 4924 "sign-never", 4925 "+_", 4926 NumberFormatter.with().sign(SignDisplay.NEVER), 4927 ULocale.ENGLISH, 4928 -444444, 4929 "444,444"); 4930 4931 assertFormatSingle( 4932 "Sign Never Zero", 4933 "sign-never", 4934 "+_", 4935 NumberFormatter.with().sign(SignDisplay.NEVER), 4936 ULocale.ENGLISH, 4937 0, 4938 "0"); 4939 4940 assertFormatSingle( 4941 "Sign Accounting Positive", 4942 "currency/USD sign-accounting", 4943 "currency/USD ()", 4944 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), 4945 ULocale.ENGLISH, 4946 444444, 4947 "$444,444.00"); 4948 4949 assertFormatSingle( 4950 "Sign Accounting Negative", 4951 "currency/USD sign-accounting", 4952 "currency/USD ()", 4953 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), 4954 ULocale.ENGLISH, 4955 -444444, 4956 "($444,444.00)"); 4957 4958 assertFormatSingle( 4959 "Sign Accounting Zero", 4960 "currency/USD sign-accounting", 4961 "currency/USD ()", 4962 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD), 4963 ULocale.ENGLISH, 4964 0, 4965 "$0.00"); 4966 4967 assertFormatSingle( 4968 "Sign Accounting-Always Positive", 4969 "currency/USD sign-accounting-always", 4970 "currency/USD ()!", 4971 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), 4972 ULocale.ENGLISH, 4973 444444, 4974 "+$444,444.00"); 4975 4976 assertFormatSingle( 4977 "Sign Accounting-Always Negative", 4978 "currency/USD sign-accounting-always", 4979 "currency/USD ()!", 4980 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), 4981 ULocale.ENGLISH, 4982 -444444, 4983 "($444,444.00)"); 4984 4985 assertFormatSingle( 4986 "Sign Accounting-Always Zero", 4987 "currency/USD sign-accounting-always", 4988 "currency/USD ()!", 4989 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD), 4990 ULocale.ENGLISH, 4991 0, 4992 "+$0.00"); 4993 4994 assertFormatSingle( 4995 "Sign Except-Zero Positive", 4996 "sign-except-zero", 4997 "+?", 4998 NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), 4999 ULocale.ENGLISH, 5000 444444, 5001 "+444,444"); 5002 5003 assertFormatSingle( 5004 "Sign Except-Zero Negative", 5005 "sign-except-zero", 5006 "+?", 5007 NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), 5008 ULocale.ENGLISH, 5009 -444444, 5010 "-444,444"); 5011 5012 assertFormatSingle( 5013 "Sign Except-Zero Zero", 5014 "sign-except-zero", 5015 "+?", 5016 NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO), 5017 ULocale.ENGLISH, 5018 0, 5019 "0"); 5020 5021 assertFormatSingle( 5022 "Sign Accounting-Except-Zero Positive", 5023 "currency/USD sign-accounting-except-zero", 5024 "currency/USD ()?", 5025 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), 5026 ULocale.ENGLISH, 5027 444444, 5028 "+$444,444.00"); 5029 5030 assertFormatSingle( 5031 "Sign Accounting-Except-Zero Negative", 5032 "currency/USD sign-accounting-except-zero", 5033 "currency/USD ()?", 5034 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), 5035 ULocale.ENGLISH, 5036 -444444, 5037 "($444,444.00)"); 5038 5039 assertFormatSingle( 5040 "Sign Accounting-Except-Zero Zero", 5041 "currency/USD sign-accounting-except-zero", 5042 "currency/USD ()?", 5043 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD), 5044 ULocale.ENGLISH, 5045 0, 5046 "$0.00"); 5047 5048 assertFormatSingle( 5049 "Sign Negative Positive", 5050 "sign-negative", 5051 "+-", 5052 NumberFormatter.with().sign(SignDisplay.NEGATIVE), 5053 ULocale.ENGLISH, 5054 444444, 5055 "444,444"); 5056 5057 assertFormatSingle( 5058 "Sign Negative Negative", 5059 "sign-negative", 5060 "+-", 5061 NumberFormatter.with().sign(SignDisplay.NEGATIVE), 5062 ULocale.ENGLISH, 5063 -444444, 5064 "-444,444"); 5065 5066 assertFormatSingle( 5067 "Sign Negative Negative Zero", 5068 "sign-negative", 5069 "+-", 5070 NumberFormatter.with().sign(SignDisplay.NEGATIVE), 5071 ULocale.ENGLISH, 5072 -0.0000001, 5073 "0"); 5074 5075 assertFormatSingle( 5076 "Sign Accounting-Negative Positive", 5077 "currency/USD sign-accounting-negative", 5078 "currency/USD ()-", 5079 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_NEGATIVE).unit(USD), 5080 ULocale.ENGLISH, 5081 444444, 5082 "$444,444.00"); 5083 5084 assertFormatSingle( 5085 "Sign Accounting-Negative Negative", 5086 "currency/USD sign-accounting-negative", 5087 "currency/USD ()-", 5088 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_NEGATIVE).unit(USD), 5089 ULocale.ENGLISH, 5090 -444444, 5091 "($444,444.00)"); 5092 5093 assertFormatSingle( 5094 "Sign Accounting-Negative Negative Zero", 5095 "currency/USD sign-accounting-negative", 5096 "currency/USD ()-", 5097 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_NEGATIVE).unit(USD), 5098 ULocale.ENGLISH, 5099 -0.0000001, 5100 "$0.00"); 5101 5102 assertFormatSingle( 5103 "Sign Accounting Negative Hidden", 5104 "currency/USD unit-width-hidden sign-accounting", 5105 "currency/USD unit-width-hidden ()", 5106 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.HIDDEN), 5107 ULocale.ENGLISH, 5108 -444444, 5109 "(444,444.00)"); 5110 5111 assertFormatSingle( 5112 "Sign Accounting Negative Narrow", 5113 "currency/USD unit-width-narrow sign-accounting", 5114 "currency/USD unit-width-narrow ()", 5115 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.NARROW), 5116 ULocale.CANADA, 5117 -444444, 5118 "(US$444,444.00)"); 5119 5120 assertFormatSingle( 5121 "Sign Accounting Negative Short", 5122 "currency/USD sign-accounting", 5123 "currency/USD ()", 5124 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.SHORT), 5125 ULocale.CANADA, 5126 -444444, 5127 "(US$444,444.00)"); 5128 5129 assertFormatSingle( 5130 "Sign Accounting Negative Iso Code", 5131 "currency/USD unit-width-iso-code sign-accounting", 5132 "currency/USD unit-width-iso-code ()", 5133 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.ISO_CODE), 5134 ULocale.CANADA, 5135 -444444, 5136 "(USD 444,444.00)"); 5137 5138 // Note: CLDR does not provide an accounting pattern for long name currency. 5139 // We fall back to normal currency format. This may change in the future. 5140 assertFormatSingle( 5141 "Sign Accounting Negative Full Name", 5142 "currency/USD unit-width-full-name sign-accounting", 5143 "currency/USD unit-width-full-name ()", 5144 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.FULL_NAME), 5145 ULocale.CANADA, 5146 -444444, 5147 "-444,444.00 US dollars"); 5148 } 5149 5150 @Test signNearZero()5151 public void signNearZero() { 5152 // https://unicode-org.atlassian.net/browse/ICU-20709 5153 Object[][] cases = { 5154 { SignDisplay.AUTO, 1.1, "1" }, 5155 { SignDisplay.AUTO, 0.9, "1" }, 5156 { SignDisplay.AUTO, 0.1, "0" }, 5157 { SignDisplay.AUTO, -0.1, "-0" }, // interesting case 5158 { SignDisplay.AUTO, -0.9, "-1" }, 5159 { SignDisplay.AUTO, -1.1, "-1" }, 5160 { SignDisplay.ALWAYS, 1.1, "+1" }, 5161 { SignDisplay.ALWAYS, 0.9, "+1" }, 5162 { SignDisplay.ALWAYS, 0.1, "+0" }, 5163 { SignDisplay.ALWAYS, -0.1, "-0" }, 5164 { SignDisplay.ALWAYS, -0.9, "-1" }, 5165 { SignDisplay.ALWAYS, -1.1, "-1" }, 5166 { SignDisplay.EXCEPT_ZERO, 1.1, "+1" }, 5167 { SignDisplay.EXCEPT_ZERO, 0.9, "+1" }, 5168 { SignDisplay.EXCEPT_ZERO, 0.1, "0" }, // interesting case 5169 { SignDisplay.EXCEPT_ZERO, -0.1, "0" }, // interesting case 5170 { SignDisplay.EXCEPT_ZERO, -0.9, "-1" }, 5171 { SignDisplay.EXCEPT_ZERO, -1.1, "-1" }, 5172 { SignDisplay.NEGATIVE, 1.1, "1" }, 5173 { SignDisplay.NEGATIVE, 0.9, "1" }, 5174 { SignDisplay.NEGATIVE, 0.1, "0" }, 5175 { SignDisplay.NEGATIVE, -0.1, "0" }, // interesting case 5176 { SignDisplay.NEGATIVE, -0.9, "-1" }, 5177 { SignDisplay.NEGATIVE, -1.1, "-1" }, 5178 }; 5179 for (Object[] cas : cases) { 5180 SignDisplay sign = (SignDisplay) cas[0]; 5181 double input = (Double) cas[1]; 5182 String expected = (String) cas[2]; 5183 String actual = NumberFormatter.with() 5184 .sign(sign) 5185 .precision(Precision.integer()) 5186 .locale(Locale.US) 5187 .format(input) 5188 .toString(); 5189 assertEquals( 5190 input + " @ SignDisplay " + sign, 5191 expected, actual); 5192 } 5193 } 5194 5195 @Test signCoverage()5196 public void signCoverage() { 5197 // https://unicode-org.atlassian.net/browse/ICU-20708 5198 Object[][][] cases = new Object[][][] { 5199 { {SignDisplay.AUTO}, { "-∞", "-1", "-0", "0", "1", "∞", "NaN", "-NaN" } }, 5200 { {SignDisplay.ALWAYS}, { "-∞", "-1", "-0", "+0", "+1", "+∞", "+NaN", "-NaN" } }, 5201 { {SignDisplay.NEVER}, { "∞", "1", "0", "0", "1", "∞", "NaN", "NaN" } }, 5202 { {SignDisplay.EXCEPT_ZERO}, { "-∞", "-1", "0", "0", "+1", "+∞", "NaN", "NaN" } }, 5203 }; 5204 double negNaN = Math.copySign(Double.NaN, -0.0); 5205 double inputs[] = new double[] { 5206 Double.NEGATIVE_INFINITY, -1, -0.0, 0, 1, Double.POSITIVE_INFINITY, Double.NaN, negNaN 5207 }; 5208 for (Object[][] cas : cases) { 5209 SignDisplay sign = (SignDisplay) cas[0][0]; 5210 for (int i = 0; i < inputs.length; i++) { 5211 double input = inputs[i]; 5212 String expected = (String) cas[1][i]; 5213 String actual = NumberFormatter.with() 5214 .sign(sign) 5215 .locale(Locale.US) 5216 .format(input) 5217 .toString(); 5218 assertEquals( 5219 input + " " + sign, 5220 expected, actual); 5221 } 5222 } 5223 } 5224 5225 @Test decimal()5226 public void decimal() { 5227 assertFormatDescending( 5228 "Decimal Default", 5229 "decimal-auto", 5230 "", 5231 NumberFormatter.with().decimal(DecimalSeparatorDisplay.AUTO), 5232 ULocale.ENGLISH, 5233 "87,650", 5234 "8,765", 5235 "876.5", 5236 "87.65", 5237 "8.765", 5238 "0.8765", 5239 "0.08765", 5240 "0.008765", 5241 "0"); 5242 5243 assertFormatDescending( 5244 "Decimal Always Shown", 5245 "decimal-always", 5246 "decimal-always", 5247 NumberFormatter.with().decimal(DecimalSeparatorDisplay.ALWAYS), 5248 ULocale.ENGLISH, 5249 "87,650.", 5250 "8,765.", 5251 "876.5", 5252 "87.65", 5253 "8.765", 5254 "0.8765", 5255 "0.08765", 5256 "0.008765", 5257 "0."); 5258 } 5259 5260 @Test scale()5261 public void scale() { 5262 assertFormatDescending( 5263 "Multiplier None", 5264 "scale/1", 5265 "", 5266 NumberFormatter.with().scale(Scale.none()), 5267 ULocale.ENGLISH, 5268 "87,650", 5269 "8,765", 5270 "876.5", 5271 "87.65", 5272 "8.765", 5273 "0.8765", 5274 "0.08765", 5275 "0.008765", 5276 "0"); 5277 5278 assertFormatDescending( 5279 "Multiplier Power of Ten", 5280 "scale/1000000", 5281 "scale/1000000", 5282 NumberFormatter.with().scale(Scale.powerOfTen(6)), 5283 ULocale.ENGLISH, 5284 "87,650,000,000", 5285 "8,765,000,000", 5286 "876,500,000", 5287 "87,650,000", 5288 "8,765,000", 5289 "876,500", 5290 "87,650", 5291 "8,765", 5292 "0"); 5293 5294 assertFormatDescending( 5295 "Multiplier Arbitrary Double", 5296 "scale/5.2", 5297 "scale/5.2", 5298 NumberFormatter.with().scale(Scale.byDouble(5.2)), 5299 ULocale.ENGLISH, 5300 "455,780", 5301 "45,578", 5302 "4,557.8", 5303 "455.78", 5304 "45.578", 5305 "4.5578", 5306 "0.45578", 5307 "0.045578", 5308 "0"); 5309 5310 assertFormatDescending( 5311 "Multiplier Arbitrary BigDecimal", 5312 "scale/5.2", 5313 "scale/5.2", 5314 NumberFormatter.with().scale(Scale.byBigDecimal(new BigDecimal("5.2"))), 5315 ULocale.ENGLISH, 5316 "455,780", 5317 "45,578", 5318 "4,557.8", 5319 "455.78", 5320 "45.578", 5321 "4.5578", 5322 "0.45578", 5323 "0.045578", 5324 "0"); 5325 5326 assertFormatDescending( 5327 "Multiplier Arbitrary Double And Power Of Ten", 5328 "scale/5200", 5329 "scale/5200", 5330 NumberFormatter.with().scale(Scale.byDoubleAndPowerOfTen(5.2, 3)), 5331 ULocale.ENGLISH, 5332 "455,780,000", 5333 "45,578,000", 5334 "4,557,800", 5335 "455,780", 5336 "45,578", 5337 "4,557.8", 5338 "455.78", 5339 "45.578", 5340 "0"); 5341 5342 assertFormatDescending( 5343 "Multiplier Zero", 5344 "scale/0", 5345 "scale/0", 5346 NumberFormatter.with().scale(Scale.byDouble(0)), 5347 ULocale.ENGLISH, 5348 "0", 5349 "0", 5350 "0", 5351 "0", 5352 "0", 5353 "0", 5354 "0", 5355 "0", 5356 "0"); 5357 5358 assertFormatSingle( 5359 "Multiplier Skeleton Scientific Notation and Percent", 5360 "percent scale/1E2", 5361 "%x100", 5362 NumberFormatter.with().unit(NoUnit.PERCENT).scale(Scale.powerOfTen(2)), 5363 ULocale.ENGLISH, 5364 0.5, 5365 "50%"); 5366 5367 assertFormatSingle( 5368 "Negative Multiplier", 5369 "scale/-5.2", 5370 "scale/-5.2", 5371 NumberFormatter.with().scale(Scale.byDouble(-5.2)), 5372 ULocale.ENGLISH, 5373 2, 5374 "-10.4"); 5375 5376 assertFormatSingle( 5377 "Negative One Multiplier", 5378 "scale/-1", 5379 "scale/-1", 5380 NumberFormatter.with().scale(Scale.byDouble(-1)), 5381 ULocale.ENGLISH, 5382 444444, 5383 "-444,444"); 5384 5385 assertFormatSingle( 5386 "Two-Type Multiplier with Overlap", 5387 "scale/10000", 5388 "scale/10000", 5389 NumberFormatter.with().scale(Scale.byDoubleAndPowerOfTen(100, 2)), 5390 ULocale.ENGLISH, 5391 2, 5392 "20,000"); 5393 } 5394 5395 @Test locale()5396 public void locale() { 5397 // Coverage for the locale setters. 5398 Assert.assertEquals(NumberFormatter.with().locale(ULocale.ENGLISH), NumberFormatter.with().locale(Locale.ENGLISH)); 5399 Assert.assertEquals(NumberFormatter.with().locale(ULocale.ENGLISH), NumberFormatter.withLocale(ULocale.ENGLISH)); 5400 Assert.assertEquals(NumberFormatter.with().locale(ULocale.ENGLISH), NumberFormatter.withLocale(Locale.ENGLISH)); 5401 Assert.assertNotEquals(NumberFormatter.with().locale(ULocale.ENGLISH), NumberFormatter.with().locale(Locale.FRENCH)); 5402 } 5403 5404 @Test formatTypes()5405 public void formatTypes() { 5406 LocalizedNumberFormatter formatter = NumberFormatter.withLocale(ULocale.ENGLISH); 5407 5408 // Double 5409 Assert.assertEquals("514.23", formatter.format(514.23).toString()); 5410 5411 // Int64 5412 Assert.assertEquals("51,423", formatter.format(51423L).toString()); 5413 5414 // BigDecimal 5415 Assert.assertEquals("987,654,321,234,567,890", 5416 formatter.format(new BigDecimal("98765432123456789E1")).toString()); 5417 5418 // Also test proper DecimalQuantity bytes storage when all digits are in the fraction. 5419 // The number needs to have exactly 40 digits, which is the size of the default buffer. 5420 // (issue discovered by the address sanitizer in C++) 5421 Assert.assertEquals("0.009876543210987654321098765432109876543211", 5422 formatter.precision(Precision.unlimited()) 5423 .format(new BigDecimal("0.009876543210987654321098765432109876543211")) 5424 .toString()); 5425 } 5426 5427 @Test fieldPositionLogic()5428 public void fieldPositionLogic() { 5429 String message = "Field position logic test"; 5430 5431 FormattedNumber fmtd = assertFormatSingle( 5432 message, 5433 "", 5434 "", 5435 NumberFormatter.with(), 5436 ULocale.ENGLISH, 5437 -9876543210.12, 5438 "-9,876,543,210.12"); 5439 5440 Object[][] expectedFieldPositions = new Object[][]{ 5441 {NumberFormat.Field.SIGN, 0, 1}, 5442 {NumberFormat.Field.GROUPING_SEPARATOR, 2, 3}, 5443 {NumberFormat.Field.GROUPING_SEPARATOR, 6, 7}, 5444 {NumberFormat.Field.GROUPING_SEPARATOR, 10, 11}, 5445 {NumberFormat.Field.INTEGER, 1, 14}, 5446 {NumberFormat.Field.DECIMAL_SEPARATOR, 14, 15}, 5447 {NumberFormat.Field.FRACTION, 15, 17}}; 5448 5449 assertNumberFieldPositions(message, fmtd, expectedFieldPositions); 5450 5451 // Test the iteration functionality of nextFieldPosition 5452 ConstrainedFieldPosition actual = new ConstrainedFieldPosition(); 5453 actual.constrainField(NumberFormat.Field.GROUPING_SEPARATOR); 5454 int i = 1; 5455 while (fmtd.nextPosition(actual)) { 5456 Object[] cas = expectedFieldPositions[i++]; 5457 NumberFormat.Field expectedField = (NumberFormat.Field) cas[0]; 5458 int expectedBeginIndex = (Integer) cas[1]; 5459 int expectedEndIndex = (Integer) cas[2]; 5460 5461 assertEquals( 5462 "Next for grouping, field, case #" + i, 5463 expectedField, 5464 actual.getField()); 5465 assertEquals( 5466 "Next for grouping, begin index, case #" + i, 5467 expectedBeginIndex, 5468 actual.getStart()); 5469 assertEquals( 5470 "Next for grouping, end index, case #" + i, 5471 expectedEndIndex, 5472 actual.getLimit()); 5473 } 5474 assertEquals("Should have seen all grouping separators", 4, i); 5475 5476 // Make sure strings without fraction do not contain fraction field 5477 actual.reset(); 5478 actual.constrainField(NumberFormat.Field.FRACTION); 5479 fmtd = NumberFormatter.withLocale(ULocale.ENGLISH).format(5); 5480 assertFalse("No fraction part in an integer", fmtd.nextPosition(actual)); 5481 } 5482 5483 @Test fieldPositionCoverage()5484 public void fieldPositionCoverage() { 5485 { 5486 String message = "Measure unit field position basic"; 5487 FormattedNumber result = assertFormatSingle( 5488 message, 5489 "measure-unit/temperature-fahrenheit", 5490 "unit/fahrenheit", 5491 NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT), 5492 ULocale.ENGLISH, 5493 68, 5494 "68°F"); 5495 Object[][] expectedFieldPositions = new Object[][] { 5496 // field, begin index, end index 5497 {NumberFormat.Field.INTEGER, 0, 2}, 5498 {NumberFormat.Field.MEASURE_UNIT, 2, 4}}; 5499 assertNumberFieldPositions( 5500 message, 5501 result, 5502 expectedFieldPositions); 5503 } 5504 5505 { 5506 String message = "Measure unit field position with compound unit"; 5507 FormattedNumber result = assertFormatSingle( 5508 message, 5509 "measure-unit/temperature-fahrenheit per-measure-unit/duration-day", 5510 "unit/fahrenheit-per-day", 5511 NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).perUnit(MeasureUnit.DAY), 5512 ULocale.ENGLISH, 5513 68, 5514 "68°F/d"); 5515 Object[][] expectedFieldPositions = new Object[][] { 5516 // field, begin index, end index 5517 {NumberFormat.Field.INTEGER, 0, 2}, 5518 {NumberFormat.Field.MEASURE_UNIT, 2, 6}}; 5519 assertNumberFieldPositions( 5520 message, 5521 result, 5522 expectedFieldPositions); 5523 } 5524 5525 { 5526 String message = "Measure unit field position with spaces"; 5527 FormattedNumber result = assertFormatSingle( 5528 message, 5529 "measure-unit/length-meter unit-width-full-name", 5530 "unit/meter unit-width-full-name", 5531 NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME), 5532 ULocale.ENGLISH, 5533 68, 5534 "68 meters"); 5535 Object[][] expectedFieldPositions = new Object[][] { 5536 // field, begin index, end index 5537 {NumberFormat.Field.INTEGER, 0, 2}, 5538 // note: field starts after the space 5539 {NumberFormat.Field.MEASURE_UNIT, 3, 9}}; 5540 assertNumberFieldPositions( 5541 message, 5542 result, 5543 expectedFieldPositions); 5544 } 5545 5546 { 5547 String message = "Measure unit field position with prefix and suffix, composed m/s"; 5548 FormattedNumber result = assertFormatSingle( 5549 message, 5550 "measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name", 5551 "measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name", 5552 NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND).unitWidth(UnitWidth.FULL_NAME), 5553 new ULocale("ky"), // locale with the interesting data 5554 68, 5555 "секундасына 68 метр"); 5556 Object[][] expectedFieldPositions = new Object[][] { 5557 // field, begin index, end index 5558 {NumberFormat.Field.MEASURE_UNIT, 0, 11}, 5559 {NumberFormat.Field.INTEGER, 12, 14}, 5560 {NumberFormat.Field.MEASURE_UNIT, 15, 19}}; 5561 assertNumberFieldPositions( 5562 message, 5563 result, 5564 expectedFieldPositions); 5565 } 5566 5567 { 5568 String message = "Measure unit field position with prefix and suffix, built-in m/s"; 5569 FormattedNumber result = assertFormatSingle( 5570 message, 5571 "measure-unit/speed-meter-per-second unit-width-full-name", 5572 "unit/meter-per-second unit-width-full-name", 5573 NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND).unitWidth(UnitWidth.FULL_NAME), 5574 new ULocale("ky"), // locale with the interesting data 5575 68, 5576 "секундасына 68 метр"); 5577 Object[][] expectedFieldPositions = new Object[][] { 5578 // field, begin index, end index 5579 {NumberFormat.Field.MEASURE_UNIT, 0, 11}, 5580 {NumberFormat.Field.INTEGER, 12, 14}, 5581 {NumberFormat.Field.MEASURE_UNIT, 15, 19}}; 5582 assertNumberFieldPositions( 5583 message, 5584 result, 5585 expectedFieldPositions); 5586 } 5587 5588 { 5589 String message = "Measure unit field position with inner spaces"; 5590 FormattedNumber result = assertFormatSingle( 5591 message, 5592 "measure-unit/temperature-fahrenheit unit-width-full-name", 5593 "unit/fahrenheit unit-width-full-name", 5594 NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.FULL_NAME), 5595 new ULocale("vi"), // locale with the interesting data 5596 68, 5597 "68 độ F"); 5598 Object[][] expectedFieldPositions = new Object[][] { 5599 // field, begin index, end index 5600 {NumberFormat.Field.INTEGER, 0, 2}, 5601 // Should trim leading/trailing spaces, but not inner spaces: 5602 {NumberFormat.Field.MEASURE_UNIT, 3, 7}}; 5603 assertNumberFieldPositions( 5604 message, 5605 result, 5606 expectedFieldPositions); 5607 } 5608 5609 { 5610 // Data: other{"{0} K"} == "\u200E{0} K" 5611 // If that data changes, try to find another example of a non-empty unit prefix/suffix 5612 // that is also all ignorables (whitespace and bidi control marks). 5613 String message = "Measure unit field position with fully ignorable prefix"; 5614 FormattedNumber result = assertFormatSingle( 5615 message, 5616 "measure-unit/temperature-kelvin", 5617 "unit/kelvin", 5618 NumberFormatter.with().unit(MeasureUnit.KELVIN), 5619 new ULocale("fa"), // locale with the interesting data 5620 68, 5621 "۶۸ K"); 5622 Object[][] expectedFieldPositions = new Object[][] { 5623 // field, begin index, end index 5624 {NumberFormat.Field.INTEGER, 1, 3}, 5625 {NumberFormat.Field.MEASURE_UNIT, 4, 5}}; 5626 assertNumberFieldPositions( 5627 message, 5628 result, 5629 expectedFieldPositions); 5630 } 5631 5632 { 5633 String message = "Compact field basic"; 5634 FormattedNumber result = assertFormatSingle( 5635 message, 5636 "compact-short", 5637 "K", 5638 NumberFormatter.with().notation(Notation.compactShort()), 5639 ULocale.US, 5640 65000, 5641 "65K"); 5642 Object[][] expectedFieldPositions = new Object[][] { 5643 // field, begin index, end index 5644 {NumberFormat.Field.INTEGER, 0, 2}, 5645 {NumberFormat.Field.COMPACT, 2, 3}}; 5646 assertNumberFieldPositions( 5647 message, 5648 result, 5649 expectedFieldPositions); 5650 } 5651 5652 { 5653 String message = "Compact field with spaces"; 5654 FormattedNumber result = assertFormatSingle( 5655 message, 5656 "compact-long", 5657 "KK", 5658 NumberFormatter.with().notation(Notation.compactLong()), 5659 ULocale.US, 5660 65000, 5661 "65 thousand"); 5662 Object[][] expectedFieldPositions = new Object[][] { 5663 // field, begin index, end index 5664 {NumberFormat.Field.INTEGER, 0, 2}, 5665 {NumberFormat.Field.COMPACT, 3, 11}}; 5666 assertNumberFieldPositions( 5667 message, 5668 result, 5669 expectedFieldPositions); 5670 } 5671 5672 { 5673 String message = "Compact field with inner space"; 5674 FormattedNumber result = assertFormatSingle( 5675 message, 5676 "compact-long", 5677 "KK", 5678 NumberFormatter.with().notation(Notation.compactLong()), 5679 new ULocale("fil"), // locale with interesting data 5680 6000, 5681 "6 na libo"); 5682 Object[][] expectedFieldPositions = new Object[][] { 5683 // field, begin index, end index 5684 {NumberFormat.Field.INTEGER, 0, 1}, 5685 {NumberFormat.Field.COMPACT, 2, 9}}; 5686 assertNumberFieldPositions( 5687 message, 5688 result, 5689 expectedFieldPositions); 5690 } 5691 5692 { 5693 String message = "Compact field with bidi mark"; 5694 FormattedNumber result = assertFormatSingle( 5695 message, 5696 "compact-long", 5697 "KK", 5698 NumberFormatter.with().notation(Notation.compactLong()), 5699 new ULocale("he"), // locale with interesting data 5700 6000, 5701 "\u200F6 אלף"); 5702 Object[][] expectedFieldPositions = new Object[][] { 5703 // field, begin index, end index 5704 {NumberFormat.Field.INTEGER, 1, 2}, 5705 {NumberFormat.Field.COMPACT, 3, 6}}; 5706 assertNumberFieldPositions( 5707 message, 5708 result, 5709 expectedFieldPositions); 5710 } 5711 5712 { 5713 String message = "Compact with currency fields"; 5714 FormattedNumber result = assertFormatSingle( 5715 message, 5716 "compact-short currency/USD", 5717 "K currency/USD", 5718 NumberFormatter.with().notation(Notation.compactShort()).unit(USD), 5719 new ULocale("sr_Latn"), // locale with interesting data 5720 65000, 5721 "65 hilj. US$"); 5722 Object[][] expectedFieldPositions = new Object[][] { 5723 // field, begin index, end index 5724 {NumberFormat.Field.INTEGER, 0, 2}, 5725 {NumberFormat.Field.COMPACT, 3, 8}, 5726 {NumberFormat.Field.CURRENCY, 9, 12}}; 5727 assertNumberFieldPositions( 5728 message, 5729 result, 5730 expectedFieldPositions); 5731 } 5732 5733 { 5734 String message = "Currency long name fields"; 5735 FormattedNumber result = assertFormatSingle( 5736 message, 5737 "currency/USD unit-width-full-name", 5738 "currency/USD unit-width-full-name", 5739 NumberFormatter.with().unit(USD) 5740 .unitWidth(UnitWidth.FULL_NAME), 5741 ULocale.ENGLISH, 5742 12345, 5743 "12,345.00 US dollars"); 5744 Object[][] expectedFieldPositions = new Object[][] { 5745 // field, begin index, end index 5746 {NumberFormat.Field.GROUPING_SEPARATOR, 2, 3}, 5747 {NumberFormat.Field.INTEGER, 0, 6}, 5748 {NumberFormat.Field.DECIMAL_SEPARATOR, 6, 7}, 5749 {NumberFormat.Field.FRACTION, 7, 9}, 5750 {NumberFormat.Field.CURRENCY, 10, 20}}; 5751 assertNumberFieldPositions( 5752 message, 5753 result, 5754 expectedFieldPositions); 5755 } 5756 5757 { 5758 String message = "Compact with measure unit fields"; 5759 FormattedNumber result = assertFormatSingle( 5760 message, 5761 "compact-long measure-unit/length-meter unit-width-full-name", 5762 "KK unit/meter unit-width-full-name", 5763 NumberFormatter.with().notation(Notation.compactLong()) 5764 .unit(MeasureUnit.METER) 5765 .unitWidth(UnitWidth.FULL_NAME), 5766 ULocale.US, 5767 65000, 5768 "65 thousand meters"); 5769 Object[][] expectedFieldPositions = new Object[][] { 5770 // field, begin index, end index 5771 {NumberFormat.Field.INTEGER, 0, 2}, 5772 {NumberFormat.Field.COMPACT, 3, 11}, 5773 {NumberFormat.Field.MEASURE_UNIT, 12, 18}}; 5774 assertNumberFieldPositions( 5775 message, 5776 result, 5777 expectedFieldPositions); 5778 } 5779 } 5780 5781 /** Handler for serialization compatibility test suite. */ 5782 public static class FormatHandler implements SerializableTestUtility.Handler { 5783 @Override getTestObjects()5784 public Object[] getTestObjects() { 5785 return new Object[] { 5786 NumberFormatter.withLocale(ULocale.FRENCH).toFormat(), 5787 NumberFormatter.forSkeleton("percent").locale(ULocale.JAPANESE).toFormat(), 5788 NumberFormatter.forSkeleton("scientific .000").locale(ULocale.ENGLISH).toFormat() }; 5789 } 5790 5791 @Override hasSameBehavior(Object a, Object b)5792 public boolean hasSameBehavior(Object a, Object b) { 5793 LocalizedNumberFormatterAsFormat f1 = (LocalizedNumberFormatterAsFormat) a; 5794 LocalizedNumberFormatterAsFormat f2 = (LocalizedNumberFormatterAsFormat) b; 5795 String s1 = f1.format(514.23); 5796 String s2 = f1.format(514.23); 5797 String k1 = f1.getNumberFormatter().toSkeleton(); 5798 String k2 = f2.getNumberFormatter().toSkeleton(); 5799 return s1.equals(s2) && k1.equals(k2); 5800 } 5801 } 5802 5803 @Test toFormat()5804 public void toFormat() { 5805 LocalizedNumberFormatter lnf = NumberFormatter.withLocale(ULocale.FRENCH) 5806 .precision(Precision.fixedFraction(3)); 5807 Format format = lnf.toFormat(); 5808 FieldPosition fpos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR); 5809 StringBuffer sb = new StringBuffer(); 5810 format.format(514.23, sb, fpos); 5811 assertEquals("Should correctly format number", "514,230", sb.toString()); 5812 assertEquals("Should find decimal separator", 3, fpos.getBeginIndex()); 5813 assertEquals("Should find end of decimal separator", 4, fpos.getEndIndex()); 5814 assertEquals("LocalizedNumberFormatter should round-trip", 5815 lnf, 5816 ((LocalizedNumberFormatterAsFormat) format).getNumberFormatter()); 5817 assertEquals("Should produce same character iterator", 5818 lnf.format(514.23).toCharacterIterator().getAttributes(), 5819 format.formatToCharacterIterator(514.23).getAttributes()); 5820 } 5821 5822 @Test plurals()5823 public void plurals() { 5824 // TODO: Expand this test. 5825 5826 assertFormatSingle( 5827 "Plural 1", 5828 "currency/USD precision-integer unit-width-full-name", 5829 "currency/USD . unit-width-full-name", 5830 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).precision(Precision.fixedFraction(0)), 5831 ULocale.ENGLISH, 5832 1, 5833 "1 US dollar"); 5834 5835 assertFormatSingle( 5836 "Plural 1.00", 5837 "currency/USD .00 unit-width-full-name", 5838 "currency/USD .00 unit-width-full-name", 5839 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).precision(Precision.fixedFraction(2)), 5840 ULocale.ENGLISH, 5841 1, 5842 "1.00 US dollars"); 5843 } 5844 5845 @Test validRanges()5846 public void validRanges() throws NoSuchMethodException, IllegalAccessException { 5847 Method[] methodsWithOneArgument = new Method[] { Precision.class.getDeclaredMethod("fixedFraction", Integer.TYPE), 5848 Precision.class.getDeclaredMethod("minFraction", Integer.TYPE), 5849 Precision.class.getDeclaredMethod("maxFraction", Integer.TYPE), 5850 Precision.class.getDeclaredMethod("fixedSignificantDigits", Integer.TYPE), 5851 Precision.class.getDeclaredMethod("minSignificantDigits", Integer.TYPE), 5852 Precision.class.getDeclaredMethod("maxSignificantDigits", Integer.TYPE), 5853 FractionPrecision.class.getDeclaredMethod("withMinDigits", Integer.TYPE), 5854 FractionPrecision.class.getDeclaredMethod("withMaxDigits", Integer.TYPE), 5855 ScientificNotation.class.getDeclaredMethod("withMinExponentDigits", Integer.TYPE), 5856 IntegerWidth.class.getDeclaredMethod("zeroFillTo", Integer.TYPE), 5857 IntegerWidth.class.getDeclaredMethod("truncateAt", Integer.TYPE), }; 5858 Method[] methodsWithTwoArguments = new Method[] { 5859 Precision.class.getDeclaredMethod("minMaxFraction", Integer.TYPE, Integer.TYPE), 5860 Precision.class.getDeclaredMethod("minMaxSignificantDigits", Integer.TYPE, Integer.TYPE), }; 5861 5862 final int EXPECTED_MAX_INT_FRAC_SIG = 999; 5863 final String expectedSubstring0 = "between 0 and 999 (inclusive)"; 5864 final String expectedSubstring1 = "between 1 and 999 (inclusive)"; 5865 final String expectedSubstringN1 = "between -1 and 999 (inclusive)"; 5866 5867 // We require that the upper bounds all be 999 inclusive. 5868 // The lower bound may be either -1, 0, or 1. 5869 Set<String> methodsWithLowerBound1 = new HashSet(); 5870 methodsWithLowerBound1.add("fixedSignificantDigits"); 5871 methodsWithLowerBound1.add("minSignificantDigits"); 5872 methodsWithLowerBound1.add("maxSignificantDigits"); 5873 methodsWithLowerBound1.add("minMaxSignificantDigits"); 5874 methodsWithLowerBound1.add("withMinDigits"); 5875 methodsWithLowerBound1.add("withMaxDigits"); 5876 methodsWithLowerBound1.add("withMinExponentDigits"); 5877 // Methods with lower bound 0: 5878 // fixedFraction 5879 // minFraction 5880 // maxFraction 5881 // minMaxFraction 5882 // zeroFillTo 5883 Set<String> methodsWithLowerBoundN1 = new HashSet(); 5884 methodsWithLowerBoundN1.add("truncateAt"); 5885 5886 // Some of the methods require an object to be called upon. 5887 Map<String, Object> targets = new HashMap<>(); 5888 targets.put("withMinDigits", Precision.integer()); 5889 targets.put("withMaxDigits", Precision.integer()); 5890 targets.put("withMinExponentDigits", Notation.scientific()); 5891 targets.put("truncateAt", IntegerWidth.zeroFillTo(0)); 5892 5893 for (int argument = -2; argument <= EXPECTED_MAX_INT_FRAC_SIG + 2; argument++) { 5894 for (Method method : methodsWithOneArgument) { 5895 String message = "i = " + argument + "; method = " + method.getName(); 5896 int lowerBound = methodsWithLowerBound1.contains(method.getName()) ? 1 5897 : methodsWithLowerBoundN1.contains(method.getName()) ? -1 : 0; 5898 String expectedSubstring = lowerBound == 0 ? expectedSubstring0 5899 : lowerBound == 1 ? expectedSubstring1 : expectedSubstringN1; 5900 Object target = targets.get(method.getName()); 5901 try { 5902 method.invoke(target, argument); 5903 assertTrue(message, argument >= lowerBound && argument <= EXPECTED_MAX_INT_FRAC_SIG); 5904 } catch (InvocationTargetException e) { 5905 assertTrue(message, argument < lowerBound || argument > EXPECTED_MAX_INT_FRAC_SIG); 5906 // Ensure the exception message contains the expected substring 5907 String actualMessage = e.getCause().getMessage(); 5908 assertNotEquals(message + ": " + actualMessage + "; " + expectedSubstring 5909 , -1, actualMessage.indexOf(expectedSubstring)); 5910 } 5911 } 5912 for (Method method : methodsWithTwoArguments) { 5913 String message = "i = " + argument + "; method = " + method.getName(); 5914 int lowerBound = methodsWithLowerBound1.contains(method.getName()) ? 1 5915 : methodsWithLowerBoundN1.contains(method.getName()) ? -1 : 0; 5916 String expectedSubstring = lowerBound == 0 ? expectedSubstring0 : expectedSubstring1; 5917 Object target = targets.get(method.getName()); 5918 // Check range on the first argument 5919 try { 5920 // Pass EXPECTED_MAX_INT_FRAC_SIG as the second argument so arg1 <= arg2 in expected cases 5921 method.invoke(target, argument, EXPECTED_MAX_INT_FRAC_SIG); 5922 assertTrue(message, argument >= lowerBound && argument <= EXPECTED_MAX_INT_FRAC_SIG); 5923 } catch (InvocationTargetException e) { 5924 assertTrue(message, argument < lowerBound || argument > EXPECTED_MAX_INT_FRAC_SIG); 5925 // Ensure the exception message contains the expected substring 5926 String actualMessage = e.getCause().getMessage(); 5927 assertNotEquals(message + ": " + actualMessage, -1, actualMessage.indexOf(expectedSubstring)); 5928 } 5929 // Check range on the second argument 5930 try { 5931 // Pass lowerBound as the first argument so arg1 <= arg2 in expected cases 5932 method.invoke(target, lowerBound, argument); 5933 assertTrue(message, argument >= lowerBound && argument <= EXPECTED_MAX_INT_FRAC_SIG); 5934 } catch (InvocationTargetException e) { 5935 assertTrue(message, argument < lowerBound || argument > EXPECTED_MAX_INT_FRAC_SIG); 5936 // Ensure the exception message contains the expected substring 5937 String actualMessage = e.getCause().getMessage(); 5938 assertNotEquals(message + ": " + actualMessage, -1, actualMessage.indexOf(expectedSubstring)); 5939 } 5940 // Check that first argument must be less than or equal to second argument 5941 try { 5942 method.invoke(target, argument, argument - 1); 5943 org.junit.Assert.fail(); 5944 } catch (InvocationTargetException e) { 5945 // Pass 5946 } 5947 } 5948 } 5949 5950 // Check first argument less than or equal to second argument on IntegerWidth 5951 try { 5952 IntegerWidth.zeroFillTo(4).truncateAt(2); 5953 org.junit.Assert.fail(); 5954 } catch (IllegalArgumentException e) { 5955 // Pass 5956 } 5957 } 5958 5959 @Test formatUnitsAliases()5960 public void formatUnitsAliases() { 5961 5962 class TestCase { 5963 final MeasureUnit measureUnit; 5964 final String expectedFormat; 5965 5966 TestCase(MeasureUnit measureUnit, String expectedFormat) { 5967 this.measureUnit = measureUnit; 5968 this.expectedFormat = expectedFormat; 5969 } 5970 } 5971 5972 TestCase[] testCases = { 5973 // Aliases 5974 new TestCase(MeasureUnit.MILLIGRAM_PER_DECILITER, "2 milligrams per deciliter"), 5975 new TestCase(MeasureUnit.LITER_PER_100KILOMETERS, "2 liters per 100 kilometers"), 5976 new TestCase(MeasureUnit.PART_PER_MILLION, "2 parts per million"), 5977 new TestCase(MeasureUnit.MILLIMETER_OF_MERCURY, "2 millimeters of mercury"), 5978 5979 // Replacements 5980 new TestCase(MeasureUnit.MILLIGRAM_OFGLUCOSE_PER_DECILITER, "2 milligrams per deciliter"), 5981 new TestCase(MeasureUnit.forIdentifier("millimeter-ofhg"), "2 millimeters of mercury"), 5982 new TestCase(MeasureUnit.forIdentifier("liter-per-100-kilometer"), "2 liters per 100 kilometers"), 5983 new TestCase(MeasureUnit.forIdentifier("permillion"), "2 parts per million"), 5984 }; 5985 5986 for (TestCase testCase : testCases) { 5987 String actualFormat = NumberFormatter 5988 .withLocale(ULocale.ENGLISH) 5989 .unit(testCase.measureUnit) 5990 .unitWidth(UnitWidth.FULL_NAME) 5991 .format(2.0) 5992 .toString(); 5993 5994 assertEquals("test unit aliases", testCase.expectedFormat, actualFormat); 5995 } 5996 } 5997 assertFormatDescending( String message, String skeleton, String conciseSkeleton, UnlocalizedNumberFormatter f, ULocale locale, String... expected)5998 static void assertFormatDescending( 5999 String message, 6000 String skeleton, 6001 String conciseSkeleton, 6002 UnlocalizedNumberFormatter f, 6003 ULocale locale, 6004 String... expected) { 6005 final double[] inputs = new double[] { 87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0 }; 6006 assertFormatDescending(message, skeleton, conciseSkeleton, f, locale, inputs, expected); 6007 } 6008 assertFormatDescendingBig( String message, String skeleton, String conciseSkeleton, UnlocalizedNumberFormatter f, ULocale locale, String... expected)6009 static void assertFormatDescendingBig( 6010 String message, 6011 String skeleton, 6012 String conciseSkeleton, 6013 UnlocalizedNumberFormatter f, 6014 ULocale locale, 6015 String... expected) { 6016 final double[] inputs = new double[] { 87650000, 8765000, 876500, 87650, 8765, 876.5, 87.65, 8.765, 0 }; 6017 assertFormatDescending(message, skeleton, conciseSkeleton, f, locale, inputs, expected); 6018 } 6019 assertFormatDescending( String message, String skeleton, String conciseSkeleton, UnlocalizedNumberFormatter f, ULocale locale, double[] inputs, String... expected)6020 static void assertFormatDescending( 6021 String message, 6022 String skeleton, 6023 String conciseSkeleton, 6024 UnlocalizedNumberFormatter f, 6025 ULocale locale, 6026 double[] inputs, 6027 String... expected) { 6028 assert expected.length == 9; 6029 LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation 6030 LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation 6031 for (int i = 0; i < 9; i++) { 6032 double d = inputs[i]; 6033 String actual1 = l1.format(d).toString(); 6034 assertEquals(message + ": Unsafe Path: " + d, expected[i], actual1); 6035 String actual2 = l2.format(d).toString(); 6036 assertEquals(message + ": Safe Path: " + d, expected[i], actual2); 6037 } 6038 if (skeleton != null) { // if null, skeleton is declared as undefined. 6039 // Only compare normalized skeletons: the tests need not provide the normalized forms. 6040 // Use the normalized form to construct the testing formatter to guarantee no loss of info. 6041 String normalized = NumberFormatter.forSkeleton(skeleton).toSkeleton(); 6042 assertEquals(message + ": Skeleton:", normalized, f.toSkeleton()); 6043 LocalizedNumberFormatter l3 = NumberFormatter.forSkeleton(normalized).locale(locale); 6044 for (int i = 0; i < 9; i++) { 6045 double d = inputs[i]; 6046 String actual3 = l3.format(d).toString(); 6047 assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3); 6048 } 6049 // Concise skeletons should have same output, and usually round-trip to the normalized skeleton. 6050 // If the concise skeleton starts with '~', disable the round-trip check. 6051 boolean shouldRoundTrip = true; 6052 if (conciseSkeleton.length() > 0 && conciseSkeleton.charAt(0) == '~') { 6053 conciseSkeleton = conciseSkeleton.substring(1); 6054 shouldRoundTrip = false; 6055 } 6056 LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale); 6057 if (shouldRoundTrip) { 6058 assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton()); 6059 } 6060 for (int i = 0; i < 9; i++) { 6061 double d = inputs[i]; 6062 String actual4 = l4.format(d).toString(); 6063 assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + d, expected[i], actual4); 6064 } 6065 } else { 6066 assertUndefinedSkeleton(f); 6067 } 6068 } 6069 assertFormatSingle( String message, String skeleton, String conciseSkeleton, UnlocalizedNumberFormatter f, ULocale locale, Number input, String expected)6070 static FormattedNumber assertFormatSingle( 6071 String message, 6072 String skeleton, 6073 String conciseSkeleton, 6074 UnlocalizedNumberFormatter f, 6075 ULocale locale, 6076 Number input, 6077 String expected) { 6078 LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation 6079 LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation 6080 FormattedNumber result1 = l1.format(input); 6081 String actual1 = result1.toString(); 6082 assertEquals(message + ": Unsafe Path: " + input, expected, actual1); 6083 String actual2 = l2.format(input).toString(); 6084 assertEquals(message + ": Safe Path: " + input, expected, actual2); 6085 if (skeleton != null) { // if null, skeleton is declared as undefined. 6086 // Only compare normalized skeletons: the tests need not provide the normalized forms. 6087 // Use the normalized form to construct the testing formatter to ensure no loss of info. 6088 String normalized = NumberFormatter.forSkeleton(skeleton).toSkeleton(); 6089 assertEquals(message + ": Skeleton:", normalized, f.toSkeleton()); 6090 LocalizedNumberFormatter l3 = NumberFormatter.forSkeleton(normalized).locale(locale); 6091 String actual3 = l3.format(input).toString(); 6092 assertEquals(message + ": Skeleton Path: " + input, expected, actual3); 6093 // Concise skeletons should have same output, and usually round-trip to the normalized skeleton. 6094 // If the concise skeleton starts with '~', disable the round-trip check. 6095 boolean shouldRoundTrip = true; 6096 if (conciseSkeleton.length() > 0 && conciseSkeleton.charAt(0) == '~') { 6097 conciseSkeleton = conciseSkeleton.substring(1); 6098 shouldRoundTrip = false; 6099 } 6100 LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale); 6101 if (shouldRoundTrip) { 6102 assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton()); 6103 } 6104 String actual4 = l4.format(input).toString(); 6105 assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + input, expected, actual4); 6106 } else { 6107 assertUndefinedSkeleton(f); 6108 } 6109 return result1; 6110 } 6111 assertFormatSingleMeasure( String message, String skeleton, String conciseSkeleton, UnlocalizedNumberFormatter f, ULocale locale, Measure input, String expected)6112 static void assertFormatSingleMeasure( 6113 String message, 6114 String skeleton, 6115 String conciseSkeleton, 6116 UnlocalizedNumberFormatter f, 6117 ULocale locale, 6118 Measure input, 6119 String expected) { 6120 LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation 6121 LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation 6122 String actual1 = l1.format(input).toString(); 6123 assertEquals(message + ": Unsafe Path: " + input, expected, actual1); 6124 String actual2 = l2.format(input).toString(); 6125 assertEquals(message + ": Safe Path: " + input, expected, actual2); 6126 if (skeleton != null) { // if null, skeleton is declared as undefined. 6127 // Only compare normalized skeletons: the tests need not provide the normalized forms. 6128 // Use the normalized form to construct the testing formatter to ensure no loss of info. 6129 String normalized = NumberFormatter.forSkeleton(skeleton).toSkeleton(); 6130 assertEquals(message + ": Skeleton:", normalized, f.toSkeleton()); 6131 LocalizedNumberFormatter l3 = NumberFormatter.forSkeleton(normalized).locale(locale); 6132 String actual3 = l3.format(input).toString(); 6133 assertEquals(message + ": Skeleton Path: " + input, expected, actual3); 6134 // Concise skeletons should have same output, and usually round-trip to the normalized skeleton. 6135 // If the concise skeleton starts with '~', disable the round-trip check. 6136 boolean shouldRoundTrip = true; 6137 if (conciseSkeleton.length() > 0 && conciseSkeleton.charAt(0) == '~') { 6138 conciseSkeleton = conciseSkeleton.substring(1); 6139 shouldRoundTrip = false; 6140 } 6141 6142 LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale); 6143 if (shouldRoundTrip) { 6144 assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton()); 6145 } 6146 String actual4 = l4.format(input).toString(); 6147 assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + input, expected, actual4); 6148 } else { 6149 assertUndefinedSkeleton(f); 6150 } 6151 } 6152 assertUndefinedSkeleton(UnlocalizedNumberFormatter f)6153 static void assertUndefinedSkeleton(UnlocalizedNumberFormatter f) { 6154 try { 6155 String skeleton = f.toSkeleton(); 6156 fail("Expected toSkeleton to fail, but it passed, producing: " + skeleton); 6157 } catch (UnsupportedOperationException expected) {} 6158 } 6159 assertNumberFieldPositions(String message, FormattedNumber result, Object[][] expectedFieldPositions)6160 private void assertNumberFieldPositions(String message, FormattedNumber result, Object[][] expectedFieldPositions) { 6161 FormattedValueTest.checkFormattedValue(message, result, result.toString(), expectedFieldPositions); 6162 } 6163 } 6164