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 java.time.LocalDate;
  63 import java.time.ZoneOffset;
  64 import java.time.format.*;
  65 
  66 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
  67 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
  68 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
  69 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
  70 import static java.time.temporal.ChronoField.YEAR;
  71 import static org.testng.Assert.assertEquals;
  72 
  73 import java.text.ParsePosition;
  74 import java.util.HashMap;
  75 import java.util.Locale;
  76 import java.util.Map;
  77 
  78 import java.time.format.DateTimeBuilder;
  79 import java.time.temporal.Temporal;
  80 
  81 import org.testng.annotations.BeforeMethod;
  82 import org.testng.annotations.DataProvider;
  83 import org.testng.annotations.Test;
  84 
  85 /**
  86  * Test DateTimeFormatterBuilder.
  87  */
  88 @Test
  89 public class TCKDateTimeFormatterBuilder {
  90 
  91     private DateTimeFormatterBuilder builder;
  92 
  93     @BeforeMethod(groups={"tck"})
  94     public void setUp() {
  95         builder = new DateTimeFormatterBuilder();
  96     }
  97 
  98     //-----------------------------------------------------------------------
  99     @Test(groups={"tck"})
 100     public void test_toFormatter_empty() throws Exception {
 101         DateTimeFormatter f = builder.toFormatter();
 102         assertEquals(f.toString(), "");
 103     }
 104 
 105     //-----------------------------------------------------------------------
 106     @Test(groups={"tck"})
 107     public void test_parseCaseSensitive() throws Exception {
 108         builder.parseCaseSensitive();
 109         DateTimeFormatter f = builder.toFormatter();
 110         assertEquals(f.toString(), "ParseCaseSensitive(true)");
 111     }
 112 
 113     @Test(groups={"tck"})
 114     public void test_parseCaseInsensitive() throws Exception {
 115         builder.parseCaseInsensitive();
 116         DateTimeFormatter f = builder.toFormatter();
 117         assertEquals(f.toString(), "ParseCaseSensitive(false)");
 118     }
 119 
 120     //-----------------------------------------------------------------------
 121     @Test(groups={"tck"})
 122     public void test_parseStrict() throws Exception {
 123         builder.parseStrict();
 124         DateTimeFormatter f = builder.toFormatter();
 125         assertEquals(f.toString(), "ParseStrict(true)");
 126     }
 127 
 128     @Test(groups={"tck"})
 129     public void test_parseLenient() throws Exception {
 130         builder.parseLenient();
 131         DateTimeFormatter f = builder.toFormatter();
 132         assertEquals(f.toString(), "ParseStrict(false)");
 133     }
 134 
 135     //-----------------------------------------------------------------------
 136     @Test(groups={"tck"})
 137     public void test_appendValue_1arg() throws Exception {
 138         builder.appendValue(DAY_OF_MONTH);
 139         DateTimeFormatter f = builder.toFormatter();
 140         assertEquals(f.toString(), "Value(DayOfMonth)");
 141     }
 142 
 143     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 144     public void test_appendValue_1arg_null() throws Exception {
 145         builder.appendValue(null);
 146     }
 147 
 148     //-----------------------------------------------------------------------
 149     @Test(groups={"tck"})
 150     public void test_appendValue_2arg() throws Exception {
 151         builder.appendValue(DAY_OF_MONTH, 3);
 152         DateTimeFormatter f = builder.toFormatter();
 153         assertEquals(f.toString(), "Value(DayOfMonth,3)");
 154     }
 155 
 156     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 157     public void test_appendValue_2arg_null() throws Exception {
 158         builder.appendValue(null, 3);
 159     }
 160 
 161     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 162     public void test_appendValue_2arg_widthTooSmall() throws Exception {
 163         builder.appendValue(DAY_OF_MONTH, 0);
 164     }
 165 
 166     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 167     public void test_appendValue_2arg_widthTooBig() throws Exception {
 168         builder.appendValue(DAY_OF_MONTH, 20);
 169     }
 170 
 171     //-----------------------------------------------------------------------
 172     @Test(groups={"tck"})
 173     public void test_appendValue_3arg() throws Exception {
 174         builder.appendValue(DAY_OF_MONTH, 2, 3, SignStyle.NORMAL);
 175         DateTimeFormatter f = builder.toFormatter();
 176         assertEquals(f.toString(), "Value(DayOfMonth,2,3,NORMAL)");
 177     }
 178 
 179     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 180     public void test_appendValue_3arg_nullField() throws Exception {
 181         builder.appendValue(null, 2, 3, SignStyle.NORMAL);
 182     }
 183 
 184     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 185     public void test_appendValue_3arg_minWidthTooSmall() throws Exception {
 186         builder.appendValue(DAY_OF_MONTH, 0, 2, SignStyle.NORMAL);
 187     }
 188 
 189     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 190     public void test_appendValue_3arg_minWidthTooBig() throws Exception {
 191         builder.appendValue(DAY_OF_MONTH, 20, 2, SignStyle.NORMAL);
 192     }
 193 
 194     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 195     public void test_appendValue_3arg_maxWidthTooSmall() throws Exception {
 196         builder.appendValue(DAY_OF_MONTH, 2, 0, SignStyle.NORMAL);
 197     }
 198 
 199     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 200     public void test_appendValue_3arg_maxWidthTooBig() throws Exception {
 201         builder.appendValue(DAY_OF_MONTH, 2, 20, SignStyle.NORMAL);
 202     }
 203 
 204     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 205     public void test_appendValue_3arg_maxWidthMinWidth() throws Exception {
 206         builder.appendValue(DAY_OF_MONTH, 4, 2, SignStyle.NORMAL);
 207     }
 208 
 209     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 210     public void test_appendValue_3arg_nullSignStyle() throws Exception {
 211         builder.appendValue(DAY_OF_MONTH, 2, 3, null);
 212     }
 213 
 214     //-----------------------------------------------------------------------
 215     @Test(groups={"tck"})
 216     public void test_appendValue_subsequent2_parse3() throws Exception {
 217         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2);
 218         DateTimeFormatter f = builder.toFormatter();
 219         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)");
 220         DateTimeBuilder cal = f.parseToBuilder("123", new ParsePosition(0));
 221         assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(1));
 222         assertEquals(cal.getFieldValueMap().get(DAY_OF_MONTH), Long.valueOf(23));
 223     }
 224 
 225     @Test(groups={"tck"})
 226     public void test_appendValue_subsequent2_parse4() throws Exception {
 227         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2);
 228         DateTimeFormatter f = builder.toFormatter();
 229         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)");
 230         DateTimeBuilder cal = f.parseToBuilder("0123", new ParsePosition(0));
 231         assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(1));
 232         assertEquals(cal.getFieldValueMap().get(DAY_OF_MONTH), Long.valueOf(23));
 233     }
 234 
 235     @Test(groups={"tck"})
 236     public void test_appendValue_subsequent2_parse5() throws Exception {
 237         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2).appendLiteral('4');
 238         DateTimeFormatter f = builder.toFormatter();
 239         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)'4'");
 240         DateTimeBuilder cal = f.parseToBuilder("01234", new ParsePosition(0));
 241         assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(1));
 242         assertEquals(cal.getFieldValueMap().get(DAY_OF_MONTH), Long.valueOf(23));
 243     }
 244 
 245     @Test(groups={"tck"})
 246     public void test_appendValue_subsequent3_parse6() throws Exception {
 247         builder
 248             .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
 249             .appendValue(MONTH_OF_YEAR, 2)
 250             .appendValue(DAY_OF_MONTH, 2);
 251         DateTimeFormatter f = builder.toFormatter();
 252         assertEquals(f.toString(), "Value(Year,4,10,EXCEEDS_PAD)Value(MonthOfYear,2)Value(DayOfMonth,2)");
 253         DateTimeBuilder cal = f.parseToBuilder("20090630", new ParsePosition(0));
 254         assertEquals(cal.getFieldValueMap().get(YEAR), Long.valueOf(2009));
 255         assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(6));
 256         assertEquals(cal.getFieldValueMap().get(DAY_OF_MONTH), Long.valueOf(30));
 257     }
 258 
 259     //-----------------------------------------------------------------------
 260     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 261     public void test_appendValueReduced_null() throws Exception {
 262         builder.appendValueReduced(null, 2, 2000);
 263     }
 264 
 265     @Test(groups={"tck"})
 266     public void test_appendValueReduced() throws Exception {
 267         builder.appendValueReduced(YEAR, 2, 2000);
 268         DateTimeFormatter f = builder.toFormatter();
 269         assertEquals(f.toString(), "ReducedValue(Year,2,2000)");
 270         DateTimeBuilder cal = f.parseToBuilder("12", new ParsePosition(0));
 271         assertEquals(cal.getFieldValueMap().get(YEAR), Long.valueOf(2012));
 272     }
 273 
 274     @Test(groups={"tck"})
 275     public void test_appendValueReduced_subsequent_parse() throws Exception {
 276         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValueReduced(YEAR, 2, 2000);
 277         DateTimeFormatter f = builder.toFormatter();
 278         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)ReducedValue(Year,2,2000)");
 279         DateTimeBuilder cal = f.parseToBuilder("123", new ParsePosition(0));
 280         assertEquals(cal.getFieldValueMap().get(MONTH_OF_YEAR), Long.valueOf(1));
 281         assertEquals(cal.getFieldValueMap().get(YEAR), Long.valueOf(2023));
 282     }
 283 
 284     //-----------------------------------------------------------------------
 285     //-----------------------------------------------------------------------
 286     //-----------------------------------------------------------------------
 287     @Test(groups={"tck"})
 288     public void test_appendFraction_4arg() throws Exception {
 289         builder.appendFraction(MINUTE_OF_HOUR, 1, 9, false);
 290         DateTimeFormatter f = builder.toFormatter();
 291         assertEquals(f.toString(), "Fraction(MinuteOfHour,1,9)");
 292     }
 293 
 294     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 295     public void test_appendFraction_4arg_nullRule() throws Exception {
 296         builder.appendFraction(null, 1, 9, false);
 297     }
 298 
 299     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 300     public void test_appendFraction_4arg_invalidRuleNotFixedSet() throws Exception {
 301         builder.appendFraction(DAY_OF_MONTH, 1, 9, false);
 302     }
 303 
 304     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 305     public void test_appendFraction_4arg_minTooSmall() throws Exception {
 306         builder.appendFraction(MINUTE_OF_HOUR, -1, 9, false);
 307     }
 308 
 309     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 310     public void test_appendFraction_4arg_minTooBig() throws Exception {
 311         builder.appendFraction(MINUTE_OF_HOUR, 10, 9, false);
 312     }
 313 
 314     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 315     public void test_appendFraction_4arg_maxTooSmall() throws Exception {
 316         builder.appendFraction(MINUTE_OF_HOUR, 0, -1, false);
 317     }
 318 
 319     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 320     public void test_appendFraction_4arg_maxTooBig() throws Exception {
 321         builder.appendFraction(MINUTE_OF_HOUR, 1, 10, false);
 322     }
 323 
 324     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 325     public void test_appendFraction_4arg_maxWidthMinWidth() throws Exception {
 326         builder.appendFraction(MINUTE_OF_HOUR, 9, 3, false);
 327     }
 328 
 329     //-----------------------------------------------------------------------
 330     //-----------------------------------------------------------------------
 331     //-----------------------------------------------------------------------
 332     @Test(groups={"tck"})
 333     public void test_appendText_1arg() throws Exception {
 334         builder.appendText(MONTH_OF_YEAR);
 335         DateTimeFormatter f = builder.toFormatter();
 336         assertEquals(f.toString(), "Text(MonthOfYear)");
 337     }
 338 
 339     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 340     public void test_appendText_1arg_null() throws Exception {
 341         builder.appendText(null);
 342     }
 343 
 344     //-----------------------------------------------------------------------
 345     @Test(groups={"tck"})
 346     public void test_appendText_2arg() throws Exception {
 347         builder.appendText(MONTH_OF_YEAR, TextStyle.SHORT);
 348         DateTimeFormatter f = builder.toFormatter();
 349         assertEquals(f.toString(), "Text(MonthOfYear,SHORT)");
 350     }
 351 
 352     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 353     public void test_appendText_2arg_nullRule() throws Exception {
 354         builder.appendText(null, TextStyle.SHORT);
 355     }
 356 
 357     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 358     public void test_appendText_2arg_nullStyle() throws Exception {
 359         builder.appendText(MONTH_OF_YEAR, (TextStyle) null);
 360     }
 361 
 362     //-----------------------------------------------------------------------
 363     @Test(groups={"tck"})
 364     public void test_appendTextMap() throws Exception {
 365         Map<Long, String> map = new HashMap<Long, String>();
 366         map.put(1L, "JNY");
 367         map.put(2L, "FBY");
 368         map.put(3L, "MCH");
 369         map.put(4L, "APL");
 370         map.put(5L, "MAY");
 371         map.put(6L, "JUN");
 372         map.put(7L, "JLY");
 373         map.put(8L, "AGT");
 374         map.put(9L, "SPT");
 375         map.put(10L, "OBR");
 376         map.put(11L, "NVR");
 377         map.put(12L, "DBR");
 378         builder.appendText(MONTH_OF_YEAR, map);
 379         DateTimeFormatter f = builder.toFormatter();
 380         assertEquals(f.toString(), "Text(MonthOfYear)");  // TODO: toString should be different?
 381     }
 382 
 383     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 384     public void test_appendTextMap_nullRule() throws Exception {
 385         builder.appendText(null, new HashMap<Long, String>());
 386     }
 387 
 388     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 389     public void test_appendTextMap_nullStyle() throws Exception {
 390         builder.appendText(MONTH_OF_YEAR, (Map<Long, String>) null);
 391     }
 392 
 393     //-----------------------------------------------------------------------
 394     //-----------------------------------------------------------------------
 395     //-----------------------------------------------------------------------
 396     @Test(groups={"tck"})
 397     public void test_appendOffsetId() throws Exception {
 398         builder.appendOffsetId();
 399         DateTimeFormatter f = builder.toFormatter();
 400         assertEquals(f.toString(), "Offset('Z',+HH:MM:ss)");
 401     }
 402 
 403     @DataProvider(name="offsetPatterns")
 404     Object[][] data_offsetPatterns() {
 405         return new Object[][] {
 406                 {"+HH", 2, 0, 0, "+02"},
 407                 {"+HH", -2, 0, 0, "-02"},
 408                 {"+HH", 2, 30, 0, "+02"},
 409                 {"+HH", 2, 0, 45, "+02"},
 410                 {"+HH", 2, 30, 45, "+02"},
 411 
 412                 {"+HHMM", 2, 0, 0, "+0200"},
 413                 {"+HHMM", -2, 0, 0, "-0200"},
 414                 {"+HHMM", 2, 30, 0, "+0230"},
 415                 {"+HHMM", 2, 0, 45, "+0200"},
 416                 {"+HHMM", 2, 30, 45, "+0230"},
 417 
 418                 {"+HH:MM", 2, 0, 0, "+02:00"},
 419                 {"+HH:MM", -2, 0, 0, "-02:00"},
 420                 {"+HH:MM", 2, 30, 0, "+02:30"},
 421                 {"+HH:MM", 2, 0, 45, "+02:00"},
 422                 {"+HH:MM", 2, 30, 45, "+02:30"},
 423 
 424                 {"+HHMMss", 2, 0, 0, "+0200"},
 425                 {"+HHMMss", -2, 0, 0, "-0200"},
 426                 {"+HHMMss", 2, 30, 0, "+0230"},
 427                 {"+HHMMss", 2, 0, 45, "+020045"},
 428                 {"+HHMMss", 2, 30, 45, "+023045"},
 429 
 430                 {"+HH:MM:ss", 2, 0, 0, "+02:00"},
 431                 {"+HH:MM:ss", -2, 0, 0, "-02:00"},
 432                 {"+HH:MM:ss", 2, 30, 0, "+02:30"},
 433                 {"+HH:MM:ss", 2, 0, 45, "+02:00:45"},
 434                 {"+HH:MM:ss", 2, 30, 45, "+02:30:45"},
 435 
 436                 {"+HHMMSS", 2, 0, 0, "+020000"},
 437                 {"+HHMMSS", -2, 0, 0, "-020000"},
 438                 {"+HHMMSS", 2, 30, 0, "+023000"},
 439                 {"+HHMMSS", 2, 0, 45, "+020045"},
 440                 {"+HHMMSS", 2, 30, 45, "+023045"},
 441 
 442                 {"+HH:MM:SS", 2, 0, 0, "+02:00:00"},
 443                 {"+HH:MM:SS", -2, 0, 0, "-02:00:00"},
 444                 {"+HH:MM:SS", 2, 30, 0, "+02:30:00"},
 445                 {"+HH:MM:SS", 2, 0, 45, "+02:00:45"},
 446                 {"+HH:MM:SS", 2, 30, 45, "+02:30:45"},
 447         };
 448     }
 449 
 450     @Test(dataProvider="offsetPatterns", groups={"tck"})
 451     public void test_appendOffset_print(String pattern, int h, int m, int s, String expected) throws Exception {
 452         builder.appendOffset(pattern, "Z");
 453         DateTimeFormatter f = builder.toFormatter();
 454         ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(h, m, s);
 455         assertEquals(f.print(offset), expected);
 456     }
 457 
 458     @Test(dataProvider="offsetPatterns", groups={"tck"})
 459     public void test_appendOffset_parse(String pattern, int h, int m, int s, String expected) throws Exception {
 460         builder.appendOffset(pattern, "Z");
 461         DateTimeFormatter f = builder.toFormatter();
 462         ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(h, m, s);
 463         ZoneOffset parsed = f.parse(expected, ZoneOffset::from);
 464         assertEquals(f.print(parsed), expected);
 465     }
 466 
 467     @DataProvider(name="badOffsetPatterns")
 468     Object[][] data_badOffsetPatterns() {
 469         return new Object[][] {
 470             {"HH"},
 471             {"HHMM"},
 472             {"HH:MM"},
 473             {"HHMMss"},
 474             {"HH:MM:ss"},
 475             {"HHMMSS"},
 476             {"HH:MM:SS"},
 477             {"+H"},
 478             {"+HMM"},
 479             {"+HHM"},
 480             {"+A"},
 481         };
 482     }
 483 
 484     @Test(dataProvider="badOffsetPatterns", expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 485     public void test_appendOffset_badPattern(String pattern) throws Exception {
 486         builder.appendOffset(pattern, "Z");
 487     }
 488 
 489     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 490     public void test_appendOffset_3arg_nullText() throws Exception {
 491         builder.appendOffset("+HH:MM", null);
 492     }
 493 
 494     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 495     public void test_appendOffset_3arg_nullPattern() throws Exception {
 496         builder.appendOffset(null, "Z");
 497     }
 498 
 499     //-----------------------------------------------------------------------
 500     //-----------------------------------------------------------------------
 501     //-----------------------------------------------------------------------
 502     @Test(groups={"tck"})
 503     public void test_appendZoneId() throws Exception {
 504         builder.appendZoneId();
 505         DateTimeFormatter f = builder.toFormatter();
 506         assertEquals(f.toString(), "ZoneId()");
 507     }
 508 
 509     @Test(groups={"tck"})
 510     public void test_appendZoneText_1arg() throws Exception {
 511         builder.appendZoneText(TextStyle.FULL);
 512         DateTimeFormatter f = builder.toFormatter();
 513         assertEquals(f.toString(), "ZoneText(FULL)");
 514     }
 515 
 516     @Test(expectedExceptions=NullPointerException.class, groups={"tck"})
 517     public void test_appendZoneText_1arg_nullText() throws Exception {
 518         builder.appendZoneText(null);
 519     }
 520 
 521     //-----------------------------------------------------------------------
 522     //-----------------------------------------------------------------------
 523     //-----------------------------------------------------------------------
 524     @Test(groups={"tck"})
 525     public void test_padNext_1arg() throws Exception {
 526         builder.appendValue(MONTH_OF_YEAR).padNext(2).appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK);
 527         DateTimeFormatter f = builder.toFormatter();
 528         assertEquals(f.toString(), "Value(MonthOfYear)Pad(Value(DayOfMonth),2)Value(DayOfWeek)");
 529     }
 530 
 531     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 532     public void test_padNext_1arg_invalidWidth() throws Exception {
 533         builder.padNext(0);
 534     }
 535 
 536     //-----------------------------------------------------------------------
 537     @Test(groups={"tck"})
 538     public void test_padNext_2arg_dash() throws Exception {
 539         builder.appendValue(MONTH_OF_YEAR).padNext(2, '-').appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK);
 540         DateTimeFormatter f = builder.toFormatter();
 541         assertEquals(f.toString(), "Value(MonthOfYear)Pad(Value(DayOfMonth),2,'-')Value(DayOfWeek)");
 542     }
 543 
 544     @Test(expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 545     public void test_padNext_2arg_invalidWidth() throws Exception {
 546         builder.padNext(0, '-');
 547     }
 548 
 549     //-----------------------------------------------------------------------
 550     @Test(groups={"tck"})
 551     public void test_padOptional() throws Exception {
 552         builder.appendValue(MONTH_OF_YEAR).padNext(5).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().appendValue(DAY_OF_WEEK);
 553         DateTimeFormatter f = builder.toFormatter();
 554         assertEquals(f.toString(), "Value(MonthOfYear)Pad([Value(DayOfMonth)],5)Value(DayOfWeek)");
 555     }
 556 
 557     //-----------------------------------------------------------------------
 558     //-----------------------------------------------------------------------
 559     //-----------------------------------------------------------------------
 560     @Test(groups={"tck"})
 561     public void test_optionalStart_noEnd() throws Exception {
 562         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK);
 563         DateTimeFormatter f = builder.toFormatter();
 564         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)Value(DayOfWeek)]");
 565     }
 566 
 567     @Test(groups={"tck"})
 568     public void test_optionalStart2_noEnd() throws Exception {
 569         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalStart().appendValue(DAY_OF_WEEK);
 570         DateTimeFormatter f = builder.toFormatter();
 571         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]]");
 572     }
 573 
 574     @Test(groups={"tck"})
 575     public void test_optionalStart_doubleStart() throws Exception {
 576         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH);
 577         DateTimeFormatter f = builder.toFormatter();
 578         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
 579     }
 580 
 581     //-----------------------------------------------------------------------
 582     @Test(groups={"tck"})
 583     public void test_optionalEnd() throws Exception {
 584         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().appendValue(DAY_OF_WEEK);
 585         DateTimeFormatter f = builder.toFormatter();
 586         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)]Value(DayOfWeek)");
 587     }
 588 
 589     @Test(groups={"tck"})
 590     public void test_optionalEnd2() throws Exception {
 591         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH)
 592             .optionalStart().appendValue(DAY_OF_WEEK).optionalEnd().appendValue(DAY_OF_MONTH).optionalEnd();
 593         DateTimeFormatter f = builder.toFormatter();
 594         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]Value(DayOfMonth)]");
 595     }
 596 
 597     @Test(groups={"tck"})
 598     public void test_optionalEnd_doubleStartSingleEnd() throws Exception {
 599         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd();
 600         DateTimeFormatter f = builder.toFormatter();
 601         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
 602     }
 603 
 604     @Test(groups={"tck"})
 605     public void test_optionalEnd_doubleStartDoubleEnd() throws Exception {
 606         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().optionalEnd();
 607         DateTimeFormatter f = builder.toFormatter();
 608         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
 609     }
 610 
 611     @Test(groups={"tck"})
 612     public void test_optionalStartEnd_immediateStartEnd() throws Exception {
 613         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalEnd().appendValue(DAY_OF_MONTH);
 614         DateTimeFormatter f = builder.toFormatter();
 615         assertEquals(f.toString(), "Value(MonthOfYear)Value(DayOfMonth)");
 616     }
 617 
 618     @Test(expectedExceptions=IllegalStateException.class, groups={"tck"})
 619     public void test_optionalEnd_noStart() throws Exception {
 620         builder.optionalEnd();
 621     }
 622 
 623     //-----------------------------------------------------------------------
 624     //-----------------------------------------------------------------------
 625     //-----------------------------------------------------------------------
 626     @DataProvider(name="validPatterns")
 627     Object[][] dataValid() {
 628         return new Object[][] {
 629             {"'a'", "'a'"},
 630             {"''", "''"},
 631             {"'!'", "'!'"},
 632             {"!", "'!'"},
 633 
 634             {"'hello_people,][)('", "'hello_people,][)('"},
 635             {"'hi'", "'hi'"},
 636             {"'yyyy'", "'yyyy'"},
 637             {"''''", "''"},
 638             {"'o''clock'", "'o''clock'"},
 639 
 640             {"G", "Value(Era)"},
 641             {"GG", "Value(Era,2)"},
 642             {"GGG", "Text(Era,SHORT)"},
 643             {"GGGG", "Text(Era)"},
 644             {"GGGGG", "Text(Era,NARROW)"},
 645 
 646             {"y", "Value(Year)"},
 647             {"yy", "ReducedValue(Year,2,2000)"},
 648             {"yyy", "Value(Year,3,19,NORMAL)"},
 649             {"yyyy", "Value(Year,4,19,EXCEEDS_PAD)"},
 650             {"yyyyy", "Value(Year,5,19,EXCEEDS_PAD)"},
 651 
 652 //            {"Y", "Value(WeekBasedYear)"},
 653 //            {"YY", "ReducedValue(WeekBasedYear,2,2000)"},
 654 //            {"YYY", "Value(WeekBasedYear,3,19,NORMAL)"},
 655 //            {"YYYY", "Value(WeekBasedYear,4,19,EXCEEDS_PAD)"},
 656 //            {"YYYYY", "Value(WeekBasedYear,5,19,EXCEEDS_PAD)"},
 657 
 658             {"M", "Value(MonthOfYear)"},
 659             {"MM", "Value(MonthOfYear,2)"},
 660             {"MMM", "Text(MonthOfYear,SHORT)"},
 661             {"MMMM", "Text(MonthOfYear)"},
 662             {"MMMMM", "Text(MonthOfYear,NARROW)"},
 663 
 664             {"D", "Value(DayOfYear)"},
 665             {"DD", "Value(DayOfYear,2)"},
 666             {"DDD", "Value(DayOfYear,3)"},
 667 
 668             {"d", "Value(DayOfMonth)"},
 669             {"dd", "Value(DayOfMonth,2)"},
 670             {"ddd", "Value(DayOfMonth,3)"},
 671 
 672             {"F", "Value(AlignedWeekOfMonth)"},
 673             {"FF", "Value(AlignedWeekOfMonth,2)"},
 674             {"FFF", "Value(AlignedWeekOfMonth,3)"},
 675 
 676             {"Q", "Value(QuarterOfYear)"},
 677             {"QQ", "Value(QuarterOfYear,2)"},
 678             {"QQQ", "Text(QuarterOfYear,SHORT)"},
 679             {"QQQQ", "Text(QuarterOfYear)"},
 680             {"QQQQQ", "Text(QuarterOfYear,NARROW)"},
 681 
 682             {"E", "Value(DayOfWeek)"},
 683             {"EE", "Value(DayOfWeek,2)"},
 684             {"EEE", "Text(DayOfWeek,SHORT)"},
 685             {"EEEE", "Text(DayOfWeek)"},
 686             {"EEEEE", "Text(DayOfWeek,NARROW)"},
 687 
 688             {"a", "Text(AmPmOfDay,SHORT)"},
 689             {"aa", "Text(AmPmOfDay,SHORT)"},
 690             {"aaa", "Text(AmPmOfDay,SHORT)"},
 691             {"aaaa", "Text(AmPmOfDay)"},
 692             {"aaaaa", "Text(AmPmOfDay,NARROW)"},
 693 
 694             {"H", "Value(HourOfDay)"},
 695             {"HH", "Value(HourOfDay,2)"},
 696             {"HHH", "Value(HourOfDay,3)"},
 697 
 698             {"K", "Value(HourOfAmPm)"},
 699             {"KK", "Value(HourOfAmPm,2)"},
 700             {"KKK", "Value(HourOfAmPm,3)"},
 701 
 702             {"k", "Value(ClockHourOfDay)"},
 703             {"kk", "Value(ClockHourOfDay,2)"},
 704             {"kkk", "Value(ClockHourOfDay,3)"},
 705 
 706             {"h", "Value(ClockHourOfAmPm)"},
 707             {"hh", "Value(ClockHourOfAmPm,2)"},
 708             {"hhh", "Value(ClockHourOfAmPm,3)"},
 709 
 710             {"m", "Value(MinuteOfHour)"},
 711             {"mm", "Value(MinuteOfHour,2)"},
 712             {"mmm", "Value(MinuteOfHour,3)"},
 713 
 714             {"s", "Value(SecondOfMinute)"},
 715             {"ss", "Value(SecondOfMinute,2)"},
 716             {"sss", "Value(SecondOfMinute,3)"},
 717 
 718             {"S", "Fraction(NanoOfSecond,1,1)"},
 719             {"SS", "Fraction(NanoOfSecond,2,2)"},
 720             {"SSS", "Fraction(NanoOfSecond,3,3)"},
 721             {"SSSSSSSSS", "Fraction(NanoOfSecond,9,9)"},
 722 
 723             {"A", "Value(MilliOfDay)"},
 724             {"AA", "Value(MilliOfDay,2)"},
 725             {"AAA", "Value(MilliOfDay,3)"},
 726 
 727             {"n", "Value(NanoOfSecond)"},
 728             {"nn", "Value(NanoOfSecond,2)"},
 729             {"nnn", "Value(NanoOfSecond,3)"},
 730 
 731             {"N", "Value(NanoOfDay)"},
 732             {"NN", "Value(NanoOfDay,2)"},
 733             {"NNN", "Value(NanoOfDay,3)"},
 734 
 735             {"z", "ZoneText(SHORT)"},
 736             {"zz", "ZoneText(SHORT)"},
 737             {"zzz", "ZoneText(SHORT)"},
 738             {"zzzz", "ZoneText(FULL)"},
 739             {"zzzzz", "ZoneText(FULL)"},
 740 
 741             {"I", "ZoneId()"},
 742             {"II", "ZoneId()"},
 743             {"III", "ZoneId()"},
 744             {"IIII", "ZoneId()"},
 745             {"IIIII", "ZoneId()"},
 746 
 747             {"Z", "Offset('+0000',+HHMM)"},  // SimpleDateFormat compatible
 748             {"ZZ", "Offset('+0000',+HHMM)"},
 749             {"ZZZ", "Offset('+00:00',+HH:MM)"},
 750 
 751             {"X", "Offset('Z',+HH)"},
 752             {"XX", "Offset('Z',+HHMM)"},
 753             {"XXX", "Offset('Z',+HH:MM)"},
 754             {"XXXX", "Offset('Z',+HHMMss)"},
 755             {"XXXXX", "Offset('Z',+HH:MM:ss)"},
 756 
 757             {"ppH", "Pad(Value(HourOfDay),2)"},
 758             {"pppDD", "Pad(Value(DayOfYear,2),3)"},
 759 
 760             {"yyyy[-MM[-dd", "Value(Year,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"},
 761             {"yyyy[-MM[-dd]]", "Value(Year,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"},
 762             {"yyyy[-MM[]-dd]", "Value(Year,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)]"},
 763 
 764             {"yyyy-MM-dd'T'HH:mm:ss.SSS", "Value(Year,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)" +
 765                 "'T'Value(HourOfDay,2)':'Value(MinuteOfHour,2)':'Value(SecondOfMinute,2)'.'Fraction(NanoOfSecond,3,3)"},
 766 
 767             {"e", "WeekBased(e1)"},
 768             {"w", "WeekBased(w1)"},
 769             {"W", "WeekBased(W1)"},
 770             {"WW", "WeekBased(W2)"},
 771 
 772         };
 773     }
 774 
 775     @Test(dataProvider="validPatterns", groups={"implementation"})
 776     public void test_appendPattern_valid(String input, String expected) throws Exception {
 777         builder.appendPattern(input);
 778         DateTimeFormatter f = builder.toFormatter();
 779         assertEquals(f.toString(), expected);
 780     }
 781 
 782     //-----------------------------------------------------------------------
 783     @DataProvider(name="invalidPatterns")
 784     Object[][] dataInvalid() {
 785         return new Object[][] {
 786             {"'"},
 787             {"'hello"},
 788             {"'hel''lo"},
 789             {"'hello''"},
 790             {"{"},
 791             {"}"},
 792             {"{}"},
 793             {"]"},
 794             {"yyyy]"},
 795             {"yyyy]MM"},
 796             {"yyyy[MM]]"},
 797 
 798             {"MMMMMM"},
 799             {"QQQQQQ"},
 800             {"EEEEEE"},
 801             {"aaaaaa"},
 802             {"ZZZZ"},
 803             {"XXXXXX"},
 804 
 805             {"RO"},
 806 
 807             {"p"},
 808             {"pp"},
 809             {"p:"},
 810 
 811             {"f"},
 812             {"ff"},
 813             {"f:"},
 814             {"fy"},
 815             {"fa"},
 816             {"fM"},
 817 
 818             {"ww"},
 819             {"ee"},
 820             {"WWW"},
 821         };
 822     }
 823 
 824     @Test(dataProvider="invalidPatterns", expectedExceptions=IllegalArgumentException.class, groups={"tck"})
 825     public void test_appendPattern_invalid(String input) throws Exception {
 826         try {
 827             builder.appendPattern(input);
 828         } catch (IllegalArgumentException ex) {
 829             throw ex;
 830         }
 831     }
 832 
 833     //-----------------------------------------------------------------------
 834     @DataProvider(name="patternPrint")
 835     Object[][] data_patternPrint() {
 836         return new Object[][] {
 837             {"Q", date(2012, 2, 10), "1"},
 838             {"QQ", date(2012, 2, 10), "01"},
 839 //            {"QQQ", date(2012, 2, 10), "Q1"},  // TODO: data for quarters?
 840 //            {"QQQQ", date(2012, 2, 10), "Q1"},
 841 //            {"QQQQQ", date(2012, 2, 10), "Q1"},
 842         };
 843     }
 844 
 845     @Test(dataProvider="patternPrint", groups={"tck"})
 846     public void test_appendPattern_patternPrint(String input, Temporal temporal, String expected) throws Exception {
 847         DateTimeFormatter f = builder.appendPattern(input).toFormatter(Locale.UK);
 848         String test = f.print(temporal);
 849         assertEquals(test, expected);
 850     }
 851 
 852     private static Temporal date(int y, int m, int d) {
 853         return LocalDate.of(y, m, d);
 854     }
 855 
 856 }