1 /*
   2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * This file is available under and governed by the GNU General Public
  26  * License version 2 only, as published by the Free Software Foundation.
  27  * However, the following notice accompanied the original version of this
  28  * file:
  29  *
  30  * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
  31  *
  32  * All rights reserved.
  33  *
  34  * Redistribution and use in source and binary forms, with or without
  35  * modification, are permitted provided that the following conditions are met:
  36  *
  37  *  * Redistributions of source code must retain the above copyright notice,
  38  *    this list of conditions and the following disclaimer.
  39  *
  40  *  * Redistributions in binary form must reproduce the above copyright notice,
  41  *    this list of conditions and the following disclaimer in the documentation
  42  *    and/or other materials provided with the distribution.
  43  *
  44  *  * Neither the name of JSR-310 nor the names of its contributors
  45  *    may be used to endorse or promote products derived from this software
  46  *    without specific prior written permission.
  47  *
  48  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  49  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  50  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  51  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  52  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  53  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  54  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  55  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  56  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  57  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  58  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  59  */
  60 package test.java.time.format;
  61 
  62 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
  63 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
  64 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
  65 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
  66 import static java.time.temporal.ChronoField.YEAR;
  67 import static org.testng.Assert.assertEquals;
  68 import static org.testng.Assert.assertNotNull;
  69 
  70 import java.text.ParsePosition;
  71 import java.time.LocalDate;
  72 import java.time.YearMonth;
  73 import java.time.ZoneOffset;
  74 import java.time.chrono.Chronology;
  75 import java.time.chrono.IsoChronology;
  76 import java.time.chrono.JapaneseChronology;
  77 import java.time.chrono.MinguoChronology;
  78 import java.time.format.DateTimeFormatter;
  79 import java.time.format.DateTimeFormatterBuilder;
  80 import java.time.format.FormatStyle;
  81 import java.time.format.SignStyle;
  82 import java.time.format.TextStyle;
  83 import java.time.temporal.Temporal;
  84 import java.time.temporal.TemporalAccessor;
  85 import java.util.HashMap;
  86 import java.util.Locale;
  87 import java.util.Map;
  88 
  89 import org.testng.annotations.BeforeMethod;
  90 import org.testng.annotations.DataProvider;
  91 import org.testng.annotations.Test;
  92 
  93 /**
  94  * Test DateTimeFormatterBuilder.
  95  */
  96 @Test
  97 public class TestDateTimeFormatterBuilder {
  98 
  99     private DateTimeFormatterBuilder builder;
 100 
 101     @BeforeMethod
 102     public void setUp() {
 103         builder = new DateTimeFormatterBuilder();
 104     }
 105 
 106     //-----------------------------------------------------------------------
 107     @Test
 108     public void test_toFormatter_empty() throws Exception {
 109         DateTimeFormatter f = builder.toFormatter();
 110         assertEquals(f.toString(), "");
 111     }
 112 
 113     //-----------------------------------------------------------------------
 114     @Test
 115     public void test_parseCaseSensitive() throws Exception {
 116         builder.parseCaseSensitive();
 117         DateTimeFormatter f = builder.toFormatter();
 118         assertEquals(f.toString(), "ParseCaseSensitive(true)");
 119     }
 120 
 121     @Test
 122     public void test_parseCaseInsensitive() throws Exception {
 123         builder.parseCaseInsensitive();
 124         DateTimeFormatter f = builder.toFormatter();
 125         assertEquals(f.toString(), "ParseCaseSensitive(false)");
 126     }
 127 
 128     //-----------------------------------------------------------------------
 129     @Test
 130     public void test_parseStrict() throws Exception {
 131         builder.parseStrict();
 132         DateTimeFormatter f = builder.toFormatter();
 133         assertEquals(f.toString(), "ParseStrict(true)");
 134     }
 135 
 136     @Test
 137     public void test_parseLenient() throws Exception {
 138         builder.parseLenient();
 139         DateTimeFormatter f = builder.toFormatter();
 140         assertEquals(f.toString(), "ParseStrict(false)");
 141     }
 142 
 143     //-----------------------------------------------------------------------
 144     @Test
 145     public void test_appendValue_1arg() throws Exception {
 146         builder.appendValue(DAY_OF_MONTH);
 147         DateTimeFormatter f = builder.toFormatter();
 148         assertEquals(f.toString(), "Value(DayOfMonth)");
 149     }
 150 
 151     @Test(expectedExceptions=NullPointerException.class)
 152     public void test_appendValue_1arg_null() throws Exception {
 153         builder.appendValue(null);
 154     }
 155 
 156     //-----------------------------------------------------------------------
 157     @Test
 158     public void test_appendValue_2arg() throws Exception {
 159         builder.appendValue(DAY_OF_MONTH, 3);
 160         DateTimeFormatter f = builder.toFormatter();
 161         assertEquals(f.toString(), "Value(DayOfMonth,3)");
 162     }
 163 
 164     @Test(expectedExceptions=NullPointerException.class)
 165     public void test_appendValue_2arg_null() throws Exception {
 166         builder.appendValue(null, 3);
 167     }
 168 
 169     @Test(expectedExceptions=IllegalArgumentException.class)
 170     public void test_appendValue_2arg_widthTooSmall() throws Exception {
 171         builder.appendValue(DAY_OF_MONTH, 0);
 172     }
 173 
 174     @Test(expectedExceptions=IllegalArgumentException.class)
 175     public void test_appendValue_2arg_widthTooBig() throws Exception {
 176         builder.appendValue(DAY_OF_MONTH, 20);
 177     }
 178 
 179     //-----------------------------------------------------------------------
 180     @Test
 181     public void test_appendValue_3arg() throws Exception {
 182         builder.appendValue(DAY_OF_MONTH, 2, 3, SignStyle.NORMAL);
 183         DateTimeFormatter f = builder.toFormatter();
 184         assertEquals(f.toString(), "Value(DayOfMonth,2,3,NORMAL)");
 185     }
 186 
 187     @Test(expectedExceptions=NullPointerException.class)
 188     public void test_appendValue_3arg_nullField() throws Exception {
 189         builder.appendValue(null, 2, 3, SignStyle.NORMAL);
 190     }
 191 
 192     @Test(expectedExceptions=IllegalArgumentException.class)
 193     public void test_appendValue_3arg_minWidthTooSmall() throws Exception {
 194         builder.appendValue(DAY_OF_MONTH, 0, 2, SignStyle.NORMAL);
 195     }
 196 
 197     @Test(expectedExceptions=IllegalArgumentException.class)
 198     public void test_appendValue_3arg_minWidthTooBig() throws Exception {
 199         builder.appendValue(DAY_OF_MONTH, 20, 2, SignStyle.NORMAL);
 200     }
 201 
 202     @Test(expectedExceptions=IllegalArgumentException.class)
 203     public void test_appendValue_3arg_maxWidthTooSmall() throws Exception {
 204         builder.appendValue(DAY_OF_MONTH, 2, 0, SignStyle.NORMAL);
 205     }
 206 
 207     @Test(expectedExceptions=IllegalArgumentException.class)
 208     public void test_appendValue_3arg_maxWidthTooBig() throws Exception {
 209         builder.appendValue(DAY_OF_MONTH, 2, 20, SignStyle.NORMAL);
 210     }
 211 
 212     @Test(expectedExceptions=IllegalArgumentException.class)
 213     public void test_appendValue_3arg_maxWidthMinWidth() throws Exception {
 214         builder.appendValue(DAY_OF_MONTH, 4, 2, SignStyle.NORMAL);
 215     }
 216 
 217     @Test(expectedExceptions=NullPointerException.class)
 218     public void test_appendValue_3arg_nullSignStyle() throws Exception {
 219         builder.appendValue(DAY_OF_MONTH, 2, 3, null);
 220     }
 221 
 222     //-----------------------------------------------------------------------
 223     @Test
 224     public void test_appendValue_subsequent2_parse3() throws Exception {
 225         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2);
 226         DateTimeFormatter f = builder.toFormatter();
 227         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)");
 228         TemporalAccessor parsed = f.parseUnresolved("123", new ParsePosition(0));
 229         assertEquals(parsed.getLong(MONTH_OF_YEAR), 1L);
 230         assertEquals(parsed.getLong(DAY_OF_MONTH), 23L);
 231     }
 232 
 233     @Test
 234     public void test_appendValue_subsequent2_parse4() throws Exception {
 235         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2);
 236         DateTimeFormatter f = builder.toFormatter();
 237         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)");
 238         TemporalAccessor parsed = f.parseUnresolved("0123", new ParsePosition(0));
 239         assertEquals(parsed.getLong(MONTH_OF_YEAR), 1L);
 240         assertEquals(parsed.getLong(DAY_OF_MONTH), 23L);
 241     }
 242 
 243     @Test
 244     public void test_appendValue_subsequent2_parse5() throws Exception {
 245         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2).appendLiteral('4');
 246         DateTimeFormatter f = builder.toFormatter();
 247         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)'4'");
 248         TemporalAccessor parsed = f.parseUnresolved("01234", new ParsePosition(0));
 249         assertEquals(parsed.getLong(MONTH_OF_YEAR), 1L);
 250         assertEquals(parsed.getLong(DAY_OF_MONTH), 23L);
 251     }
 252 
 253     @Test
 254     public void test_appendValue_subsequent3_parse6() throws Exception {
 255         builder
 256             .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
 257             .appendValue(MONTH_OF_YEAR, 2)
 258             .appendValue(DAY_OF_MONTH, 2);
 259         DateTimeFormatter f = builder.toFormatter();
 260         assertEquals(f.toString(), "Value(Year,4,10,EXCEEDS_PAD)Value(MonthOfYear,2)Value(DayOfMonth,2)");
 261         TemporalAccessor parsed = f.parseUnresolved("20090630", new ParsePosition(0));
 262         assertEquals(parsed.getLong(YEAR), 2009L);
 263         assertEquals(parsed.getLong(MONTH_OF_YEAR), 6L);
 264         assertEquals(parsed.getLong(DAY_OF_MONTH), 30L);
 265     }
 266 
 267     //-----------------------------------------------------------------------
 268     @Test(expectedExceptions=NullPointerException.class)
 269     public void test_appendValueReduced_null() throws Exception {
 270         builder.appendValueReduced(null, 2, 2, 2000);
 271     }
 272 
 273     @Test
 274     public void test_appendValueReduced() throws Exception {
 275         builder.appendValueReduced(YEAR, 2, 2, 2000);
 276         DateTimeFormatter f = builder.toFormatter();
 277         assertEquals(f.toString(), "ReducedValue(Year,2,2,2000)");
 278         TemporalAccessor parsed = f.parseUnresolved("12", new ParsePosition(0));
 279         assertEquals(parsed.getLong(YEAR), 2012L);
 280     }
 281 
 282     @Test
 283     public void test_appendValueReduced_subsequent_parse() throws Exception {
 284         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValueReduced(YEAR, 2, 2, 2000);
 285         DateTimeFormatter f = builder.toFormatter();
 286         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)ReducedValue(Year,2,2,2000)");
 287         ParsePosition ppos = new ParsePosition(0);
 288         TemporalAccessor parsed = f.parseUnresolved("123", ppos);
 289         assertNotNull(parsed, "Parse failed: " + ppos.toString());
 290         assertEquals(parsed.getLong(MONTH_OF_YEAR), 1L);
 291         assertEquals(parsed.getLong(YEAR), 2023L);
 292     }
 293 
 294     //-----------------------------------------------------------------------
 295     //-----------------------------------------------------------------------
 296     //-----------------------------------------------------------------------
 297     @Test
 298     public void test_appendFraction_4arg() throws Exception {
 299         builder.appendFraction(MINUTE_OF_HOUR, 1, 9, false);
 300         DateTimeFormatter f = builder.toFormatter();
 301         assertEquals(f.toString(), "Fraction(MinuteOfHour,1,9)");
 302     }
 303 
 304     @Test(expectedExceptions=NullPointerException.class)
 305     public void test_appendFraction_4arg_nullRule() throws Exception {
 306         builder.appendFraction(null, 1, 9, false);
 307     }
 308 
 309     @Test(expectedExceptions=IllegalArgumentException.class)
 310     public void test_appendFraction_4arg_invalidRuleNotFixedSet() throws Exception {
 311         builder.appendFraction(DAY_OF_MONTH, 1, 9, false);
 312     }
 313 
 314     @Test(expectedExceptions=IllegalArgumentException.class)
 315     public void test_appendFraction_4arg_minTooSmall() throws Exception {
 316         builder.appendFraction(MINUTE_OF_HOUR, -1, 9, false);
 317     }
 318 
 319     @Test(expectedExceptions=IllegalArgumentException.class)
 320     public void test_appendFraction_4arg_minTooBig() throws Exception {
 321         builder.appendFraction(MINUTE_OF_HOUR, 10, 9, false);
 322     }
 323 
 324     @Test(expectedExceptions=IllegalArgumentException.class)
 325     public void test_appendFraction_4arg_maxTooSmall() throws Exception {
 326         builder.appendFraction(MINUTE_OF_HOUR, 0, -1, false);
 327     }
 328 
 329     @Test(expectedExceptions=IllegalArgumentException.class)
 330     public void test_appendFraction_4arg_maxTooBig() throws Exception {
 331         builder.appendFraction(MINUTE_OF_HOUR, 1, 10, false);
 332     }
 333 
 334     @Test(expectedExceptions=IllegalArgumentException.class)
 335     public void test_appendFraction_4arg_maxWidthMinWidth() throws Exception {
 336         builder.appendFraction(MINUTE_OF_HOUR, 9, 3, false);
 337     }
 338 
 339     //-----------------------------------------------------------------------
 340     //-----------------------------------------------------------------------
 341     //-----------------------------------------------------------------------
 342     @Test
 343     public void test_appendText_1arg() throws Exception {
 344         builder.appendText(MONTH_OF_YEAR);
 345         DateTimeFormatter f = builder.toFormatter();
 346         assertEquals(f.toString(), "Text(MonthOfYear)");
 347     }
 348 
 349     @Test(expectedExceptions=NullPointerException.class)
 350     public void test_appendText_1arg_null() throws Exception {
 351         builder.appendText(null);
 352     }
 353 
 354     //-----------------------------------------------------------------------
 355     @Test
 356     public void test_appendText_2arg() throws Exception {
 357         builder.appendText(MONTH_OF_YEAR, TextStyle.SHORT);
 358         DateTimeFormatter f = builder.toFormatter();
 359         assertEquals(f.toString(), "Text(MonthOfYear,SHORT)");
 360     }
 361 
 362     @Test(expectedExceptions=NullPointerException.class)
 363     public void test_appendText_2arg_nullRule() throws Exception {
 364         builder.appendText(null, TextStyle.SHORT);
 365     }
 366 
 367     @Test(expectedExceptions=NullPointerException.class)
 368     public void test_appendText_2arg_nullStyle() throws Exception {
 369         builder.appendText(MONTH_OF_YEAR, (TextStyle) null);
 370     }
 371 
 372     //-----------------------------------------------------------------------
 373     @Test
 374     public void test_appendTextMap() throws Exception {
 375         Map<Long, String> map = new HashMap<>();
 376         map.put(1L, "JNY");
 377         map.put(2L, "FBY");
 378         map.put(3L, "MCH");
 379         map.put(4L, "APL");
 380         map.put(5L, "MAY");
 381         map.put(6L, "JUN");
 382         map.put(7L, "JLY");
 383         map.put(8L, "AGT");
 384         map.put(9L, "SPT");
 385         map.put(10L, "OBR");
 386         map.put(11L, "NVR");
 387         map.put(12L, "DBR");
 388         builder.appendText(MONTH_OF_YEAR, map);
 389         DateTimeFormatter f = builder.toFormatter();
 390         assertEquals(f.toString(), "Text(MonthOfYear)");  // TODO: toString should be different?
 391     }
 392 
 393     @Test(expectedExceptions=NullPointerException.class)
 394     public void test_appendTextMap_nullRule() throws Exception {
 395         builder.appendText(null, new HashMap<Long, String>());
 396     }
 397 
 398     @Test(expectedExceptions=NullPointerException.class)
 399     public void test_appendTextMap_nullStyle() throws Exception {
 400         builder.appendText(MONTH_OF_YEAR, (Map<Long, String>) null);
 401     }
 402 
 403     //-----------------------------------------------------------------------
 404     //-----------------------------------------------------------------------
 405     //-----------------------------------------------------------------------
 406     @Test
 407     public void test_appendOffsetId() throws Exception {
 408         builder.appendOffsetId();
 409         DateTimeFormatter f = builder.toFormatter();
 410         assertEquals(f.toString(), "Offset(+HH:MM:ss,'Z')");
 411     }
 412 
 413     @DataProvider(name="offsetPatterns")
 414     Object[][] data_offsetPatterns() {
 415         return new Object[][] {
 416                 {"+HH", 2, 0, 0, "+02"},
 417                 {"+HH", -2, 0, 0, "-02"},
 418                 {"+HH", 2, 30, 0, "+02"},
 419                 {"+HH", 2, 0, 45, "+02"},
 420                 {"+HH", 2, 30, 45, "+02"},
 421 
 422                 {"+HHMM", 2, 0, 0, "+0200"},
 423                 {"+HHMM", -2, 0, 0, "-0200"},
 424                 {"+HHMM", 2, 30, 0, "+0230"},
 425                 {"+HHMM", 2, 0, 45, "+0200"},
 426                 {"+HHMM", 2, 30, 45, "+0230"},
 427 
 428                 {"+HH:MM", 2, 0, 0, "+02:00"},
 429                 {"+HH:MM", -2, 0, 0, "-02:00"},
 430                 {"+HH:MM", 2, 30, 0, "+02:30"},
 431                 {"+HH:MM", 2, 0, 45, "+02:00"},
 432                 {"+HH:MM", 2, 30, 45, "+02:30"},
 433 
 434                 {"+HHMMss", 2, 0, 0, "+0200"},
 435                 {"+HHMMss", -2, 0, 0, "-0200"},
 436                 {"+HHMMss", 2, 30, 0, "+0230"},
 437                 {"+HHMMss", 2, 0, 45, "+020045"},
 438                 {"+HHMMss", 2, 30, 45, "+023045"},
 439 
 440                 {"+HH:MM:ss", 2, 0, 0, "+02:00"},
 441                 {"+HH:MM:ss", -2, 0, 0, "-02:00"},
 442                 {"+HH:MM:ss", 2, 30, 0, "+02:30"},
 443                 {"+HH:MM:ss", 2, 0, 45, "+02:00:45"},
 444                 {"+HH:MM:ss", 2, 30, 45, "+02:30:45"},
 445 
 446                 {"+HHMMSS", 2, 0, 0, "+020000"},
 447                 {"+HHMMSS", -2, 0, 0, "-020000"},
 448                 {"+HHMMSS", 2, 30, 0, "+023000"},
 449                 {"+HHMMSS", 2, 0, 45, "+020045"},
 450                 {"+HHMMSS", 2, 30, 45, "+023045"},
 451 
 452                 {"+HH:MM:SS", 2, 0, 0, "+02:00:00"},
 453                 {"+HH:MM:SS", -2, 0, 0, "-02:00:00"},
 454                 {"+HH:MM:SS", 2, 30, 0, "+02:30:00"},
 455                 {"+HH:MM:SS", 2, 0, 45, "+02:00:45"},
 456                 {"+HH:MM:SS", 2, 30, 45, "+02:30:45"},
 457         };
 458     }
 459 
 460     @Test(dataProvider="offsetPatterns")
 461     public void test_appendOffset_format(String pattern, int h, int m, int s, String expected) throws Exception {
 462         builder.appendOffset(pattern, "Z");
 463         DateTimeFormatter f = builder.toFormatter();
 464         ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(h, m, s);
 465         assertEquals(f.format(offset), expected);
 466     }
 467 
 468     @Test(dataProvider="offsetPatterns")
 469     public void test_appendOffset_parse(String pattern, int h, int m, int s, String expected) throws Exception {
 470         builder.appendOffset(pattern, "Z");
 471         DateTimeFormatter f = builder.toFormatter();
 472         ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(h, m, s);
 473         ZoneOffset parsed = f.parse(expected, ZoneOffset::from);
 474         assertEquals(f.format(parsed), expected);
 475     }
 476 
 477     @DataProvider(name="badOffsetPatterns")
 478     Object[][] data_badOffsetPatterns() {
 479         return new Object[][] {
 480             {"HH"},
 481             {"HHMM"},
 482             {"HH:MM"},
 483             {"HHMMss"},
 484             {"HH:MM:ss"},
 485             {"HHMMSS"},
 486             {"HH:MM:SS"},
 487             {"+H"},
 488             {"+HMM"},
 489             {"+HHM"},
 490             {"+A"},
 491         };
 492     }
 493 
 494     @Test(dataProvider="badOffsetPatterns", expectedExceptions=IllegalArgumentException.class)
 495     public void test_appendOffset_badPattern(String pattern) throws Exception {
 496         builder.appendOffset(pattern, "Z");
 497     }
 498 
 499     @Test(expectedExceptions=NullPointerException.class)
 500     public void test_appendOffset_3arg_nullText() throws Exception {
 501         builder.appendOffset("+HH:MM", null);
 502     }
 503 
 504     @Test(expectedExceptions=NullPointerException.class)
 505     public void test_appendOffset_3arg_nullPattern() throws Exception {
 506         builder.appendOffset(null, "Z");
 507     }
 508 
 509     //-----------------------------------------------------------------------
 510     //-----------------------------------------------------------------------
 511     //-----------------------------------------------------------------------
 512     @Test
 513     public void test_appendZoneId() throws Exception {
 514         builder.appendZoneId();
 515         DateTimeFormatter f = builder.toFormatter();
 516         assertEquals(f.toString(), "ZoneId()");
 517     }
 518 
 519     @Test
 520     public void test_appendZoneText_1arg() throws Exception {
 521         builder.appendZoneText(TextStyle.FULL);
 522         DateTimeFormatter f = builder.toFormatter();
 523         assertEquals(f.toString(), "ZoneText(FULL)");
 524     }
 525 
 526     @Test(expectedExceptions=NullPointerException.class)
 527     public void test_appendZoneText_1arg_nullText() throws Exception {
 528         builder.appendZoneText(null);
 529     }
 530 
 531     //-----------------------------------------------------------------------
 532     //-----------------------------------------------------------------------
 533     //-----------------------------------------------------------------------
 534     @Test
 535     public void test_padNext_1arg() {
 536         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':').padNext(2).appendValue(DAY_OF_MONTH);
 537         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2: 1");
 538     }
 539 
 540     @Test(expectedExceptions=IllegalArgumentException.class)
 541     public void test_padNext_1arg_invalidWidth() throws Exception {
 542         builder.padNext(0);
 543     }
 544 
 545     //-----------------------------------------------------------------------
 546     @Test
 547     public void test_padNext_2arg_dash() throws Exception {
 548         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':').padNext(2, '-').appendValue(DAY_OF_MONTH);
 549         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2:-1");
 550     }
 551 
 552     @Test(expectedExceptions=IllegalArgumentException.class)
 553     public void test_padNext_2arg_invalidWidth() throws Exception {
 554         builder.padNext(0, '-');
 555     }
 556 
 557     //-----------------------------------------------------------------------
 558     @Test
 559     public void test_padOptional() throws Exception {
 560         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':')
 561                 .padNext(5).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd()
 562                 .appendLiteral(':').appendValue(YEAR);
 563         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2:    1:2013");
 564         assertEquals(builder.toFormatter().format(YearMonth.of(2013, 2)), "2:     :2013");
 565     }
 566 
 567     //-----------------------------------------------------------------------
 568     //-----------------------------------------------------------------------
 569     //-----------------------------------------------------------------------
 570     @Test
 571     public void test_optionalStart_noEnd() throws Exception {
 572         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK);
 573         DateTimeFormatter f = builder.toFormatter();
 574         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)Value(DayOfWeek)]");
 575     }
 576 
 577     @Test
 578     public void test_optionalStart2_noEnd() throws Exception {
 579         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalStart().appendValue(DAY_OF_WEEK);
 580         DateTimeFormatter f = builder.toFormatter();
 581         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]]");
 582     }
 583 
 584     @Test
 585     public void test_optionalStart_doubleStart() throws Exception {
 586         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH);
 587         DateTimeFormatter f = builder.toFormatter();
 588         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
 589     }
 590 
 591     //-----------------------------------------------------------------------
 592     @Test
 593     public void test_optionalEnd() throws Exception {
 594         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().appendValue(DAY_OF_WEEK);
 595         DateTimeFormatter f = builder.toFormatter();
 596         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)]Value(DayOfWeek)");
 597     }
 598 
 599     @Test
 600     public void test_optionalEnd2() throws Exception {
 601         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH)
 602             .optionalStart().appendValue(DAY_OF_WEEK).optionalEnd().appendValue(DAY_OF_MONTH).optionalEnd();
 603         DateTimeFormatter f = builder.toFormatter();
 604         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]Value(DayOfMonth)]");
 605     }
 606 
 607     @Test
 608     public void test_optionalEnd_doubleStartSingleEnd() throws Exception {
 609         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd();
 610         DateTimeFormatter f = builder.toFormatter();
 611         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
 612     }
 613 
 614     @Test
 615     public void test_optionalEnd_doubleStartDoubleEnd() throws Exception {
 616         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().optionalEnd();
 617         DateTimeFormatter f = builder.toFormatter();
 618         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
 619     }
 620 
 621     @Test
 622     public void test_optionalStartEnd_immediateStartEnd() throws Exception {
 623         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalEnd().appendValue(DAY_OF_MONTH);
 624         DateTimeFormatter f = builder.toFormatter();
 625         assertEquals(f.toString(), "Value(MonthOfYear)Value(DayOfMonth)");
 626     }
 627 
 628     @Test(expectedExceptions=IllegalStateException.class)
 629     public void test_optionalEnd_noStart() throws Exception {
 630         builder.optionalEnd();
 631     }
 632 
 633     //-----------------------------------------------------------------------
 634     //-----------------------------------------------------------------------
 635     //-----------------------------------------------------------------------
 636     @DataProvider(name="validPatterns")
 637     Object[][] dataValid() {
 638         return new Object[][] {
 639             {"'a'", "'a'"},
 640             {"''", "''"},
 641             {"'!'", "'!'"},
 642             {"!", "'!'"},
 643 
 644             {"'hello_people,][)('", "'hello_people,][)('"},
 645             {"'hi'", "'hi'"},
 646             {"'yyyy'", "'yyyy'"},
 647             {"''''", "''"},
 648             {"'o''clock'", "'o''clock'"},
 649 
 650             {"G", "Text(Era,SHORT)"},
 651             {"GG", "Text(Era,SHORT)"},
 652             {"GGG", "Text(Era,SHORT)"},
 653             {"GGGG", "Text(Era)"},
 654             {"GGGGG", "Text(Era,NARROW)"},
 655 
 656             {"u", "Value(Year)"},
 657             {"uu", "ReducedValue(Year,2,2,2000-01-01)"},
 658             {"uuu", "Value(Year,3,19,NORMAL)"},
 659             {"uuuu", "Value(Year,4,19,EXCEEDS_PAD)"},
 660             {"uuuuu", "Value(Year,5,19,EXCEEDS_PAD)"},
 661 
 662             {"y", "Value(YearOfEra)"},
 663             {"yy", "ReducedValue(YearOfEra,2,2,2000-01-01)"},
 664             {"yyy", "Value(YearOfEra,3,19,NORMAL)"},
 665             {"yyyy", "Value(YearOfEra,4,19,EXCEEDS_PAD)"},
 666             {"yyyyy", "Value(YearOfEra,5,19,EXCEEDS_PAD)"},
 667 
 668             {"Y", "Localized(WeekBasedYear)"},
 669             {"YY", "Localized(ReducedValue(WeekBasedYear,2,2,2000-01-01))"},
 670             {"YYY", "Localized(WeekBasedYear,3,19,NORMAL)"},
 671             {"YYYY", "Localized(WeekBasedYear,4,19,EXCEEDS_PAD)"},
 672             {"YYYYY", "Localized(WeekBasedYear,5,19,EXCEEDS_PAD)"},
 673 
 674             {"M", "Value(MonthOfYear)"},
 675             {"MM", "Value(MonthOfYear,2)"},
 676             {"MMM", "Text(MonthOfYear,SHORT)"},
 677             {"MMMM", "Text(MonthOfYear)"},
 678             {"MMMMM", "Text(MonthOfYear,NARROW)"},
 679 
 680             {"L", "Value(MonthOfYear)"},
 681             {"LL", "Value(MonthOfYear,2)"},
 682             {"LLL", "Text(MonthOfYear,SHORT_STANDALONE)"},
 683             {"LLLL", "Text(MonthOfYear,FULL_STANDALONE)"},
 684             {"LLLLL", "Text(MonthOfYear,NARROW_STANDALONE)"},
 685 
 686             {"D", "Value(DayOfYear)"},
 687             {"DD", "Value(DayOfYear,2)"},
 688             {"DDD", "Value(DayOfYear,3)"},
 689 
 690             {"d", "Value(DayOfMonth)"},
 691             {"dd", "Value(DayOfMonth,2)"},
 692 
 693             {"F", "Value(AlignedDayOfWeekInMonth)"},
 694 
 695             {"Q", "Value(QuarterOfYear)"},
 696             {"QQ", "Value(QuarterOfYear,2)"},
 697             {"QQQ", "Text(QuarterOfYear,SHORT)"},
 698             {"QQQQ", "Text(QuarterOfYear)"},
 699             {"QQQQQ", "Text(QuarterOfYear,NARROW)"},
 700 
 701             {"q", "Value(QuarterOfYear)"},
 702             {"qq", "Value(QuarterOfYear,2)"},
 703             {"qqq", "Text(QuarterOfYear,SHORT_STANDALONE)"},
 704             {"qqqq", "Text(QuarterOfYear,FULL_STANDALONE)"},
 705             {"qqqqq", "Text(QuarterOfYear,NARROW_STANDALONE)"},
 706 
 707             {"E", "Text(DayOfWeek,SHORT)"},
 708             {"EE", "Text(DayOfWeek,SHORT)"},
 709             {"EEE", "Text(DayOfWeek,SHORT)"},
 710             {"EEEE", "Text(DayOfWeek)"},
 711             {"EEEEE", "Text(DayOfWeek,NARROW)"},
 712 
 713             {"e", "Localized(DayOfWeek,1)"},
 714             {"ee", "Localized(DayOfWeek,2)"},
 715             {"eee", "Text(DayOfWeek,SHORT)"},
 716             {"eeee", "Text(DayOfWeek)"},
 717             {"eeeee", "Text(DayOfWeek,NARROW)"},
 718 
 719             {"c", "Localized(DayOfWeek,1)"},
 720             {"ccc", "Text(DayOfWeek,SHORT_STANDALONE)"},
 721             {"cccc", "Text(DayOfWeek,FULL_STANDALONE)"},
 722             {"ccccc", "Text(DayOfWeek,NARROW_STANDALONE)"},
 723 
 724             {"a", "Text(AmPmOfDay,SHORT)"},
 725 
 726             {"H", "Value(HourOfDay)"},
 727             {"HH", "Value(HourOfDay,2)"},
 728 
 729             {"K", "Value(HourOfAmPm)"},
 730             {"KK", "Value(HourOfAmPm,2)"},
 731 
 732             {"k", "Value(ClockHourOfDay)"},
 733             {"kk", "Value(ClockHourOfDay,2)"},
 734 
 735             {"h", "Value(ClockHourOfAmPm)"},
 736             {"hh", "Value(ClockHourOfAmPm,2)"},
 737 
 738             {"m", "Value(MinuteOfHour)"},
 739             {"mm", "Value(MinuteOfHour,2)"},
 740 
 741             {"s", "Value(SecondOfMinute)"},
 742             {"ss", "Value(SecondOfMinute,2)"},
 743 
 744             {"S", "Fraction(NanoOfSecond,1,1)"},
 745             {"SS", "Fraction(NanoOfSecond,2,2)"},
 746             {"SSS", "Fraction(NanoOfSecond,3,3)"},
 747             {"SSSSSSSSS", "Fraction(NanoOfSecond,9,9)"},
 748 
 749             {"A", "Value(MilliOfDay)"},
 750             {"AA", "Value(MilliOfDay,2)"},
 751             {"AAA", "Value(MilliOfDay,3)"},
 752 
 753             {"n", "Value(NanoOfSecond)"},
 754             {"nn", "Value(NanoOfSecond,2)"},
 755             {"nnn", "Value(NanoOfSecond,3)"},
 756 
 757             {"N", "Value(NanoOfDay)"},
 758             {"NN", "Value(NanoOfDay,2)"},
 759             {"NNN", "Value(NanoOfDay,3)"},
 760 
 761             {"z", "ZoneText(SHORT)"},
 762             {"zz", "ZoneText(SHORT)"},
 763             {"zzz", "ZoneText(SHORT)"},
 764             {"zzzz", "ZoneText(FULL)"},
 765 
 766             {"VV", "ZoneId()"},
 767 
 768             {"Z", "Offset(+HHMM,'+0000')"},  // SimpleDateFormat
 769             {"ZZ", "Offset(+HHMM,'+0000')"},  // SimpleDateFormat
 770             {"ZZZ", "Offset(+HHMM,'+0000')"},  // SimpleDateFormat
 771 
 772             {"X", "Offset(+HHmm,'Z')"},  // LDML/almost SimpleDateFormat
 773             {"XX", "Offset(+HHMM,'Z')"},  // LDML/SimpleDateFormat
 774             {"XXX", "Offset(+HH:MM,'Z')"},  // LDML/SimpleDateFormat
 775             {"XXXX", "Offset(+HHMMss,'Z')"},  // LDML
 776             {"XXXXX", "Offset(+HH:MM:ss,'Z')"},  // LDML
 777 
 778             {"x", "Offset(+HHmm,'+00')"},  // LDML
 779             {"xx", "Offset(+HHMM,'+0000')"},  // LDML
 780             {"xxx", "Offset(+HH:MM,'+00:00')"},  // LDML
 781             {"xxxx", "Offset(+HHMMss,'+0000')"},  // LDML
 782             {"xxxxx", "Offset(+HH:MM:ss,'+00:00')"},  // LDML
 783 
 784             {"ppH", "Pad(Value(HourOfDay),2)"},
 785             {"pppDD", "Pad(Value(DayOfYear,2),3)"},
 786 
 787             {"yyyy[-MM[-dd", "Value(YearOfEra,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"},
 788             {"yyyy[-MM[-dd]]", "Value(YearOfEra,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"},
 789             {"yyyy[-MM[]-dd]", "Value(YearOfEra,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)]"},
 790 
 791             {"yyyy-MM-dd'T'HH:mm:ss.SSS", "Value(YearOfEra,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)" +
 792                 "'T'Value(HourOfDay,2)':'Value(MinuteOfHour,2)':'Value(SecondOfMinute,2)'.'Fraction(NanoOfSecond,3,3)"},
 793 
 794             {"w", "Localized(WeekOfWeekBasedYear,1)"},
 795             {"ww", "Localized(WeekOfWeekBasedYear,2)"},
 796             {"W", "Localized(WeekOfMonth,1)"},
 797         };
 798     }
 799 
 800     @Test(dataProvider="validPatterns")
 801     public void test_appendPattern_valid(String input, String expected) throws Exception {
 802         builder.appendPattern(input);
 803         DateTimeFormatter f = builder.toFormatter();
 804         assertEquals(f.toString(), expected);
 805     }
 806 
 807     //-----------------------------------------------------------------------
 808     @DataProvider(name="invalidPatterns")
 809     Object[][] dataInvalid() {
 810         return new Object[][] {
 811             {"'"},
 812             {"'hello"},
 813             {"'hel''lo"},
 814             {"'hello''"},
 815             {"{"},
 816             {"}"},
 817             {"{}"},
 818             {"]"},
 819             {"yyyy]"},
 820             {"yyyy]MM"},
 821             {"yyyy[MM]]"},
 822 
 823             {"aa"},
 824             {"aaa"},
 825             {"aaaa"},
 826             {"aaaaa"},
 827             {"aaaaaa"},
 828             {"MMMMMM"},
 829             {"LLLLLL"},
 830             {"QQQQQQ"},
 831             {"qqqqqq"},
 832             {"EEEEEE"},
 833             {"eeeeee"},
 834             {"cc"},
 835             {"cccccc"},
 836             {"ddd"},
 837             {"DDDD"},
 838             {"FF"},
 839             {"FFF"},
 840             {"hhh"},
 841             {"HHH"},
 842             {"kkk"},
 843             {"KKK"},
 844             {"mmm"},
 845             {"sss"},
 846             {"OO"},
 847             {"OOO"},
 848             {"OOOOO"},
 849             {"XXXXXX"},
 850             {"ZZZZZZ"},
 851             {"zzzzz"},
 852             {"V"},
 853             {"VVV"},
 854             {"VVVV"},
 855             {"VVVVV"},
 856 
 857             {"RO"},
 858 
 859             {"p"},
 860             {"pp"},
 861             {"p:"},
 862 
 863             {"f"},
 864             {"ff"},
 865             {"f:"},
 866             {"fy"},
 867             {"fa"},
 868             {"fM"},
 869 
 870             {"www"},
 871             {"WW"},
 872         };
 873     }
 874 
 875     @Test(dataProvider="invalidPatterns", expectedExceptions=IllegalArgumentException.class)
 876     public void test_appendPattern_invalid(String input) throws Exception {
 877         try {
 878             builder.appendPattern(input);
 879         } catch (IllegalArgumentException ex) {
 880             throw ex;
 881         }
 882     }
 883 
 884     //-----------------------------------------------------------------------
 885     @DataProvider(name="patternPrint")
 886     Object[][] data_patternPrint() {
 887         return new Object[][] {
 888             {"Q", date(2012, 2, 10), "1"},
 889             {"QQ", date(2012, 2, 10), "01"},
 890             {"QQQ", date(2012, 2, 10), "Q1"},
 891             {"QQQQ", date(2012, 2, 10), "1st quarter"},
 892             {"QQQQQ", date(2012, 2, 10), "1"},
 893         };
 894     }
 895 
 896     @Test(dataProvider="patternPrint")
 897     public void test_appendPattern_patternPrint(String input, Temporal temporal, String expected) throws Exception {
 898         DateTimeFormatter f = builder.appendPattern(input).toFormatter(Locale.UK);
 899         String test = f.format(temporal);
 900         assertEquals(test, expected);
 901     }
 902 
 903     //-----------------------------------------------------------------------
 904     @DataProvider(name="localePatterns")
 905     Object[][] localizedDateTimePatterns() {
 906         return new Object[][] {
 907             {FormatStyle.FULL, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.US, "EEEE, MMMM d, yyyy h:mm:ss a z"},
 908             {FormatStyle.LONG, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.US, "MMMM d, yyyy h:mm:ss a z"},
 909             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.US, "MMM d, yyyy h:mm:ss a"},
 910             {FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.US, "M/d/yy h:mm a"},
 911             {FormatStyle.FULL, null, IsoChronology.INSTANCE, Locale.US, "EEEE, MMMM d, yyyy"},
 912             {FormatStyle.LONG, null, IsoChronology.INSTANCE, Locale.US, "MMMM d, yyyy"},
 913             {FormatStyle.MEDIUM, null, IsoChronology.INSTANCE, Locale.US, "MMM d, yyyy"},
 914             {FormatStyle.SHORT, null, IsoChronology.INSTANCE, Locale.US, "M/d/yy"},
 915             {null, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.US, "h:mm:ss a z"},
 916             {null, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.US, "h:mm:ss a z"},
 917             {null, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.US, "h:mm:ss a"},
 918             {null, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.US, "h:mm a"},
 919 
 920             // French Locale and ISO Chronology
 921             {FormatStyle.FULL, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.FRENCH, "EEEE d MMMM yyyy HH' h 'mm z"},
 922             {FormatStyle.LONG, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.FRENCH, "d MMMM yyyy HH:mm:ss z"},
 923             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.FRENCH, "d MMM yyyy HH:mm:ss"},
 924             {FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.FRENCH, "dd/MM/yy HH:mm"},
 925             {FormatStyle.FULL, null, IsoChronology.INSTANCE, Locale.FRENCH, "EEEE d MMMM yyyy"},
 926             {FormatStyle.LONG, null, IsoChronology.INSTANCE, Locale.FRENCH, "d MMMM yyyy"},
 927             {FormatStyle.MEDIUM, null, IsoChronology.INSTANCE, Locale.FRENCH, "d MMM yyyy"},
 928             {FormatStyle.SHORT, null, IsoChronology.INSTANCE, Locale.FRENCH, "dd/MM/yy"},
 929             {null, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.FRENCH, "HH' h 'mm z"},
 930             {null, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm:ss z"},
 931             {null, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm:ss"},
 932             {null, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm"},
 933 
 934             // Japanese Locale and JapaneseChronology
 935             {FormatStyle.FULL, FormatStyle.FULL, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy'\u5e74'M'\u6708'd'\u65e5' H'\u6642'mm'\u5206'ss'\u79d2' z"},
 936             {FormatStyle.LONG, FormatStyle.LONG, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy.MM.dd H:mm:ss z"},
 937             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy.MM.dd H:mm:ss"},
 938             {FormatStyle.SHORT, FormatStyle.SHORT, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy.MM.dd H:mm"},
 939             {FormatStyle.FULL, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy'\u5e74'M'\u6708'd'\u65e5'"},
 940             {FormatStyle.LONG, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy.MM.dd"},
 941             {FormatStyle.MEDIUM, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy.MM.dd"},
 942             {FormatStyle.SHORT, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy.MM.dd"},
 943             {null, FormatStyle.FULL, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H'\u6642'mm'\u5206'ss'\u79d2' z"},
 944             {null, FormatStyle.LONG, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H:mm:ss z"},
 945             {null, FormatStyle.MEDIUM, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H:mm:ss"},
 946             {null, FormatStyle.SHORT, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H:mm"},
 947 
 948             // Chinese Local and Chronology
 949             {FormatStyle.FULL, FormatStyle.FULL, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5EEEE ahh'\u65f6'mm'\u5206'ss'\u79d2' z"},
 950             {FormatStyle.LONG, FormatStyle.LONG, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5 ahh'\u65f6'mm'\u5206'ss'\u79d2'"},
 951             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy-M-d H:mm:ss"},
 952             {FormatStyle.SHORT, FormatStyle.SHORT, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy-M-d ah:mm"},
 953             {FormatStyle.FULL, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5EEEE"},
 954             {FormatStyle.LONG, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5"},
 955             {FormatStyle.MEDIUM, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy-M-d"},
 956             {FormatStyle.SHORT, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy-M-d"},
 957             {null, FormatStyle.FULL, MinguoChronology.INSTANCE, Locale.CHINESE, "ahh'\u65f6'mm'\u5206'ss'\u79d2' z"},
 958             {null, FormatStyle.LONG, MinguoChronology.INSTANCE, Locale.CHINESE, "ahh'\u65f6'mm'\u5206'ss'\u79d2'"},
 959             {null, FormatStyle.MEDIUM, MinguoChronology.INSTANCE, Locale.CHINESE, "H:mm:ss"},
 960             {null, FormatStyle.SHORT, MinguoChronology.INSTANCE, Locale.CHINESE, "ah:mm"},
 961         };
 962     }
 963 
 964     @Test(dataProvider="localePatterns")
 965     public void test_getLocalizedDateTimePattern(FormatStyle dateStyle, FormatStyle timeStyle,
 966             Chronology chrono, Locale locale, String expected) {
 967         String actual = DateTimeFormatterBuilder.getLocalizedDateTimePattern(dateStyle, timeStyle, chrono, locale);
 968         assertEquals(actual, expected, "Pattern " + convertNonAscii(actual));
 969     }
 970 
 971     @Test(expectedExceptions=java.lang.IllegalArgumentException.class)
 972     public void test_getLocalizedDateTimePatternIAE() {
 973         DateTimeFormatterBuilder.getLocalizedDateTimePattern(null, null, IsoChronology.INSTANCE, Locale.US);
 974     }
 975 
 976     @Test(expectedExceptions=java.lang.NullPointerException.class)
 977     public void test_getLocalizedChronoNPE() {
 978         DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT, FormatStyle.SHORT, null, Locale.US);
 979     }
 980 
 981     @Test(expectedExceptions=java.lang.NullPointerException.class)
 982     public void test_getLocalizedLocaleNPE() {
 983         DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, null);
 984     }
 985 
 986     /**
 987      * Returns a string that includes non-ascii characters after expanding
 988      * the non-ascii characters to their Java language \\uxxxx form.
 989      * @param input an input string
 990      * @return the encoded string.
 991      */
 992     private String convertNonAscii(String input) {
 993         StringBuilder sb = new StringBuilder(input.length() * 6);
 994         for (int i = 0; i < input.length(); i++) {
 995             char ch = input.charAt(i);
 996             if (ch < 255) {
 997                 sb.append(ch);
 998             } else {
 999                 sb.append("\\u");
1000                 sb.append(Integer.toHexString(ch));
1001             }
1002         }
1003         return sb.toString();
1004     }
1005 
1006     private static Temporal date(int y, int m, int d) {
1007         return LocalDate.of(y, m, d);
1008     }
1009 
1010 }