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.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             {"g"},
 665             {"ggggg"},
 666         };
 667     }
 668 
 669     @Test(dataProvider="validPatterns")
 670     public void test_appendPattern_valid(String input) throws Exception {
 671         builder.appendPattern(input);  // test is for no error here
 672     }
 673 
 674     //-----------------------------------------------------------------------
 675     @DataProvider(name="invalidPatterns")
 676     Object[][] dataInvalid() {
 677         return new Object[][] {
 678             {"'"},
 679             {"'hello"},
 680             {"'hel''lo"},
 681             {"'hello''"},
 682             {"{"},
 683             {"}"},
 684             {"{}"},
 685             {"#"},
 686             {"]"},
 687             {"yyyy]"},
 688             {"yyyy]MM"},
 689             {"yyyy[MM]]"},
 690 
 691             {"aa"},
 692             {"aaa"},
 693             {"aaaa"},
 694             {"aaaaa"},
 695             {"aaaaaa"},
 696             {"MMMMMM"},
 697             {"QQQQQQ"},
 698             {"qqqqqq"},
 699             {"EEEEEE"},
 700             {"eeeeee"},
 701             {"cc"},
 702             {"cccccc"},
 703             {"ddd"},
 704             {"DDDD"},
 705             {"FF"},
 706             {"FFF"},
 707             {"hhh"},
 708             {"HHH"},
 709             {"kkk"},
 710             {"KKK"},
 711             {"mmm"},
 712             {"sss"},
 713             {"OO"},
 714             {"OOO"},
 715             {"OOOOO"},
 716             {"XXXXXX"},
 717             {"zzzzz"},
 718             {"ZZZZZZ"},
 719 
 720             {"RO"},
 721 
 722             {"p"},
 723             {"pp"},
 724             {"p:"},
 725 
 726             {"f"},
 727             {"ff"},
 728             {"f:"},
 729             {"fy"},
 730             {"fa"},
 731             {"fM"},
 732 
 733             {"www"},
 734             {"WW"},
 735         };
 736     }
 737 
 738     @Test(dataProvider="invalidPatterns", expectedExceptions=IllegalArgumentException.class)
 739     public void test_appendPattern_invalid(String input) throws Exception {
 740         builder.appendPattern(input);  // test is for error here
 741     }
 742 
 743     //-----------------------------------------------------------------------
 744     @DataProvider(name="patternPrint")
 745     Object[][] data_patternPrint() {
 746         return new Object[][] {
 747             {"Q", date(2012, 2, 10), "1"},
 748             {"QQ", date(2012, 2, 10), "01"},
 749             {"QQQ", date(2012, 2, 10), "Q1"},
 750             {"QQQQ", date(2012, 2, 10), "1st quarter"},
 751             {"QQQQQ", date(2012, 2, 10), "1"},
 752         };
 753     }
 754 
 755     @Test(dataProvider="patternPrint")
 756     public void test_appendPattern_patternPrint(String input, Temporal temporal, String expected) throws Exception {
 757         DateTimeFormatter f = builder.appendPattern(input).toFormatter(Locale.UK);
 758         String test = f.format(temporal);
 759         assertEquals(test, expected);
 760     }
 761 
 762     private static Temporal date(int y, int m, int d) {
 763         return LocalDate.of(y, m, d);
 764     }
 765 
 766     //-----------------------------------------------------------------------
 767     @DataProvider(name="modJulianFieldPattern")
 768     Object[][] data_modJuilanFieldPattern() {
 769         return new Object[][] {
 770             {"g", "1"},
 771             {"g", "123456"},
 772             {"gggggg", "123456"},
 773         };
 774     }
 775 
 776     @Test(dataProvider="modJulianFieldPattern")
 777     public void test_modJulianFieldPattern(String pattern, String input) throws Exception {
 778         DateTimeFormatter.ofPattern(pattern).parse(input);
 779     }
 780 
 781     @DataProvider(name="modJulianFieldValues")
 782     Object[][] data_modJuilanFieldValues() {
 783         return new Object[][] {
 784             {1970, 1, 1, "40587"},
 785             {1858, 11, 17, "0"},
 786             {1858, 11, 16, "-1"},
 787         };
 788     }
 789 
 790     @Test(dataProvider="modJulianFieldValues")
 791     public void test_modJulianFieldValues(int y, int m, int d, String expected) throws Exception {
 792         DateTimeFormatter df = new DateTimeFormatterBuilder().appendPattern("g").toFormatter();
 793          assertEquals(LocalDate.of(y, m, d).format(df), expected);
 794     }
 795 
 796     //-----------------------------------------------------------------------
 797     @DataProvider(name="dayOfYearFieldPattern")
 798     Object[][] data_dayOfYearFieldPattern() {
 799         return new Object[][] {
 800                 {"D", "1"},
 801                 {"D", "123"},
 802                 {"DD", "12"},
 803                 {"DD", "123"},
 804                 {"DDD", "123"},
 805                 {"DDD", "12345"},
 806         };
 807     }
 808 
 809     @Test(dataProvider="dayOfYearFieldPattern")
 810     public void test_dayOfYearFieldPattern(String pattern, String input) throws Exception {
 811         DateTimeFormatter.ofPattern(pattern).parse(input);
 812     }
 813 
 814     @DataProvider(name="dayOfYearFieldValues")
 815     Object[][] data_dayOfYearFieldValues() {
 816         return new Object[][] {
 817                 {2016, 1, 1, "D", "1"},
 818                 {2016, 1, 31, "D", "31"},
 819                 {2016, 1, 1, "DD", "01"},
 820                 {2016, 1, 31, "DD", "31"},
 821                 {2016, 4, 9, "DD", "100"},
 822                 {2016, 1, 1, "DDD", "001"},
 823                 {2016, 1, 31, "DDD", "031"},
 824                 {2016, 4, 9, "DDD", "100"},
 825         };
 826     }
 827 
 828     @Test(dataProvider="dayOfYearFieldValues")
 829     public void test_dayOfYearFieldValues(int y, int m, int d, String pattern, String expected) throws Exception {
 830         DateTimeFormatter df = new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();
 831         assertEquals(LocalDate.of(y, m, d).format(df), expected);
 832     }
 833     //-----------------------------------------------------------------------
 834     @Test
 835     public void test_adjacent_strict_firstFixedWidth() throws Exception {
 836         // succeeds because both number elements are fixed width
 837         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
 838         ParsePosition pp = new ParsePosition(0);
 839         TemporalAccessor parsed = f.parseUnresolved("12309", pp);
 840         assertEquals(pp.getErrorIndex(), -1);
 841         assertEquals(pp.getIndex(), 5);
 842         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 843         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 844     }
 845 
 846     @Test
 847     public void test_adjacent_strict_firstVariableWidth_success() throws Exception {
 848         // succeeds greedily parsing variable width, then fixed width, to non-numeric Z
 849         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('Z').toFormatter(Locale.UK);
 850         ParsePosition pp = new ParsePosition(0);
 851         TemporalAccessor parsed = f.parseUnresolved("12309Z", pp);
 852         assertEquals(pp.getErrorIndex(), -1);
 853         assertEquals(pp.getIndex(), 6);
 854         assertEquals(parsed.getLong(HOUR_OF_DAY), 123L);
 855         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 9L);
 856     }
 857 
 858     @Test
 859     public void test_adjacent_strict_firstVariableWidth_fails() throws Exception {
 860         // fails because literal is a number and variable width parse greedily absorbs it
 861         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
 862         ParsePosition pp = new ParsePosition(0);
 863         TemporalAccessor parsed = f.parseUnresolved("12309", pp);
 864         assertEquals(pp.getErrorIndex(), 5);
 865         assertEquals(parsed, null);
 866     }
 867 
 868     @Test
 869     public void test_adjacent_lenient() throws Exception {
 870         // succeeds because both number elements are fixed width even in lenient mode
 871         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
 872         ParsePosition pp = new ParsePosition(0);
 873         TemporalAccessor parsed = f.parseUnresolved("12309", pp);
 874         assertEquals(pp.getErrorIndex(), -1);
 875         assertEquals(pp.getIndex(), 5);
 876         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 877         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 878     }
 879 
 880     @Test
 881     public void test_adjacent_lenient_firstVariableWidth_success() throws Exception {
 882         // succeeds greedily parsing variable width, then fixed width, to non-numeric Z
 883         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('Z').toFormatter(Locale.UK);
 884         ParsePosition pp = new ParsePosition(0);
 885         TemporalAccessor parsed = f.parseUnresolved("12309Z", pp);
 886         assertEquals(pp.getErrorIndex(), -1);
 887         assertEquals(pp.getIndex(), 6);
 888         assertEquals(parsed.getLong(HOUR_OF_DAY), 123L);
 889         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 9L);
 890     }
 891 
 892     @Test
 893     public void test_adjacent_lenient_firstVariableWidth_fails() throws Exception {
 894         // fails because literal is a number and variable width parse greedily absorbs it
 895         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY).appendValue(MINUTE_OF_HOUR, 2).appendLiteral('9').toFormatter(Locale.UK);
 896         ParsePosition pp = new ParsePosition(0);
 897         TemporalAccessor parsed = f.parseUnresolved("12309", pp);
 898         assertEquals(pp.getErrorIndex(), 5);
 899         assertEquals(parsed, null);
 900     }
 901 
 902     //-----------------------------------------------------------------------
 903     @Test
 904     public void test_adjacent_strict_fractionFollows() throws Exception {
 905         // succeeds because hour/min are fixed width
 906         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 0, 3, false).toFormatter(Locale.UK);
 907         ParsePosition pp = new ParsePosition(0);
 908         TemporalAccessor parsed = f.parseUnresolved("1230567", pp);
 909         assertEquals(pp.getErrorIndex(), -1);
 910         assertEquals(pp.getIndex(), 7);
 911         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 912         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 913         assertEquals(parsed.getLong(NANO_OF_SECOND), 567_000_000L);
 914     }
 915 
 916     @Test
 917     public void test_adjacent_strict_fractionFollows_2digit() throws Exception {
 918         // succeeds because hour/min are fixed width
 919         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 0, 3, false).toFormatter(Locale.UK);
 920         ParsePosition pp = new ParsePosition(0);
 921         TemporalAccessor parsed = f.parseUnresolved("123056", pp);
 922         assertEquals(pp.getErrorIndex(), -1);
 923         assertEquals(pp.getIndex(), 6);
 924         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 925         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 926         assertEquals(parsed.getLong(NANO_OF_SECOND), 560_000_000L);
 927     }
 928 
 929     @Test
 930     public void test_adjacent_strict_fractionFollows_0digit() throws Exception {
 931         // succeeds because hour/min are fixed width
 932         DateTimeFormatter f = builder.appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 0, 3, false).toFormatter(Locale.UK);
 933         ParsePosition pp = new ParsePosition(0);
 934         TemporalAccessor parsed = f.parseUnresolved("1230", pp);
 935         assertEquals(pp.getErrorIndex(), -1);
 936         assertEquals(pp.getIndex(), 4);
 937         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 938         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 939     }
 940 
 941     @Test
 942     public void test_adjacent_lenient_fractionFollows() throws Exception {
 943         // succeeds because hour/min are fixed width
 944         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 3, 3, false).toFormatter(Locale.UK);
 945         ParsePosition pp = new ParsePosition(0);
 946         TemporalAccessor parsed = f.parseUnresolved("1230567", pp);
 947         assertEquals(pp.getErrorIndex(), -1);
 948         assertEquals(pp.getIndex(), 7);
 949         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 950         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 951         assertEquals(parsed.getLong(NANO_OF_SECOND), 567_000_000L);
 952     }
 953 
 954     @Test
 955     public void test_adjacent_lenient_fractionFollows_2digit() throws Exception {
 956         // succeeds because hour/min are fixed width
 957         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 3, 3, false).toFormatter(Locale.UK);
 958         ParsePosition pp = new ParsePosition(0);
 959         TemporalAccessor parsed = f.parseUnresolved("123056", pp);
 960         assertEquals(pp.getErrorIndex(), -1);
 961         assertEquals(pp.getIndex(), 6);
 962         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 963         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 964         assertEquals(parsed.getLong(NANO_OF_SECOND), 560_000_000L);
 965     }
 966 
 967     @Test
 968     public void test_adjacent_lenient_fractionFollows_0digit() throws Exception {
 969         // succeeds because hour/min are fixed width
 970         DateTimeFormatter f = builder.parseLenient().appendValue(HOUR_OF_DAY, 2).appendValue(MINUTE_OF_HOUR, 2).appendFraction(NANO_OF_SECOND, 3, 3, false).toFormatter(Locale.UK);
 971         ParsePosition pp = new ParsePosition(0);
 972         TemporalAccessor parsed = f.parseUnresolved("1230", pp);
 973         assertEquals(pp.getErrorIndex(), -1);
 974         assertEquals(pp.getIndex(), 4);
 975         assertEquals(parsed.getLong(HOUR_OF_DAY), 12L);
 976         assertEquals(parsed.getLong(MINUTE_OF_HOUR), 30L);
 977     }
 978 
 979     @DataProvider(name="lenientOffsetParseData")
 980     Object[][] data_lenient_offset_parse() {
 981         return new Object[][] {
 982             {"+HH", "+01", 3600},
 983             {"+HH", "+0101", 3660},
 984             {"+HH", "+010101", 3661},
 985             {"+HH", "+01", 3600},
 986             {"+HH", "+01:01", 3660},
 987             {"+HH", "+01:01:01", 3661},
 988             {"+HHmm", "+01", 3600},
 989             {"+HHmm", "+0101", 3660},
 990             {"+HHmm", "+010101", 3661},
 991             {"+HH:mm", "+01", 3600},
 992             {"+HH:mm", "+01:01", 3660},
 993             {"+HH:mm", "+01:01:01", 3661},
 994             {"+HHMM", "+01", 3600},
 995             {"+HHMM", "+0101", 3660},
 996             {"+HHMM", "+010101", 3661},
 997             {"+HH:MM", "+01", 3600},
 998             {"+HH:MM", "+01:01", 3660},
 999             {"+HH:MM", "+01:01:01", 3661},
1000             {"+HHMMss", "+01", 3600},
1001             {"+HHMMss", "+0101", 3660},
1002             {"+HHMMss", "+010101", 3661},
1003             {"+HH:MM:ss", "+01", 3600},
1004             {"+HH:MM:ss", "+01:01", 3660},
1005             {"+HH:MM:ss", "+01:01:01", 3661},
1006             {"+HHMMSS", "+01", 3600},
1007             {"+HHMMSS", "+0101", 3660},
1008             {"+HHMMSS", "+010101", 3661},
1009             {"+HH:MM:SS", "+01", 3600},
1010             {"+HH:MM:SS", "+01:01", 3660},
1011             {"+HH:MM:SS", "+01:01:01", 3661},
1012             {"+HHmmss", "+01", 3600},
1013             {"+HHmmss", "+0101", 3660},
1014             {"+HHmmss", "+010101", 3661},
1015             {"+HH:mm:ss", "+01", 3600},
1016             {"+HH:mm:ss", "+01:01", 3660},
1017             {"+HH:mm:ss", "+01:01:01", 3661},
1018         };
1019     }
1020 
1021     @Test(dataProvider="lenientOffsetParseData")
1022     public void test_lenient_offset_parse_1(String pattern, String offset, int offsetSeconds) {
1023         assertEquals(new DateTimeFormatterBuilder().parseLenient().appendOffset(pattern, "Z").toFormatter().parse(offset).get(OFFSET_SECONDS),
1024                      offsetSeconds);
1025     }
1026 
1027     @Test
1028     public void test_lenient_offset_parse_2() {
1029         assertEquals(new DateTimeFormatterBuilder().parseLenient().appendOffsetId().toFormatter().parse("+01").get(OFFSET_SECONDS),
1030                      3600);
1031     }
1032 
1033     @Test(expectedExceptions=DateTimeParseException.class)
1034     public void test_strict_appendOffsetId() {
1035         assertEquals(new DateTimeFormatterBuilder().appendOffsetId().toFormatter().parse("+01").get(OFFSET_SECONDS),
1036                      3600);
1037     }
1038 
1039     @Test(expectedExceptions=DateTimeParseException.class)
1040     public void test_strict_appendOffset_1() {
1041         assertEquals(new DateTimeFormatterBuilder().appendOffset("+HH:MM:ss", "Z").toFormatter().parse("+01").get(OFFSET_SECONDS),
1042                      3600);
1043     }
1044 
1045     @Test(expectedExceptions=DateTimeParseException.class)
1046     public void test_strict_appendOffset_2() {
1047         assertEquals(new DateTimeFormatterBuilder().appendOffset("+HHMMss", "Z").toFormatter().parse("+01").get(OFFSET_SECONDS),
1048                      3600);
1049     }
1050 
1051     @Test
1052     public void test_basic_iso_date() {
1053         assertEquals(BASIC_ISO_DATE.parse("20021231+01").get(OFFSET_SECONDS), 3600);
1054         assertEquals(BASIC_ISO_DATE.parse("20021231+0101").get(OFFSET_SECONDS), 3660);
1055     }
1056 
1057 }