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 tck.java.time.format;
  61 
  62 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
  63 import static java.time.temporal.ChronoField.HOUR_OF_DAY;
  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.NANO_OF_SECOND;
  67 import static java.time.temporal.ChronoField.YEAR;
  68 import static org.testng.Assert.assertEquals;
  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.format.DateTimeFormatter;
  75 import java.time.format.DateTimeFormatterBuilder;
  76 import java.time.format.SignStyle;
  77 import java.time.format.TextStyle;
  78 import java.time.temporal.Temporal;
  79 import java.time.temporal.TemporalAccessor;
  80 import java.util.HashMap;
  81 import java.util.Locale;
  82 import java.util.Map;
  83 
  84 import org.testng.annotations.BeforeMethod;
  85 import org.testng.annotations.DataProvider;
  86 import org.testng.annotations.Test;
  87 
  88 /**
  89  * Test DateTimeFormatterBuilder.
  90  */
  91 @Test
  92 public class TCKDateTimeFormatterBuilder {
  93 
  94     private DateTimeFormatterBuilder builder;
  95 
  96     @BeforeMethod
  97     public void setUp() {
  98         builder = new DateTimeFormatterBuilder();
  99     }
 100 
 101     //-----------------------------------------------------------------------
 102     @Test
 103     public void test_toFormatter_empty() throws Exception {
 104         DateTimeFormatter f = builder.toFormatter();
 105         assertEquals(f.format(LocalDate.of(2012, 6, 30)), "");
 106     }
 107 
 108     //-----------------------------------------------------------------------
 109     @Test
 110     public void test_parseDefaulting_entireDate() {
 111         DateTimeFormatter f = builder
 112             .parseDefaulting(YEAR, 2012).parseDefaulting(MONTH_OF_YEAR, 6)
 113             .parseDefaulting(DAY_OF_MONTH, 30).toFormatter();
 114         LocalDate parsed = f.parse("", LocalDate::from);  // blank string can be parsed
 115         assertEquals(parsed, LocalDate.of(2012, 6, 30));
 116     }
 117 
 118     @Test
 119     public void test_parseDefaulting_yearOptionalMonthOptionalDay() {
 120         DateTimeFormatter f = builder
 121                 .appendValue(YEAR)
 122                 .optionalStart().appendLiteral('-').appendValue(MONTH_OF_YEAR)
 123                 .optionalStart().appendLiteral('-').appendValue(DAY_OF_MONTH)
 124                 .optionalEnd().optionalEnd()
 125                 .parseDefaulting(MONTH_OF_YEAR, 1)
 126                 .parseDefaulting(DAY_OF_MONTH, 1).toFormatter();
 127         assertEquals(f.parse("2012", LocalDate::from), LocalDate.of(2012, 1, 1));
 128         assertEquals(f.parse("2012-6", LocalDate::from), LocalDate.of(2012, 6, 1));
 129         assertEquals(f.parse("2012-6-30", LocalDate::from), LocalDate.of(2012, 6, 30));
 130     }
 131 
 132     @Test(expectedExceptions = NullPointerException.class)
 133     public void test_parseDefaulting_null() {
 134         builder.parseDefaulting(null, 1);
 135     }
 136 
 137     //-----------------------------------------------------------------------
 138     @Test(expectedExceptions=NullPointerException.class)
 139     public void test_appendValue_1arg_null() throws Exception {
 140         builder.appendValue(null);
 141     }
 142 
 143     //-----------------------------------------------------------------------
 144     @Test(expectedExceptions=NullPointerException.class)
 145     public void test_appendValue_2arg_null() throws Exception {
 146         builder.appendValue(null, 3);
 147     }
 148 
 149     @Test(expectedExceptions=IllegalArgumentException.class)
 150     public void test_appendValue_2arg_widthTooSmall() throws Exception {
 151         builder.appendValue(DAY_OF_MONTH, 0);
 152     }
 153 
 154     @Test(expectedExceptions=IllegalArgumentException.class)
 155     public void test_appendValue_2arg_widthTooBig() throws Exception {
 156         builder.appendValue(DAY_OF_MONTH, 20);
 157     }
 158 
 159     //-----------------------------------------------------------------------
 160     @Test(expectedExceptions=NullPointerException.class)
 161     public void test_appendValue_3arg_nullField() throws Exception {
 162         builder.appendValue(null, 2, 3, SignStyle.NORMAL);
 163     }
 164 
 165     @Test(expectedExceptions=IllegalArgumentException.class)
 166     public void test_appendValue_3arg_minWidthTooSmall() throws Exception {
 167         builder.appendValue(DAY_OF_MONTH, 0, 2, SignStyle.NORMAL);
 168     }
 169 
 170     @Test(expectedExceptions=IllegalArgumentException.class)
 171     public void test_appendValue_3arg_minWidthTooBig() throws Exception {
 172         builder.appendValue(DAY_OF_MONTH, 20, 2, SignStyle.NORMAL);
 173     }
 174 
 175     @Test(expectedExceptions=IllegalArgumentException.class)
 176     public void test_appendValue_3arg_maxWidthTooSmall() throws Exception {
 177         builder.appendValue(DAY_OF_MONTH, 2, 0, SignStyle.NORMAL);
 178     }
 179 
 180     @Test(expectedExceptions=IllegalArgumentException.class)
 181     public void test_appendValue_3arg_maxWidthTooBig() throws Exception {
 182         builder.appendValue(DAY_OF_MONTH, 2, 20, SignStyle.NORMAL);
 183     }
 184 
 185     @Test(expectedExceptions=IllegalArgumentException.class)
 186     public void test_appendValue_3arg_maxWidthMinWidth() throws Exception {
 187         builder.appendValue(DAY_OF_MONTH, 4, 2, SignStyle.NORMAL);
 188     }
 189 
 190     @Test(expectedExceptions=NullPointerException.class)
 191     public void test_appendValue_3arg_nullSignStyle() throws Exception {
 192         builder.appendValue(DAY_OF_MONTH, 2, 3, null);
 193     }
 194 
 195     //-----------------------------------------------------------------------
 196     @Test(expectedExceptions=NullPointerException.class)
 197     public void test_appendValueReduced_int_nullField() throws Exception {
 198         builder.appendValueReduced(null, 2, 2, 2000);
 199     }
 200 
 201     @Test(expectedExceptions=IllegalArgumentException.class)
 202     public void test_appendValueReduced_int_minWidthTooSmall() throws Exception {
 203         builder.appendValueReduced(YEAR, 0, 2, 2000);
 204     }
 205 
 206     @Test(expectedExceptions=IllegalArgumentException.class)
 207     public void test_appendValueReduced_int_minWidthTooBig() throws Exception {
 208         builder.appendValueReduced(YEAR, 11, 2, 2000);
 209     }
 210 
 211     @Test(expectedExceptions=IllegalArgumentException.class)
 212     public void test_appendValueReduced_int_maxWidthTooSmall() throws Exception {
 213         builder.appendValueReduced(YEAR, 2, 0, 2000);
 214     }
 215 
 216     @Test(expectedExceptions=IllegalArgumentException.class)
 217     public void test_appendValueReduced_int_maxWidthTooBig() throws Exception {
 218         builder.appendValueReduced(YEAR, 2, 11, 2000);
 219     }
 220 
 221     @Test(expectedExceptions=IllegalArgumentException.class)
 222     public void test_appendValueReduced_int_maxWidthLessThanMin() throws Exception {
 223         builder.appendValueReduced(YEAR, 2, 1, 2000);
 224     }
 225 
 226     //-----------------------------------------------------------------------
 227     @Test(expectedExceptions=NullPointerException.class)
 228     public void test_appendValueReduced_date_nullField() throws Exception {
 229         builder.appendValueReduced(null, 2, 2, LocalDate.of(2000, 1, 1));
 230     }
 231 
 232     @Test(expectedExceptions=NullPointerException.class)
 233     public void test_appendValueReduced_date_nullDate() throws Exception {
 234         builder.appendValueReduced(YEAR, 2, 2, null);
 235     }
 236 
 237     @Test(expectedExceptions=IllegalArgumentException.class)
 238     public void test_appendValueReduced_date_minWidthTooSmall() throws Exception {
 239         builder.appendValueReduced(YEAR, 0, 2, LocalDate.of(2000, 1, 1));
 240     }
 241 
 242     @Test(expectedExceptions=IllegalArgumentException.class)
 243     public void test_appendValueReduced_date_minWidthTooBig() throws Exception {
 244         builder.appendValueReduced(YEAR, 11, 2, LocalDate.of(2000, 1, 1));
 245     }
 246 
 247     @Test(expectedExceptions=IllegalArgumentException.class)
 248     public void test_appendValueReduced_date_maxWidthTooSmall() throws Exception {
 249         builder.appendValueReduced(YEAR, 2, 0, LocalDate.of(2000, 1, 1));
 250     }
 251 
 252     @Test(expectedExceptions=IllegalArgumentException.class)
 253     public void test_appendValueReduced_date_maxWidthTooBig() throws Exception {
 254         builder.appendValueReduced(YEAR, 2, 11, LocalDate.of(2000, 1, 1));
 255     }
 256 
 257     @Test(expectedExceptions=IllegalArgumentException.class)
 258     public void test_appendValueReduced_date_maxWidthLessThanMin() throws Exception {
 259         builder.appendValueReduced(YEAR, 2, 1, LocalDate.of(2000, 1, 1));
 260     }
 261 
 262     //-----------------------------------------------------------------------
 263     //-----------------------------------------------------------------------
 264     //-----------------------------------------------------------------------
 265     @Test(expectedExceptions=NullPointerException.class)
 266     public void test_appendFraction_4arg_nullRule() throws Exception {
 267         builder.appendFraction(null, 1, 9, false);
 268     }
 269 
 270     @Test(expectedExceptions=IllegalArgumentException.class)
 271     public void test_appendFraction_4arg_invalidRuleNotFixedSet() throws Exception {
 272         builder.appendFraction(DAY_OF_MONTH, 1, 9, false);
 273     }
 274 
 275     @Test(expectedExceptions=IllegalArgumentException.class)
 276     public void test_appendFraction_4arg_minTooSmall() throws Exception {
 277         builder.appendFraction(MINUTE_OF_HOUR, -1, 9, false);
 278     }
 279 
 280     @Test(expectedExceptions=IllegalArgumentException.class)
 281     public void test_appendFraction_4arg_minTooBig() throws Exception {
 282         builder.appendFraction(MINUTE_OF_HOUR, 10, 9, false);
 283     }
 284 
 285     @Test(expectedExceptions=IllegalArgumentException.class)
 286     public void test_appendFraction_4arg_maxTooSmall() throws Exception {
 287         builder.appendFraction(MINUTE_OF_HOUR, 0, -1, false);
 288     }
 289 
 290     @Test(expectedExceptions=IllegalArgumentException.class)
 291     public void test_appendFraction_4arg_maxTooBig() throws Exception {
 292         builder.appendFraction(MINUTE_OF_HOUR, 1, 10, false);
 293     }
 294 
 295     @Test(expectedExceptions=IllegalArgumentException.class)
 296     public void test_appendFraction_4arg_maxWidthMinWidth() throws Exception {
 297         builder.appendFraction(MINUTE_OF_HOUR, 9, 3, false);
 298     }
 299 
 300     //-----------------------------------------------------------------------
 301     //-----------------------------------------------------------------------
 302     //-----------------------------------------------------------------------
 303     @Test(expectedExceptions=NullPointerException.class)
 304     public void test_appendText_1arg_null() throws Exception {
 305         builder.appendText(null);
 306     }
 307 
 308     //-----------------------------------------------------------------------
 309     @Test(expectedExceptions=NullPointerException.class)
 310     public void test_appendText_2arg_nullRule() throws Exception {
 311         builder.appendText(null, TextStyle.SHORT);
 312     }
 313 
 314     @Test(expectedExceptions=NullPointerException.class)
 315     public void test_appendText_2arg_nullStyle() throws Exception {
 316         builder.appendText(MONTH_OF_YEAR, (TextStyle) null);
 317     }
 318 
 319     //-----------------------------------------------------------------------
 320     @Test(expectedExceptions=NullPointerException.class)
 321     public void test_appendTextMap_nullRule() throws Exception {
 322         builder.appendText(null, new HashMap<>());
 323     }
 324 
 325     @Test(expectedExceptions=NullPointerException.class)
 326     public void test_appendTextMap_nullStyle() throws Exception {
 327         builder.appendText(MONTH_OF_YEAR, (Map<Long, String>) null);
 328     }
 329 
 330     //-----------------------------------------------------------------------
 331     //-----------------------------------------------------------------------
 332     //-----------------------------------------------------------------------
 333     @DataProvider(name="offsetPatterns")
 334     Object[][] data_offsetPatterns() {
 335         return new Object[][] {
 336                 {"+HH", 2, 0, 0, "+02"},
 337                 {"+HH", -2, 0, 0, "-02"},
 338                 {"+HH", 2, 30, 0, "+02"},
 339                 {"+HH", 2, 0, 45, "+02"},
 340                 {"+HH", 2, 30, 45, "+02"},
 341 
 342                 {"+HHMM", 2, 0, 0, "+0200"},
 343                 {"+HHMM", -2, 0, 0, "-0200"},
 344                 {"+HHMM", 2, 30, 0, "+0230"},
 345                 {"+HHMM", 2, 0, 45, "+0200"},
 346                 {"+HHMM", 2, 30, 45, "+0230"},
 347 
 348                 {"+HH:MM", 2, 0, 0, "+02:00"},
 349                 {"+HH:MM", -2, 0, 0, "-02:00"},
 350                 {"+HH:MM", 2, 30, 0, "+02:30"},
 351                 {"+HH:MM", 2, 0, 45, "+02:00"},
 352                 {"+HH:MM", 2, 30, 45, "+02:30"},
 353 
 354                 {"+HHMMss", 2, 0, 0, "+0200"},
 355                 {"+HHMMss", -2, 0, 0, "-0200"},
 356                 {"+HHMMss", 2, 30, 0, "+0230"},
 357                 {"+HHMMss", 2, 0, 45, "+020045"},
 358                 {"+HHMMss", 2, 30, 45, "+023045"},
 359 
 360                 {"+HH:MM:ss", 2, 0, 0, "+02:00"},
 361                 {"+HH:MM:ss", -2, 0, 0, "-02:00"},
 362                 {"+HH:MM:ss", 2, 30, 0, "+02:30"},
 363                 {"+HH:MM:ss", 2, 0, 45, "+02:00:45"},
 364                 {"+HH:MM:ss", 2, 30, 45, "+02:30:45"},
 365 
 366                 {"+HHMMSS", 2, 0, 0, "+020000"},
 367                 {"+HHMMSS", -2, 0, 0, "-020000"},
 368                 {"+HHMMSS", 2, 30, 0, "+023000"},
 369                 {"+HHMMSS", 2, 0, 45, "+020045"},
 370                 {"+HHMMSS", 2, 30, 45, "+023045"},
 371 
 372                 {"+HH:MM:SS", 2, 0, 0, "+02:00:00"},
 373                 {"+HH:MM:SS", -2, 0, 0, "-02:00:00"},
 374                 {"+HH:MM:SS", 2, 30, 0, "+02:30:00"},
 375                 {"+HH:MM:SS", 2, 0, 45, "+02:00:45"},
 376                 {"+HH:MM:SS", 2, 30, 45, "+02:30:45"},
 377         };
 378     }
 379 
 380     @Test(dataProvider="offsetPatterns")
 381     public void test_appendOffset_format(String pattern, int h, int m, int s, String expected) throws Exception {
 382         builder.appendOffset(pattern, "Z");
 383         DateTimeFormatter f = builder.toFormatter();
 384         ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(h, m, s);
 385         assertEquals(f.format(offset), expected);
 386     }
 387 
 388     @Test(dataProvider="offsetPatterns")
 389     public void test_appendOffset_parse(String pattern, int h, int m, int s, String expected) throws Exception {
 390         builder.appendOffset(pattern, "Z");
 391         DateTimeFormatter f = builder.toFormatter();
 392         ZoneOffset parsed = f.parse(expected, ZoneOffset::from);
 393         assertEquals(f.format(parsed), expected);
 394     }
 395 
 396     @DataProvider(name="badOffsetPatterns")
 397     Object[][] data_badOffsetPatterns() {
 398         return new Object[][] {
 399             {"HH"},
 400             {"HHMM"},
 401             {"HH:MM"},
 402             {"HHMMss"},
 403             {"HH:MM:ss"},
 404             {"HHMMSS"},
 405             {"HH:MM:SS"},
 406             {"+H"},
 407             {"+HMM"},
 408             {"+HHM"},
 409             {"+A"},
 410         };
 411     }
 412 
 413     @Test(dataProvider="badOffsetPatterns", expectedExceptions=IllegalArgumentException.class)
 414     public void test_appendOffset_badPattern(String pattern) throws Exception {
 415         builder.appendOffset(pattern, "Z");
 416     }
 417 
 418     @Test(expectedExceptions=NullPointerException.class)
 419     public void test_appendOffset_3arg_nullText() throws Exception {
 420         builder.appendOffset("+HH:MM", null);
 421     }
 422 
 423     @Test(expectedExceptions=NullPointerException.class)
 424     public void test_appendOffset_3arg_nullPattern() throws Exception {
 425         builder.appendOffset(null, "Z");
 426     }
 427 
 428     //-----------------------------------------------------------------------
 429     //-----------------------------------------------------------------------
 430     //-----------------------------------------------------------------------
 431     @Test(expectedExceptions=NullPointerException.class)
 432     public void test_appendZoneText_1arg_nullText() throws Exception {
 433         builder.appendZoneText(null);
 434     }
 435 
 436     //-----------------------------------------------------------------------
 437     //-----------------------------------------------------------------------
 438     //-----------------------------------------------------------------------
 439     @Test
 440     public void test_padNext_1arg() {
 441         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':').padNext(2).appendValue(DAY_OF_MONTH);
 442         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2: 1");
 443     }
 444 
 445     @Test(expectedExceptions=IllegalArgumentException.class)
 446     public void test_padNext_1arg_invalidWidth() throws Exception {
 447         builder.padNext(0);
 448     }
 449 
 450     //-----------------------------------------------------------------------
 451     @Test
 452     public void test_padNext_2arg_dash() throws Exception {
 453         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':').padNext(2, '-').appendValue(DAY_OF_MONTH);
 454         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2:-1");
 455     }
 456 
 457     @Test(expectedExceptions=IllegalArgumentException.class)
 458     public void test_padNext_2arg_invalidWidth() throws Exception {
 459         builder.padNext(0, '-');
 460     }
 461 
 462     //-----------------------------------------------------------------------
 463     @Test
 464     public void test_padOptional() throws Exception {
 465         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':')
 466                 .padNext(5).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd()
 467                 .appendLiteral(':').appendValue(YEAR);
 468         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2:    1:2013");
 469         assertEquals(builder.toFormatter().format(YearMonth.of(2013, 2)), "2:     :2013");
 470     }
 471 
 472     //-----------------------------------------------------------------------
 473     //-----------------------------------------------------------------------
 474     //-----------------------------------------------------------------------
 475     @Test(expectedExceptions=IllegalStateException.class)
 476     public void test_optionalEnd_noStart() throws Exception {
 477         builder.optionalEnd();
 478     }
 479 
 480     //-----------------------------------------------------------------------
 481     //-----------------------------------------------------------------------
 482     //-----------------------------------------------------------------------
 483     @DataProvider(name="validPatterns")
 484     Object[][] dataValid() {
 485         return new Object[][] {
 486             {"'a'"},
 487             {"''"},
 488             {"'!'"},
 489             {"!"},
 490             {"'#'"},
 491 
 492             {"'hello_people,][)('"},
 493             {"'hi'"},
 494             {"'yyyy'"},
 495             {"''''"},
 496             {"'o''clock'"},
 497 
 498             {"G"},
 499             {"GG"},
 500             {"GGG"},
 501             {"GGGG"},
 502             {"GGGGG"},
 503 
 504             {"y"},
 505             {"yy"},
 506             {"yyy"},
 507             {"yyyy"},
 508             {"yyyyy"},
 509 
 510             {"M"},
 511             {"MM"},
 512             {"MMM"},
 513             {"MMMM"},
 514             {"MMMMM"},
 515 
 516             {"L"},
 517             {"LL"},
 518             {"LLL"},
 519             {"LLLL"},
 520             {"LLLLL"},
 521 
 522             {"D"},
 523             {"DD"},
 524             {"DDD"},
 525 
 526             {"d"},
 527             {"dd"},
 528 
 529             {"F"},
 530 
 531             {"Q"},
 532             {"QQ"},
 533             {"QQQ"},
 534             {"QQQQ"},
 535             {"QQQQQ"},
 536 
 537             {"q"},
 538             {"qq"},
 539             {"qqq"},
 540             {"qqqq"},
 541             {"qqqqq"},
 542 
 543             {"E"},
 544             {"EE"},
 545             {"EEE"},
 546             {"EEEE"},
 547             {"EEEEE"},
 548 
 549             {"e"},
 550             {"ee"},
 551             {"eee"},
 552             {"eeee"},
 553             {"eeeee"},
 554 
 555             {"c"},
 556             {"ccc"},
 557             {"cccc"},
 558             {"ccccc"},
 559 
 560             {"a"},
 561 
 562             {"H"},
 563             {"HH"},
 564 
 565             {"K"},
 566             {"KK"},
 567 
 568             {"k"},
 569             {"kk"},
 570 
 571             {"h"},
 572             {"hh"},
 573 
 574             {"m"},
 575             {"mm"},
 576 
 577             {"s"},
 578             {"ss"},
 579 
 580             {"S"},
 581             {"SS"},
 582             {"SSS"},
 583             {"SSSSSSSSS"},
 584 
 585             {"A"},
 586             {"AA"},
 587             {"AAA"},
 588 
 589             {"n"},
 590             {"nn"},
 591             {"nnn"},
 592 
 593             {"N"},
 594             {"NN"},
 595             {"NNN"},
 596 
 597             {"z"},
 598             {"zz"},
 599             {"zzz"},
 600             {"zzzz"},
 601 
 602             {"VV"},
 603 
 604             {"Z"},
 605             {"ZZ"},
 606             {"ZZZ"},
 607 
 608             {"X"},
 609             {"XX"},
 610             {"XXX"},
 611             {"XXXX"},
 612             {"XXXXX"},
 613 
 614             {"x"},
 615             {"xx"},
 616             {"xxx"},
 617             {"xxxx"},
 618             {"xxxxx"},
 619 
 620             {"ppH"},
 621             {"pppDD"},
 622 
 623             {"yyyy[-MM[-dd"},
 624             {"yyyy[-MM[-dd]]"},
 625             {"yyyy[-MM[]-dd]"},
 626 
 627             {"yyyy-MM-dd'T'HH:mm:ss.SSS"},
 628 
 629             {"e"},
 630             {"w"},
 631             {"ww"},
 632             {"W"},
 633             {"W"},
 634 
 635         };
 636     }
 637 
 638     @Test(dataProvider="validPatterns")
 639     public void test_appendPattern_valid(String input) throws Exception {
 640         builder.appendPattern(input);  // test is for no error here
 641     }
 642 
 643     //-----------------------------------------------------------------------
 644     @DataProvider(name="invalidPatterns")
 645     Object[][] dataInvalid() {
 646         return new Object[][] {
 647             {"'"},
 648             {"'hello"},
 649             {"'hel''lo"},
 650             {"'hello''"},
 651             {"{"},
 652             {"}"},
 653             {"{}"},
 654             {"#"},
 655             {"]"},
 656             {"yyyy]"},
 657             {"yyyy]MM"},
 658             {"yyyy[MM]]"},
 659 
 660             {"aa"},
 661             {"aaa"},
 662             {"aaaa"},
 663             {"aaaaa"},
 664             {"aaaaaa"},
 665             {"MMMMMM"},
 666             {"QQQQQQ"},
 667             {"qqqqqq"},
 668             {"EEEEEE"},
 669             {"eeeeee"},
 670             {"cc"},
 671             {"cccccc"},
 672             {"ddd"},
 673             {"DDDD"},
 674             {"FF"},
 675             {"FFF"},
 676             {"hhh"},
 677             {"HHH"},
 678             {"kkk"},
 679             {"KKK"},
 680             {"mmm"},
 681             {"sss"},
 682             {"OO"},
 683             {"OOO"},
 684             {"OOOOO"},
 685             {"XXXXXX"},
 686             {"zzzzz"},
 687             {"ZZZZZZ"},
 688 
 689             {"RO"},
 690 
 691             {"p"},
 692             {"pp"},
 693             {"p:"},
 694 
 695             {"f"},
 696             {"ff"},
 697             {"f:"},
 698             {"fy"},
 699             {"fa"},
 700             {"fM"},
 701 
 702             {"www"},
 703             {"WW"},
 704         };
 705     }
 706 
 707     @Test(dataProvider="invalidPatterns", expectedExceptions=IllegalArgumentException.class)
 708     public void test_appendPattern_invalid(String input) throws Exception {
 709         builder.appendPattern(input);  // test is for error here
 710     }
 711 
 712     //-----------------------------------------------------------------------
 713     @DataProvider(name="patternPrint")
 714     Object[][] data_patternPrint() {
 715         return new Object[][] {
 716             {"Q", date(2012, 2, 10), "1"},
 717             {"QQ", date(2012, 2, 10), "01"},
 718             {"QQQ", date(2012, 2, 10), "Q1"},
 719             {"QQQQ", date(2012, 2, 10), "1st quarter"},
 720             {"QQQQQ", date(2012, 2, 10), "1"},
 721         };
 722     }
 723 
 724     @Test(dataProvider="patternPrint")
 725     public void test_appendPattern_patternPrint(String input, Temporal temporal, String expected) throws Exception {
 726         DateTimeFormatter f = builder.appendPattern(input).toFormatter(Locale.UK);
 727         String test = f.format(temporal);
 728         assertEquals(test, expected);
 729     }
 730 
 731     private static Temporal date(int y, int m, int d) {
 732         return LocalDate.of(y, m, d);
 733     }
 734 
 735     //-----------------------------------------------------------------------
 736     @Test
 737     public void test_adjacent_strict_firstFixedWidth() throws Exception {
 738         // succeeds because both number elements are fixed width
 739         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
 740         ParsePosition pp = new ParsePosition(0);
 741         TemporalAccessor parsed = f.parseUnresolved("12309", pp);
 742         assertEquals(pp.getErrorIndex(), -1);
 743         assertEquals(pp.getIndex(), 5);
 744         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 745         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 746     }
 747 
 748     @Test
 749     public void test_adjacent_strict_firstVariableWidth_success() throws Exception {
 750         // succeeds greedily parsing variable width, then fixed width, to non-numeric Z
 751         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('Z').toFormatter(Locale.UK);
 752         ParsePosition pp = new ParsePosition(0);
 753         TemporalAccessor parsed = f.parseUnresolved("12309Z", pp);
 754         assertEquals(pp.getErrorIndex(), -1);
 755         assertEquals(pp.getIndex(), 6);
 756         assertEquals(parsed.getLong(HOUR_OF_DAY), 123L);
 757         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 9L);
 758     }
 759 
 760     @Test
 761     public void test_adjacent_strict_firstVariableWidth_fails() throws Exception {
 762         // fails because literal is a number and variable width parse greedily absorbs it
 763         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
 764         ParsePosition pp = new ParsePosition(0);
 765         TemporalAccessor parsed = f.parseUnresolved("12309", pp);
 766         assertEquals(pp.getErrorIndex(), 5);
 767         assertEquals(parsed, null);
 768     }
 769 
 770     @Test
 771     public void test_adjacent_lenient() throws Exception {
 772         // succeeds because both number elements are fixed width even in lenient mode
 773         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
 774         ParsePosition pp = new ParsePosition(0);
 775         TemporalAccessor parsed = f.parseUnresolved("12309", pp);
 776         assertEquals(pp.getErrorIndex(), -1);
 777         assertEquals(pp.getIndex(), 5);
 778         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 779         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 780     }
 781 
 782     @Test
 783     public void test_adjacent_lenient_firstVariableWidth_success() throws Exception {
 784         // succeeds greedily parsing variable width, then fixed width, to non-numeric Z
 785         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('Z').toFormatter(Locale.UK);
 786         ParsePosition pp = new ParsePosition(0);
 787         TemporalAccessor parsed = f.parseUnresolved("12309Z", pp);
 788         assertEquals(pp.getErrorIndex(), -1);
 789         assertEquals(pp.getIndex(), 6);
 790         assertEquals(parsed.getLong(HOUR_OF_DAY), 123L);
 791         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 9L);
 792     }
 793 
 794     @Test
 795     public void test_adjacent_lenient_firstVariableWidth_fails() throws Exception {
 796         // fails because literal is a number and variable width parse greedily absorbs it
 797         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
 798         ParsePosition pp = new ParsePosition(0);
 799         TemporalAccessor parsed = f.parseUnresolved("12309", pp);
 800         assertEquals(pp.getErrorIndex(), 5);
 801         assertEquals(parsed, null);
 802     }
 803 
 804     //-----------------------------------------------------------------------
 805     @Test
 806     public void test_adjacent_strict_fractionFollows() throws Exception {
 807         // succeeds because hour/min are fixed width
 808         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 0, 3, false).toFormatter(Locale.UK);
 809         ParsePosition pp = new ParsePosition(0);
 810         TemporalAccessor parsed = f.parseUnresolved("1230567", pp);
 811         assertEquals(pp.getErrorIndex(), -1);
 812         assertEquals(pp.getIndex(), 7);
 813         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 814         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 815         assertEquals(parsed.getLong(NANO_OF_SECOND), 567_000_000L);
 816     }
 817 
 818     @Test
 819     public void test_adjacent_strict_fractionFollows_2digit() throws Exception {
 820         // succeeds because hour/min are fixed width
 821         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 0, 3, false).toFormatter(Locale.UK);
 822         ParsePosition pp = new ParsePosition(0);
 823         TemporalAccessor parsed = f.parseUnresolved("123056", pp);
 824         assertEquals(pp.getErrorIndex(), -1);
 825         assertEquals(pp.getIndex(), 6);
 826         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 827         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 828         assertEquals(parsed.getLong(NANO_OF_SECOND), 560_000_000L);
 829     }
 830 
 831     @Test
 832     public void test_adjacent_strict_fractionFollows_0digit() throws Exception {
 833         // succeeds because hour/min are fixed width
 834         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 0, 3, false).toFormatter(Locale.UK);
 835         ParsePosition pp = new ParsePosition(0);
 836         TemporalAccessor parsed = f.parseUnresolved("1230", pp);
 837         assertEquals(pp.getErrorIndex(), -1);
 838         assertEquals(pp.getIndex(), 4);
 839         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 840         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 841     }
 842 
 843     @Test
 844     public void test_adjacent_lenient_fractionFollows() throws Exception {
 845         // succeeds because hour/min are fixed width
 846         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 3, 3, false).toFormatter(Locale.UK);
 847         ParsePosition pp = new ParsePosition(0);
 848         TemporalAccessor parsed = f.parseUnresolved("1230567", pp);
 849         assertEquals(pp.getErrorIndex(), -1);
 850         assertEquals(pp.getIndex(), 7);
 851         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 852         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 853         assertEquals(parsed.getLong(NANO_OF_SECOND), 567_000_000L);
 854     }
 855 
 856     @Test
 857     public void test_adjacent_lenient_fractionFollows_2digit() throws Exception {
 858         // succeeds because hour/min are fixed width
 859         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 3, 3, false).toFormatter(Locale.UK);
 860         ParsePosition pp = new ParsePosition(0);
 861         TemporalAccessor parsed = f.parseUnresolved("123056", pp);
 862         assertEquals(pp.getErrorIndex(), -1);
 863         assertEquals(pp.getIndex(), 6);
 864         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 865         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 866         assertEquals(parsed.getLong(NANO_OF_SECOND), 560_000_000L);
 867     }
 868 
 869     @Test
 870     public void test_adjacent_lenient_fractionFollows_0digit() throws Exception {
 871         // succeeds because hour/min are fixed width
 872         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 3, 3, false).toFormatter(Locale.UK);
 873         ParsePosition pp = new ParsePosition(0);
 874         TemporalAccessor parsed = f.parseUnresolved("1230", pp);
 875         assertEquals(pp.getErrorIndex(), -1);
 876         assertEquals(pp.getIndex(), 4);
 877         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 878         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 879     }
 880 
 881 }