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