1 /*
   2  * Copyright (c) 2012, 2016, 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             {"+HHM"},
 488             {"+A"},
 489         };
 490     }
 491 
 492     @Test(dataProvider="badOffsetPatterns", expectedExceptions=IllegalArgumentException.class)
 493     public void test_appendOffset_badPattern(String pattern) throws Exception {
 494         builder.appendOffset(pattern, "Z");
 495     }
 496 
 497     @Test(expectedExceptions=NullPointerException.class)
 498     public void test_appendOffset_3arg_nullText() throws Exception {
 499         builder.appendOffset("+HH:MM", null);
 500     }
 501 
 502     @Test(expectedExceptions=NullPointerException.class)
 503     public void test_appendOffset_3arg_nullPattern() throws Exception {
 504         builder.appendOffset(null, "Z");
 505     }
 506 
 507     //-----------------------------------------------------------------------
 508     //-----------------------------------------------------------------------
 509     //-----------------------------------------------------------------------
 510     @Test
 511     public void test_appendZoneId() throws Exception {
 512         builder.appendZoneId();
 513         DateTimeFormatter f = builder.toFormatter();
 514         assertEquals(f.toString(), "ZoneId()");
 515     }
 516 
 517     @Test
 518     public void test_appendZoneText_1arg() throws Exception {
 519         builder.appendZoneText(TextStyle.FULL);
 520         DateTimeFormatter f = builder.toFormatter();
 521         assertEquals(f.toString(), "ZoneText(FULL)");
 522     }
 523 
 524     @Test(expectedExceptions=NullPointerException.class)
 525     public void test_appendZoneText_1arg_nullText() throws Exception {
 526         builder.appendZoneText(null);
 527     }
 528 
 529     //-----------------------------------------------------------------------
 530     //-----------------------------------------------------------------------
 531     //-----------------------------------------------------------------------
 532     @Test
 533     public void test_padNext_1arg() {
 534         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':').padNext(2).appendValue(DAY_OF_MONTH);
 535         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2: 1");
 536     }
 537 
 538     @Test(expectedExceptions=IllegalArgumentException.class)
 539     public void test_padNext_1arg_invalidWidth() throws Exception {
 540         builder.padNext(0);
 541     }
 542 
 543     //-----------------------------------------------------------------------
 544     @Test
 545     public void test_padNext_2arg_dash() throws Exception {
 546         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':').padNext(2, '-').appendValue(DAY_OF_MONTH);
 547         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2:-1");
 548     }
 549 
 550     @Test(expectedExceptions=IllegalArgumentException.class)
 551     public void test_padNext_2arg_invalidWidth() throws Exception {
 552         builder.padNext(0, '-');
 553     }
 554 
 555     //-----------------------------------------------------------------------
 556     @Test
 557     public void test_padOptional() throws Exception {
 558         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':')
 559                 .padNext(5).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd()
 560                 .appendLiteral(':').appendValue(YEAR);
 561         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2:    1:2013");
 562         assertEquals(builder.toFormatter().format(YearMonth.of(2013, 2)), "2:     :2013");
 563     }
 564 
 565     //-----------------------------------------------------------------------
 566     //-----------------------------------------------------------------------
 567     //-----------------------------------------------------------------------
 568     @Test
 569     public void test_optionalStart_noEnd() throws Exception {
 570         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK);
 571         DateTimeFormatter f = builder.toFormatter();
 572         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)Value(DayOfWeek)]");
 573     }
 574 
 575     @Test
 576     public void test_optionalStart2_noEnd() throws Exception {
 577         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalStart().appendValue(DAY_OF_WEEK);
 578         DateTimeFormatter f = builder.toFormatter();
 579         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]]");
 580     }
 581 
 582     @Test
 583     public void test_optionalStart_doubleStart() throws Exception {
 584         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH);
 585         DateTimeFormatter f = builder.toFormatter();
 586         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
 587     }
 588 
 589     //-----------------------------------------------------------------------
 590     @Test
 591     public void test_optionalEnd() throws Exception {
 592         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().appendValue(DAY_OF_WEEK);
 593         DateTimeFormatter f = builder.toFormatter();
 594         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)]Value(DayOfWeek)");
 595     }
 596 
 597     @Test
 598     public void test_optionalEnd2() throws Exception {
 599         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH)
 600             .optionalStart().appendValue(DAY_OF_WEEK).optionalEnd().appendValue(DAY_OF_MONTH).optionalEnd();
 601         DateTimeFormatter f = builder.toFormatter();
 602         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]Value(DayOfMonth)]");
 603     }
 604 
 605     @Test
 606     public void test_optionalEnd_doubleStartSingleEnd() throws Exception {
 607         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd();
 608         DateTimeFormatter f = builder.toFormatter();
 609         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
 610     }
 611 
 612     @Test
 613     public void test_optionalEnd_doubleStartDoubleEnd() throws Exception {
 614         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().optionalEnd();
 615         DateTimeFormatter f = builder.toFormatter();
 616         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
 617     }
 618 
 619     @Test
 620     public void test_optionalStartEnd_immediateStartEnd() throws Exception {
 621         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalEnd().appendValue(DAY_OF_MONTH);
 622         DateTimeFormatter f = builder.toFormatter();
 623         assertEquals(f.toString(), "Value(MonthOfYear)Value(DayOfMonth)");
 624     }
 625 
 626     @Test(expectedExceptions=IllegalStateException.class)
 627     public void test_optionalEnd_noStart() throws Exception {
 628         builder.optionalEnd();
 629     }
 630 
 631     //-----------------------------------------------------------------------
 632     //-----------------------------------------------------------------------
 633     //-----------------------------------------------------------------------
 634     @DataProvider(name="validPatterns")
 635     Object[][] dataValid() {
 636         return new Object[][] {
 637             {"'a'", "'a'"},
 638             {"''", "''"},
 639             {"'!'", "'!'"},
 640             {"!", "'!'"},
 641 
 642             {"'hello_people,][)('", "'hello_people,][)('"},
 643             {"'hi'", "'hi'"},
 644             {"'yyyy'", "'yyyy'"},
 645             {"''''", "''"},
 646             {"'o''clock'", "'o''clock'"},
 647 
 648             {"G", "Text(Era,SHORT)"},
 649             {"GG", "Text(Era,SHORT)"},
 650             {"GGG", "Text(Era,SHORT)"},
 651             {"GGGG", "Text(Era)"},
 652             {"GGGGG", "Text(Era,NARROW)"},
 653 
 654             {"u", "Value(Year)"},
 655             {"uu", "ReducedValue(Year,2,2,2000-01-01)"},
 656             {"uuu", "Value(Year,3,19,NORMAL)"},
 657             {"uuuu", "Value(Year,4,19,EXCEEDS_PAD)"},
 658             {"uuuuu", "Value(Year,5,19,EXCEEDS_PAD)"},
 659 
 660             {"y", "Value(YearOfEra)"},
 661             {"yy", "ReducedValue(YearOfEra,2,2,2000-01-01)"},
 662             {"yyy", "Value(YearOfEra,3,19,NORMAL)"},
 663             {"yyyy", "Value(YearOfEra,4,19,EXCEEDS_PAD)"},
 664             {"yyyyy", "Value(YearOfEra,5,19,EXCEEDS_PAD)"},
 665 
 666             {"Y", "Localized(WeekBasedYear)"},
 667             {"YY", "Localized(ReducedValue(WeekBasedYear,2,2,2000-01-01))"},
 668             {"YYY", "Localized(WeekBasedYear,3,19,NORMAL)"},
 669             {"YYYY", "Localized(WeekBasedYear,4,19,EXCEEDS_PAD)"},
 670             {"YYYYY", "Localized(WeekBasedYear,5,19,EXCEEDS_PAD)"},
 671 
 672             {"M", "Value(MonthOfYear)"},
 673             {"MM", "Value(MonthOfYear,2)"},
 674             {"MMM", "Text(MonthOfYear,SHORT)"},
 675             {"MMMM", "Text(MonthOfYear)"},
 676             {"MMMMM", "Text(MonthOfYear,NARROW)"},
 677 
 678             {"L", "Value(MonthOfYear)"},
 679             {"LL", "Value(MonthOfYear,2)"},
 680             {"LLL", "Text(MonthOfYear,SHORT_STANDALONE)"},
 681             {"LLLL", "Text(MonthOfYear,FULL_STANDALONE)"},
 682             {"LLLLL", "Text(MonthOfYear,NARROW_STANDALONE)"},
 683 
 684             {"D", "Value(DayOfYear)"},
 685             {"DD", "Value(DayOfYear,2,3,NOT_NEGATIVE)"},
 686             {"DDD", "Value(DayOfYear,3)"},
 687 
 688             {"d", "Value(DayOfMonth)"},
 689             {"dd", "Value(DayOfMonth,2)"},
 690 
 691             {"F", "Value(AlignedDayOfWeekInMonth)"},
 692 
 693             {"Q", "Value(QuarterOfYear)"},
 694             {"QQ", "Value(QuarterOfYear,2)"},
 695             {"QQQ", "Text(QuarterOfYear,SHORT)"},
 696             {"QQQQ", "Text(QuarterOfYear)"},
 697             {"QQQQQ", "Text(QuarterOfYear,NARROW)"},
 698 
 699             {"q", "Value(QuarterOfYear)"},
 700             {"qq", "Value(QuarterOfYear,2)"},
 701             {"qqq", "Text(QuarterOfYear,SHORT_STANDALONE)"},
 702             {"qqqq", "Text(QuarterOfYear,FULL_STANDALONE)"},
 703             {"qqqqq", "Text(QuarterOfYear,NARROW_STANDALONE)"},
 704 
 705             {"E", "Text(DayOfWeek,SHORT)"},
 706             {"EE", "Text(DayOfWeek,SHORT)"},
 707             {"EEE", "Text(DayOfWeek,SHORT)"},
 708             {"EEEE", "Text(DayOfWeek)"},
 709             {"EEEEE", "Text(DayOfWeek,NARROW)"},
 710 
 711             {"e", "Localized(DayOfWeek,1)"},
 712             {"ee", "Localized(DayOfWeek,2)"},
 713             {"eee", "Text(DayOfWeek,SHORT)"},
 714             {"eeee", "Text(DayOfWeek)"},
 715             {"eeeee", "Text(DayOfWeek,NARROW)"},
 716 
 717             {"c", "Localized(DayOfWeek,1)"},
 718             {"ccc", "Text(DayOfWeek,SHORT_STANDALONE)"},
 719             {"cccc", "Text(DayOfWeek,FULL_STANDALONE)"},
 720             {"ccccc", "Text(DayOfWeek,NARROW_STANDALONE)"},
 721 
 722             {"a", "Text(AmPmOfDay,SHORT)"},
 723 
 724             {"H", "Value(HourOfDay)"},
 725             {"HH", "Value(HourOfDay,2)"},
 726 
 727             {"K", "Value(HourOfAmPm)"},
 728             {"KK", "Value(HourOfAmPm,2)"},
 729 
 730             {"k", "Value(ClockHourOfDay)"},
 731             {"kk", "Value(ClockHourOfDay,2)"},
 732 
 733             {"h", "Value(ClockHourOfAmPm)"},
 734             {"hh", "Value(ClockHourOfAmPm,2)"},
 735 
 736             {"m", "Value(MinuteOfHour)"},
 737             {"mm", "Value(MinuteOfHour,2)"},
 738 
 739             {"s", "Value(SecondOfMinute)"},
 740             {"ss", "Value(SecondOfMinute,2)"},
 741 
 742             {"S", "Fraction(NanoOfSecond,1,1)"},
 743             {"SS", "Fraction(NanoOfSecond,2,2)"},
 744             {"SSS", "Fraction(NanoOfSecond,3,3)"},
 745             {"SSSSSSSSS", "Fraction(NanoOfSecond,9,9)"},
 746 
 747             {"A", "Value(MilliOfDay,1,19,NOT_NEGATIVE)"},
 748             {"AA", "Value(MilliOfDay,2,19,NOT_NEGATIVE)"},
 749             {"AAA", "Value(MilliOfDay,3,19,NOT_NEGATIVE)"},
 750 
 751             {"n", "Value(NanoOfSecond,1,19,NOT_NEGATIVE)"},
 752             {"nn", "Value(NanoOfSecond,2,19,NOT_NEGATIVE)"},
 753             {"nnn", "Value(NanoOfSecond,3,19,NOT_NEGATIVE)"},
 754 
 755             {"N", "Value(NanoOfDay,1,19,NOT_NEGATIVE)"},
 756             {"NN", "Value(NanoOfDay,2,19,NOT_NEGATIVE)"},
 757             {"NNN", "Value(NanoOfDay,3,19,NOT_NEGATIVE)"},
 758 
 759             {"z", "ZoneText(SHORT)"},
 760             {"zz", "ZoneText(SHORT)"},
 761             {"zzz", "ZoneText(SHORT)"},
 762             {"zzzz", "ZoneText(FULL)"},
 763 
 764             {"VV", "ZoneId()"},
 765 
 766             {"Z", "Offset(+HHMM,'+0000')"},  // SimpleDateFormat
 767             {"ZZ", "Offset(+HHMM,'+0000')"},  // SimpleDateFormat
 768             {"ZZZ", "Offset(+HHMM,'+0000')"},  // SimpleDateFormat
 769 
 770             {"X", "Offset(+HHmm,'Z')"},  // LDML/almost SimpleDateFormat
 771             {"XX", "Offset(+HHMM,'Z')"},  // LDML/SimpleDateFormat
 772             {"XXX", "Offset(+HH:MM,'Z')"},  // LDML/SimpleDateFormat
 773             {"XXXX", "Offset(+HHMMss,'Z')"},  // LDML
 774             {"XXXXX", "Offset(+HH:MM:ss,'Z')"},  // LDML
 775 
 776             {"x", "Offset(+HHmm,'+00')"},  // LDML
 777             {"xx", "Offset(+HHMM,'+0000')"},  // LDML
 778             {"xxx", "Offset(+HH:MM,'+00:00')"},  // LDML
 779             {"xxxx", "Offset(+HHMMss,'+0000')"},  // LDML
 780             {"xxxxx", "Offset(+HH:MM:ss,'+00:00')"},  // LDML
 781 
 782             {"ppH", "Pad(Value(HourOfDay),2)"},
 783             {"pppDD", "Pad(Value(DayOfYear,2,3,NOT_NEGATIVE),3)"},
 784 
 785             {"yyyy[-MM[-dd", "Value(YearOfEra,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"},
 786             {"yyyy[-MM[-dd]]", "Value(YearOfEra,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"},
 787             {"yyyy[-MM[]-dd]", "Value(YearOfEra,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)]"},
 788 
 789             {"yyyy-MM-dd'T'HH:mm:ss.SSS", "Value(YearOfEra,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)" +
 790                 "'T'Value(HourOfDay,2)':'Value(MinuteOfHour,2)':'Value(SecondOfMinute,2)'.'Fraction(NanoOfSecond,3,3)"},
 791 
 792             {"w", "Localized(WeekOfWeekBasedYear,1)"},
 793             {"ww", "Localized(WeekOfWeekBasedYear,2)"},
 794             {"W", "Localized(WeekOfMonth,1)"},
 795         };
 796     }
 797 
 798     @Test(dataProvider="validPatterns")
 799     public void test_appendPattern_valid(String input, String expected) throws Exception {
 800         builder.appendPattern(input);
 801         DateTimeFormatter f = builder.toFormatter();
 802         assertEquals(f.toString(), expected);
 803     }
 804 
 805     //-----------------------------------------------------------------------
 806     @DataProvider(name="invalidPatterns")
 807     Object[][] dataInvalid() {
 808         return new Object[][] {
 809             {"'"},
 810             {"'hello"},
 811             {"'hel''lo"},
 812             {"'hello''"},
 813             {"{"},
 814             {"}"},
 815             {"{}"},
 816             {"]"},
 817             {"yyyy]"},
 818             {"yyyy]MM"},
 819             {"yyyy[MM]]"},
 820 
 821             {"aa"},
 822             {"aaa"},
 823             {"aaaa"},
 824             {"aaaaa"},
 825             {"aaaaaa"},
 826             {"MMMMMM"},
 827             {"LLLLLL"},
 828             {"QQQQQQ"},
 829             {"qqqqqq"},
 830             {"EEEEEE"},
 831             {"eeeeee"},
 832             {"cc"},
 833             {"cccccc"},
 834             {"ddd"},
 835             {"DDDD"},
 836             {"FF"},
 837             {"FFF"},
 838             {"hhh"},
 839             {"HHH"},
 840             {"kkk"},
 841             {"KKK"},
 842             {"mmm"},
 843             {"sss"},
 844             {"OO"},
 845             {"OOO"},
 846             {"OOOOO"},
 847             {"XXXXXX"},
 848             {"ZZZZZZ"},
 849             {"zzzzz"},
 850             {"V"},
 851             {"VVV"},
 852             {"VVVV"},
 853             {"VVVVV"},
 854 
 855             {"RO"},
 856 
 857             {"p"},
 858             {"pp"},
 859             {"p:"},
 860 
 861             {"f"},
 862             {"ff"},
 863             {"f:"},
 864             {"fy"},
 865             {"fa"},
 866             {"fM"},
 867 
 868             {"www"},
 869             {"WW"},
 870         };
 871     }
 872 
 873     @Test(dataProvider="invalidPatterns", expectedExceptions=IllegalArgumentException.class)
 874     public void test_appendPattern_invalid(String input) throws Exception {
 875         try {
 876             builder.appendPattern(input);
 877         } catch (IllegalArgumentException ex) {
 878             throw ex;
 879         }
 880     }
 881 
 882     //-----------------------------------------------------------------------
 883     @DataProvider(name="patternPrint")
 884     Object[][] data_patternPrint() {
 885         return new Object[][] {
 886             {"Q", date(2012, 2, 10), "1"},
 887             {"QQ", date(2012, 2, 10), "01"},
 888             {"QQQ", date(2012, 2, 10), "Q1"},
 889             {"QQQQ", date(2012, 2, 10), "1st quarter"},
 890             {"QQQQQ", date(2012, 2, 10), "1"},
 891         };
 892     }
 893 
 894     @Test(dataProvider="patternPrint")
 895     public void test_appendPattern_patternPrint(String input, Temporal temporal, String expected) throws Exception {
 896         DateTimeFormatter f = builder.appendPattern(input).toFormatter(Locale.UK);
 897         String test = f.format(temporal);
 898         assertEquals(test, expected);
 899     }
 900 
 901     //-----------------------------------------------------------------------
 902     @DataProvider(name="localePatterns")
 903     Object[][] localizedDateTimePatterns() {
 904         return new Object[][] {
 905             {FormatStyle.FULL, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.US, "EEEE, MMMM d, y 'at' h:mm:ss a zzzz"},
 906             {FormatStyle.LONG, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.US, "MMMM d, y 'at' h:mm:ss a z"},
 907             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.US, "MMM d, y, h:mm:ss a"},
 908             {FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.US, "M/d/yy, h:mm a"},
 909             {FormatStyle.FULL, null, IsoChronology.INSTANCE, Locale.US, "EEEE, MMMM d, y"},
 910             {FormatStyle.LONG, null, IsoChronology.INSTANCE, Locale.US, "MMMM d, y"},
 911             {FormatStyle.MEDIUM, null, IsoChronology.INSTANCE, Locale.US, "MMM d, y"},
 912             {FormatStyle.SHORT, null, IsoChronology.INSTANCE, Locale.US, "M/d/yy"},
 913             {null, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.US, "h:mm:ss a zzzz"},
 914             {null, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.US, "h:mm:ss a z"},
 915             {null, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.US, "h:mm:ss a"},
 916             {null, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.US, "h:mm a"},
 917 
 918             // French Locale and ISO Chronology
 919             {FormatStyle.FULL, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.FRENCH, "EEEE d MMMM y '\u00e0' HH:mm:ss zzzz"},
 920             {FormatStyle.LONG, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.FRENCH, "d MMMM y '\u00e0' HH:mm:ss z"},
 921             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.FRENCH, "d MMM y '\u00e0' HH:mm:ss"},
 922             {FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.FRENCH, "dd/MM/y HH:mm"},
 923             {FormatStyle.FULL, null, IsoChronology.INSTANCE, Locale.FRENCH, "EEEE d MMMM y"},
 924             {FormatStyle.LONG, null, IsoChronology.INSTANCE, Locale.FRENCH, "d MMMM y"},
 925             {FormatStyle.MEDIUM, null, IsoChronology.INSTANCE, Locale.FRENCH, "d MMM y"},
 926             {FormatStyle.SHORT, null, IsoChronology.INSTANCE, Locale.FRENCH, "dd/MM/y"},
 927             {null, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm:ss zzzz"},
 928             {null, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm:ss z"},
 929             {null, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm:ss"},
 930             {null, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm"},
 931 
 932             // Japanese Locale and JapaneseChronology
 933             {FormatStyle.FULL, FormatStyle.FULL, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5EEEE H\u6642mm\u5206ss\u79d2 zzzz"},
 934             {FormatStyle.LONG, FormatStyle.LONG, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5 H:mm:ss z"},
 935             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5 H:mm:ss"},
 936             {FormatStyle.SHORT, FormatStyle.SHORT, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy/M/d H:mm"},
 937             {FormatStyle.FULL, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5EEEE"},
 938             {FormatStyle.LONG, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5"},
 939             {FormatStyle.MEDIUM, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5"},
 940             {FormatStyle.SHORT, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy/M/d"},
 941             {null, FormatStyle.FULL, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H\u6642mm\u5206ss\u79d2 zzzz"},
 942             {null, FormatStyle.LONG, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H:mm:ss z"},
 943             {null, FormatStyle.MEDIUM, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H:mm:ss"},
 944             {null, FormatStyle.SHORT, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H:mm"},
 945 
 946             // Chinese Local and Chronology
 947             {FormatStyle.FULL, FormatStyle.FULL, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5EEEE zzzz ah:mm:ss"},
 948             {FormatStyle.LONG, FormatStyle.LONG, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5 z ah:mm:ss"},
 949             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5 ah:mm:ss"},
 950             {FormatStyle.SHORT, FormatStyle.SHORT, MinguoChronology.INSTANCE, Locale.CHINESE, "Gyy/M/d ah:mm"},
 951             {FormatStyle.FULL, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5EEEE"},
 952             {FormatStyle.LONG, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5"},
 953             {FormatStyle.MEDIUM, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5"},
 954             {FormatStyle.SHORT, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gyy/M/d"},
 955             {null, FormatStyle.FULL, MinguoChronology.INSTANCE, Locale.CHINESE, "zzzz ah:mm:ss"},
 956             {null, FormatStyle.LONG, MinguoChronology.INSTANCE, Locale.CHINESE, "z ah:mm:ss"},
 957             {null, FormatStyle.MEDIUM, MinguoChronology.INSTANCE, Locale.CHINESE, "ah:mm:ss"},
 958             {null, FormatStyle.SHORT, MinguoChronology.INSTANCE, Locale.CHINESE, "ah:mm"},
 959         };
 960     }
 961 
 962     @Test(dataProvider="localePatterns")
 963     public void test_getLocalizedDateTimePattern(FormatStyle dateStyle, FormatStyle timeStyle,
 964             Chronology chrono, Locale locale, String expected) {
 965         String actual = DateTimeFormatterBuilder.getLocalizedDateTimePattern(dateStyle, timeStyle, chrono, locale);
 966         assertEquals(actual, expected, "Pattern " + convertNonAscii(actual));
 967     }
 968 
 969     @Test(expectedExceptions=java.lang.IllegalArgumentException.class)
 970     public void test_getLocalizedDateTimePatternIAE() {
 971         DateTimeFormatterBuilder.getLocalizedDateTimePattern(null, null, IsoChronology.INSTANCE, Locale.US);
 972     }
 973 
 974     @Test(expectedExceptions=java.lang.NullPointerException.class)
 975     public void test_getLocalizedChronoNPE() {
 976         DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT, FormatStyle.SHORT, null, Locale.US);
 977     }
 978 
 979     @Test(expectedExceptions=java.lang.NullPointerException.class)
 980     public void test_getLocalizedLocaleNPE() {
 981         DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, null);
 982     }
 983 
 984     /**
 985      * Returns a string that includes non-ascii characters after expanding
 986      * the non-ascii characters to their Java language \\uxxxx form.
 987      * @param input an input string
 988      * @return the encoded string.
 989      */
 990     private String convertNonAscii(String input) {
 991         StringBuilder sb = new StringBuilder(input.length() * 6);
 992         for (int i = 0; i < input.length(); i++) {
 993             char ch = input.charAt(i);
 994             if (ch < 255) {
 995                 sb.append(ch);
 996             } else {
 997                 sb.append("\\u");
 998                 sb.append(Integer.toHexString(ch));
 999             }
1000         }
1001         return sb.toString();
1002     }
1003 
1004     private static Temporal date(int y, int m, int d) {
1005         return LocalDate.of(y, m, d);
1006     }
1007 
1008 }