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