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