1 /*
2  * Copyright (c) 2012, 2020, 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.format.DateTimeFormatter.ISO_LOCAL_TIME;
63 import static java.time.temporal.ChronoField.DAY_OF_MONTH;
64 import static java.time.temporal.ChronoField.DAY_OF_WEEK;
65 import static java.time.temporal.ChronoField.HOUR_OF_DAY;
66 import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
67 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
68 import static java.time.temporal.ChronoField.YEAR;
69 import static org.testng.Assert.assertEquals;
70 import static org.testng.Assert.assertNotNull;
71 import static org.testng.Assert.fail;
72 
73 import android.icu.util.VersionInfo;
74 import java.text.ParsePosition;
75 import java.time.LocalDate;
76 import java.time.LocalTime;
77 import java.time.Period;
78 import java.time.YearMonth;
79 import java.time.ZoneOffset;
80 import java.time.chrono.Chronology;
81 import java.time.chrono.IsoChronology;
82 import java.time.format.DateTimeFormatter;
83 import java.time.format.DateTimeFormatterBuilder;
84 import java.time.format.DateTimeParseException;
85 import java.time.format.FormatStyle;
86 import java.time.format.ResolverStyle;
87 import java.time.format.SignStyle;
88 import java.time.format.TextStyle;
89 import java.time.temporal.ChronoField;
90 import java.time.temporal.Temporal;
91 import java.time.temporal.TemporalAccessor;
92 import java.time.temporal.TemporalField;
93 import java.time.temporal.TemporalUnit;
94 import java.time.temporal.ValueRange;
95 import java.util.HashMap;
96 import java.util.Locale;
97 import java.util.Map;
98 
99 import org.testng.annotations.BeforeMethod;
100 import org.testng.annotations.DataProvider;
101 import org.testng.annotations.Test;
102 
103 /**
104  * Test DateTimeFormatterBuilder.
105  */
106 @Test
107 public class TestDateTimeFormatterBuilder {
108 
109     private DateTimeFormatterBuilder builder;
110 
111     @BeforeMethod
setUp()112     public void setUp() {
113         builder = new DateTimeFormatterBuilder();
114     }
115 
116     //-----------------------------------------------------------------------
117     @Test
test_toFormatter_empty()118     public void test_toFormatter_empty() throws Exception {
119         DateTimeFormatter f = builder.toFormatter();
120         assertEquals(f.toString(), "");
121     }
122 
123     //-----------------------------------------------------------------------
124     @Test
test_parseCaseSensitive()125     public void test_parseCaseSensitive() throws Exception {
126         builder.parseCaseSensitive();
127         DateTimeFormatter f = builder.toFormatter();
128         assertEquals(f.toString(), "ParseCaseSensitive(true)");
129     }
130 
131     @Test
test_parseCaseInsensitive()132     public void test_parseCaseInsensitive() throws Exception {
133         builder.parseCaseInsensitive();
134         DateTimeFormatter f = builder.toFormatter();
135         assertEquals(f.toString(), "ParseCaseSensitive(false)");
136     }
137 
138     //-----------------------------------------------------------------------
139     @Test
test_parseStrict()140     public void test_parseStrict() throws Exception {
141         builder.parseStrict();
142         DateTimeFormatter f = builder.toFormatter();
143         assertEquals(f.toString(), "ParseStrict(true)");
144     }
145 
146     @Test
test_parseLenient()147     public void test_parseLenient() throws Exception {
148         builder.parseLenient();
149         DateTimeFormatter f = builder.toFormatter();
150         assertEquals(f.toString(), "ParseStrict(false)");
151     }
152 
153     //-----------------------------------------------------------------------
154     @Test
test_appendValue_1arg()155     public void test_appendValue_1arg() throws Exception {
156         builder.appendValue(DAY_OF_MONTH);
157         DateTimeFormatter f = builder.toFormatter();
158         assertEquals(f.toString(), "Value(DayOfMonth)");
159     }
160 
161     @Test(expectedExceptions=NullPointerException.class)
test_appendValue_1arg_null()162     public void test_appendValue_1arg_null() throws Exception {
163         builder.appendValue(null);
164     }
165 
166     //-----------------------------------------------------------------------
167     @Test
test_appendValue_2arg()168     public void test_appendValue_2arg() throws Exception {
169         builder.appendValue(DAY_OF_MONTH, 3);
170         DateTimeFormatter f = builder.toFormatter();
171         assertEquals(f.toString(), "Value(DayOfMonth,3)");
172     }
173 
174     @Test(expectedExceptions=NullPointerException.class)
test_appendValue_2arg_null()175     public void test_appendValue_2arg_null() throws Exception {
176         builder.appendValue(null, 3);
177     }
178 
179     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendValue_2arg_widthTooSmall()180     public void test_appendValue_2arg_widthTooSmall() throws Exception {
181         builder.appendValue(DAY_OF_MONTH, 0);
182     }
183 
184     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendValue_2arg_widthTooBig()185     public void test_appendValue_2arg_widthTooBig() throws Exception {
186         builder.appendValue(DAY_OF_MONTH, 20);
187     }
188 
189     //-----------------------------------------------------------------------
190     @Test
test_appendValue_3arg()191     public void test_appendValue_3arg() throws Exception {
192         builder.appendValue(DAY_OF_MONTH, 2, 3, SignStyle.NORMAL);
193         DateTimeFormatter f = builder.toFormatter();
194         assertEquals(f.toString(), "Value(DayOfMonth,2,3,NORMAL)");
195     }
196 
197     @Test(expectedExceptions=NullPointerException.class)
test_appendValue_3arg_nullField()198     public void test_appendValue_3arg_nullField() throws Exception {
199         builder.appendValue(null, 2, 3, SignStyle.NORMAL);
200     }
201 
202     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendValue_3arg_minWidthTooSmall()203     public void test_appendValue_3arg_minWidthTooSmall() throws Exception {
204         builder.appendValue(DAY_OF_MONTH, 0, 2, SignStyle.NORMAL);
205     }
206 
207     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendValue_3arg_minWidthTooBig()208     public void test_appendValue_3arg_minWidthTooBig() throws Exception {
209         builder.appendValue(DAY_OF_MONTH, 20, 2, SignStyle.NORMAL);
210     }
211 
212     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendValue_3arg_maxWidthTooSmall()213     public void test_appendValue_3arg_maxWidthTooSmall() throws Exception {
214         builder.appendValue(DAY_OF_MONTH, 2, 0, SignStyle.NORMAL);
215     }
216 
217     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendValue_3arg_maxWidthTooBig()218     public void test_appendValue_3arg_maxWidthTooBig() throws Exception {
219         builder.appendValue(DAY_OF_MONTH, 2, 20, SignStyle.NORMAL);
220     }
221 
222     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendValue_3arg_maxWidthMinWidth()223     public void test_appendValue_3arg_maxWidthMinWidth() throws Exception {
224         builder.appendValue(DAY_OF_MONTH, 4, 2, SignStyle.NORMAL);
225     }
226 
227     @Test(expectedExceptions=NullPointerException.class)
test_appendValue_3arg_nullSignStyle()228     public void test_appendValue_3arg_nullSignStyle() throws Exception {
229         builder.appendValue(DAY_OF_MONTH, 2, 3, null);
230     }
231 
232     //-----------------------------------------------------------------------
233     @Test
test_appendValue_subsequent2_parse3()234     public void test_appendValue_subsequent2_parse3() throws Exception {
235         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2);
236         DateTimeFormatter f = builder.toFormatter();
237         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)");
238         TemporalAccessor parsed = f.parseUnresolved("123", new ParsePosition(0));
239         assertEquals(parsed.getLong(MONTH_OF_YEAR), 1L);
240         assertEquals(parsed.getLong(DAY_OF_MONTH), 23L);
241     }
242 
243     @Test
test_appendValue_subsequent2_parse4()244     public void test_appendValue_subsequent2_parse4() throws Exception {
245         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2);
246         DateTimeFormatter f = builder.toFormatter();
247         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)");
248         TemporalAccessor parsed = f.parseUnresolved("0123", new ParsePosition(0));
249         assertEquals(parsed.getLong(MONTH_OF_YEAR), 1L);
250         assertEquals(parsed.getLong(DAY_OF_MONTH), 23L);
251     }
252 
253     @Test
test_appendValue_subsequent2_parse5()254     public void test_appendValue_subsequent2_parse5() throws Exception {
255         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValue(DAY_OF_MONTH, 2).appendLiteral('4');
256         DateTimeFormatter f = builder.toFormatter();
257         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)Value(DayOfMonth,2)'4'");
258         TemporalAccessor parsed = f.parseUnresolved("01234", new ParsePosition(0));
259         assertEquals(parsed.getLong(MONTH_OF_YEAR), 1L);
260         assertEquals(parsed.getLong(DAY_OF_MONTH), 23L);
261     }
262 
263     @Test
test_appendValue_subsequent3_parse6()264     public void test_appendValue_subsequent3_parse6() throws Exception {
265         builder
266             .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
267             .appendValue(MONTH_OF_YEAR, 2)
268             .appendValue(DAY_OF_MONTH, 2);
269         DateTimeFormatter f = builder.toFormatter();
270         assertEquals(f.toString(), "Value(Year,4,10,EXCEEDS_PAD)Value(MonthOfYear,2)Value(DayOfMonth,2)");
271         TemporalAccessor parsed = f.parseUnresolved("20090630", new ParsePosition(0));
272         assertEquals(parsed.getLong(YEAR), 2009L);
273         assertEquals(parsed.getLong(MONTH_OF_YEAR), 6L);
274         assertEquals(parsed.getLong(DAY_OF_MONTH), 30L);
275     }
276 
277     //-----------------------------------------------------------------------
278     @Test(expectedExceptions=NullPointerException.class)
test_appendValueReduced_null()279     public void test_appendValueReduced_null() throws Exception {
280         builder.appendValueReduced(null, 2, 2, 2000);
281     }
282 
283     @Test
test_appendValueReduced()284     public void test_appendValueReduced() throws Exception {
285         builder.appendValueReduced(YEAR, 2, 2, 2000);
286         DateTimeFormatter f = builder.toFormatter();
287         assertEquals(f.toString(), "ReducedValue(Year,2,2,2000)");
288         TemporalAccessor parsed = f.parseUnresolved("12", new ParsePosition(0));
289         assertEquals(parsed.getLong(YEAR), 2012L);
290     }
291 
292     @Test
test_appendValueReduced_subsequent_parse()293     public void test_appendValueReduced_subsequent_parse() throws Exception {
294         builder.appendValue(MONTH_OF_YEAR, 1, 2, SignStyle.NORMAL).appendValueReduced(YEAR, 2, 2, 2000);
295         DateTimeFormatter f = builder.toFormatter();
296         assertEquals(f.toString(), "Value(MonthOfYear,1,2,NORMAL)ReducedValue(Year,2,2,2000)");
297         ParsePosition ppos = new ParsePosition(0);
298         TemporalAccessor parsed = f.parseUnresolved("123", ppos);
299         assertNotNull(parsed, "Parse failed: " + ppos.toString());
300         assertEquals(parsed.getLong(MONTH_OF_YEAR), 1L);
301         assertEquals(parsed.getLong(YEAR), 2023L);
302     }
303 
304     //-----------------------------------------------------------------------
305     //-----------------------------------------------------------------------
306     //-----------------------------------------------------------------------
307     @Test
test_appendFraction_4arg()308     public void test_appendFraction_4arg() throws Exception {
309         builder.appendFraction(MINUTE_OF_HOUR, 1, 9, false);
310         DateTimeFormatter f = builder.toFormatter();
311         assertEquals(f.toString(), "Fraction(MinuteOfHour,1,9)");
312     }
313 
314     @Test(expectedExceptions=NullPointerException.class)
test_appendFraction_4arg_nullRule()315     public void test_appendFraction_4arg_nullRule() throws Exception {
316         builder.appendFraction(null, 1, 9, false);
317     }
318 
319     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendFraction_4arg_invalidRuleNotFixedSet()320     public void test_appendFraction_4arg_invalidRuleNotFixedSet() throws Exception {
321         builder.appendFraction(DAY_OF_MONTH, 1, 9, false);
322     }
323 
324     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendFraction_4arg_minTooSmall()325     public void test_appendFraction_4arg_minTooSmall() throws Exception {
326         builder.appendFraction(MINUTE_OF_HOUR, -1, 9, false);
327     }
328 
329     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendFraction_4arg_minTooBig()330     public void test_appendFraction_4arg_minTooBig() throws Exception {
331         builder.appendFraction(MINUTE_OF_HOUR, 10, 9, false);
332     }
333 
334     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendFraction_4arg_maxTooSmall()335     public void test_appendFraction_4arg_maxTooSmall() throws Exception {
336         builder.appendFraction(MINUTE_OF_HOUR, 0, -1, false);
337     }
338 
339     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendFraction_4arg_maxTooBig()340     public void test_appendFraction_4arg_maxTooBig() throws Exception {
341         builder.appendFraction(MINUTE_OF_HOUR, 1, 10, false);
342     }
343 
344     @Test(expectedExceptions=IllegalArgumentException.class)
test_appendFraction_4arg_maxWidthMinWidth()345     public void test_appendFraction_4arg_maxWidthMinWidth() throws Exception {
346         builder.appendFraction(MINUTE_OF_HOUR, 9, 3, false);
347     }
348 
349     //-----------------------------------------------------------------------
350     //-----------------------------------------------------------------------
351     //-----------------------------------------------------------------------
352     @Test
test_appendText_1arg()353     public void test_appendText_1arg() throws Exception {
354         builder.appendText(MONTH_OF_YEAR);
355         DateTimeFormatter f = builder.toFormatter();
356         assertEquals(f.toString(), "Text(MonthOfYear)");
357     }
358 
359     @Test(expectedExceptions=NullPointerException.class)
test_appendText_1arg_null()360     public void test_appendText_1arg_null() throws Exception {
361         builder.appendText(null);
362     }
363 
364     //-----------------------------------------------------------------------
365     @Test
test_appendText_2arg()366     public void test_appendText_2arg() throws Exception {
367         builder.appendText(MONTH_OF_YEAR, TextStyle.SHORT);
368         DateTimeFormatter f = builder.toFormatter();
369         assertEquals(f.toString(), "Text(MonthOfYear,SHORT)");
370     }
371 
372     @Test(expectedExceptions=NullPointerException.class)
test_appendText_2arg_nullRule()373     public void test_appendText_2arg_nullRule() throws Exception {
374         builder.appendText(null, TextStyle.SHORT);
375     }
376 
377     @Test(expectedExceptions=NullPointerException.class)
test_appendText_2arg_nullStyle()378     public void test_appendText_2arg_nullStyle() throws Exception {
379         builder.appendText(MONTH_OF_YEAR, (TextStyle) null);
380     }
381 
382     //-----------------------------------------------------------------------
383     @Test
test_appendTextMap()384     public void test_appendTextMap() throws Exception {
385         Map<Long, String> map = new HashMap<>();
386         map.put(1L, "JNY");
387         map.put(2L, "FBY");
388         map.put(3L, "MCH");
389         map.put(4L, "APL");
390         map.put(5L, "MAY");
391         map.put(6L, "JUN");
392         map.put(7L, "JLY");
393         map.put(8L, "AGT");
394         map.put(9L, "SPT");
395         map.put(10L, "OBR");
396         map.put(11L, "NVR");
397         map.put(12L, "DBR");
398         builder.appendText(MONTH_OF_YEAR, map);
399         DateTimeFormatter f = builder.toFormatter();
400         assertEquals(f.toString(), "Text(MonthOfYear)");  // TODO: toString should be different?
401     }
402 
403     @Test(expectedExceptions=NullPointerException.class)
test_appendTextMap_nullRule()404     public void test_appendTextMap_nullRule() throws Exception {
405         builder.appendText(null, new HashMap<Long, String>());
406     }
407 
408     @Test(expectedExceptions=NullPointerException.class)
test_appendTextMap_nullStyle()409     public void test_appendTextMap_nullStyle() throws Exception {
410         builder.appendText(MONTH_OF_YEAR, (Map<Long, String>) null);
411     }
412 
413     //-----------------------------------------------------------------------
414     //-----------------------------------------------------------------------
415     //-----------------------------------------------------------------------
416     @Test
test_appendOffsetId()417     public void test_appendOffsetId() throws Exception {
418         builder.appendOffsetId();
419         DateTimeFormatter f = builder.toFormatter();
420         assertEquals(f.toString(), "Offset(+HH:MM:ss,'Z')");
421     }
422 
423     @DataProvider(name="offsetPatterns")
data_offsetPatterns()424     Object[][] data_offsetPatterns() {
425         return new Object[][] {
426                 {"+HH", 2, 0, 0, "+02"},
427                 {"+HH", -2, 0, 0, "-02"},
428                 {"+HH", 2, 30, 0, "+02"},
429                 {"+HH", 2, 0, 45, "+02"},
430                 {"+HH", 2, 30, 45, "+02"},
431 
432                 {"+HHMM", 2, 0, 0, "+0200"},
433                 {"+HHMM", -2, 0, 0, "-0200"},
434                 {"+HHMM", 2, 30, 0, "+0230"},
435                 {"+HHMM", 2, 0, 45, "+0200"},
436                 {"+HHMM", 2, 30, 45, "+0230"},
437 
438                 {"+HH:MM", 2, 0, 0, "+02:00"},
439                 {"+HH:MM", -2, 0, 0, "-02:00"},
440                 {"+HH:MM", 2, 30, 0, "+02:30"},
441                 {"+HH:MM", 2, 0, 45, "+02:00"},
442                 {"+HH:MM", 2, 30, 45, "+02:30"},
443 
444                 {"+HHMMss", 2, 0, 0, "+0200"},
445                 {"+HHMMss", -2, 0, 0, "-0200"},
446                 {"+HHMMss", 2, 30, 0, "+0230"},
447                 {"+HHMMss", 2, 0, 45, "+020045"},
448                 {"+HHMMss", 2, 30, 45, "+023045"},
449 
450                 {"+HH:MM:ss", 2, 0, 0, "+02:00"},
451                 {"+HH:MM:ss", -2, 0, 0, "-02:00"},
452                 {"+HH:MM:ss", 2, 30, 0, "+02:30"},
453                 {"+HH:MM:ss", 2, 0, 45, "+02:00:45"},
454                 {"+HH:MM:ss", 2, 30, 45, "+02:30:45"},
455 
456                 {"+HHMMSS", 2, 0, 0, "+020000"},
457                 {"+HHMMSS", -2, 0, 0, "-020000"},
458                 {"+HHMMSS", 2, 30, 0, "+023000"},
459                 {"+HHMMSS", 2, 0, 45, "+020045"},
460                 {"+HHMMSS", 2, 30, 45, "+023045"},
461 
462                 {"+HH:MM:SS", 2, 0, 0, "+02:00:00"},
463                 {"+HH:MM:SS", -2, 0, 0, "-02:00:00"},
464                 {"+HH:MM:SS", 2, 30, 0, "+02:30:00"},
465                 {"+HH:MM:SS", 2, 0, 45, "+02:00:45"},
466                 {"+HH:MM:SS", 2, 30, 45, "+02:30:45"},
467         };
468     }
469 
470     @Test(dataProvider="offsetPatterns")
test_appendOffset_format(String pattern, int h, int m, int s, String expected)471     public void test_appendOffset_format(String pattern, int h, int m, int s, String expected) throws Exception {
472         builder.appendOffset(pattern, "Z");
473         DateTimeFormatter f = builder.toFormatter();
474         ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(h, m, s);
475         assertEquals(f.format(offset), expected);
476     }
477 
478     @Test(dataProvider="offsetPatterns")
test_appendOffset_parse(String pattern, int h, int m, int s, String expected)479     public void test_appendOffset_parse(String pattern, int h, int m, int s, String expected) throws Exception {
480         builder.appendOffset(pattern, "Z");
481         DateTimeFormatter f = builder.toFormatter();
482         ZoneOffset offset = ZoneOffset.ofHoursMinutesSeconds(h, m, s);
483         ZoneOffset parsed = f.parse(expected, ZoneOffset::from);
484         assertEquals(f.format(parsed), expected);
485     }
486 
487     @DataProvider(name="badOffsetPatterns")
data_badOffsetPatterns()488     Object[][] data_badOffsetPatterns() {
489         return new Object[][] {
490             {"HH"},
491             {"HHMM"},
492             {"HH:MM"},
493             {"HHMMss"},
494             {"HH:MM:ss"},
495             {"HHMMSS"},
496             {"HH:MM:SS"},
497             {"+HHM"},
498             {"+A"},
499         };
500     }
501 
502     @Test(dataProvider="badOffsetPatterns", expectedExceptions=IllegalArgumentException.class)
test_appendOffset_badPattern(String pattern)503     public void test_appendOffset_badPattern(String pattern) throws Exception {
504         builder.appendOffset(pattern, "Z");
505     }
506 
507     @Test(expectedExceptions=NullPointerException.class)
test_appendOffset_3arg_nullText()508     public void test_appendOffset_3arg_nullText() throws Exception {
509         builder.appendOffset("+HH:MM", null);
510     }
511 
512     @Test(expectedExceptions=NullPointerException.class)
test_appendOffset_3arg_nullPattern()513     public void test_appendOffset_3arg_nullPattern() throws Exception {
514         builder.appendOffset(null, "Z");
515     }
516 
517     //-----------------------------------------------------------------------
518     //-----------------------------------------------------------------------
519     //-----------------------------------------------------------------------
520     @Test
test_appendZoneId()521     public void test_appendZoneId() throws Exception {
522         builder.appendZoneId();
523         DateTimeFormatter f = builder.toFormatter();
524         assertEquals(f.toString(), "ZoneId()");
525     }
526 
527     @Test
test_appendZoneText_1arg()528     public void test_appendZoneText_1arg() throws Exception {
529         builder.appendZoneText(TextStyle.FULL);
530         DateTimeFormatter f = builder.toFormatter();
531         assertEquals(f.toString(), "ZoneText(FULL)");
532     }
533 
534     @Test(expectedExceptions=NullPointerException.class)
test_appendZoneText_1arg_nullText()535     public void test_appendZoneText_1arg_nullText() throws Exception {
536         builder.appendZoneText(null);
537     }
538 
539     //-----------------------------------------------------------------------
540     //-----------------------------------------------------------------------
541     //-----------------------------------------------------------------------
542     // BEGIN Android-removed: DateTimeFormatBuilder.appendDayPeriodText is temporarily unsupported.
543     /*
544     @Test
545     public void test_appendDayPeriodText_1arg() throws Exception {
546         builder.appendDayPeriodText(TextStyle.FULL);
547         DateTimeFormatter f = builder.toFormatter();
548         assertEquals(f.toString(), "DayPeriod(FULL)");
549     }
550 
551     @Test(expectedExceptions=NullPointerException.class)
552     public void test_appendDayPeriodText_1arg_nullText() throws Exception {
553         builder.appendDayPeriodText(null);
554     }
555 
556     @DataProvider(name="dayPeriodFormat")
557     Object[][] data_dayPeriodFormat() {
558         return new Object[][] {
559             {0, 0, TextStyle.FULL, Locale.US, "midnight"},
560             {0, 1, TextStyle.FULL, Locale.US, "at night"},
561             {6, 0, TextStyle.FULL, Locale.US, "in the morning"},
562             {12, 0, TextStyle.FULL, Locale.US, "noon"},
563             {12, 1, TextStyle.FULL, Locale.US, "in the afternoon"},
564             {18, 0, TextStyle.FULL, Locale.US, "in the evening"},
565             {22, 0, TextStyle.FULL, Locale.US, "at night"},
566 
567             {0, 0, TextStyle.FULL, Locale.JAPAN, "\u771f\u591c\u4e2d"},
568             {0, 1, TextStyle.FULL, Locale.JAPAN, "\u591c\u4e2d"},
569             {6, 0, TextStyle.FULL, Locale.JAPAN, "\u671d"},
570             {12, 0, TextStyle.FULL, Locale.JAPAN, "\u6b63\u5348"},
571             {12, 1, TextStyle.FULL, Locale.JAPAN, "\u663c"},
572             {18, 0, TextStyle.FULL, Locale.JAPAN, "\u5915\u65b9"},
573             {19, 0, TextStyle.FULL, Locale.JAPAN, "\u591c"},
574             {23, 0, TextStyle.FULL, Locale.JAPAN, "\u591c\u4e2d"},
575 
576             {0, 0, TextStyle.NARROW, Locale.US, "mi"},
577             {0, 1, TextStyle.NARROW, Locale.US, "at night"},
578             {6, 0, TextStyle.NARROW, Locale.US, "in the morning"},
579             {12, 0, TextStyle.NARROW, Locale.US, "n"},
580             {12, 1, TextStyle.NARROW, Locale.US, "in the afternoon"},
581             {18, 0, TextStyle.NARROW, Locale.US, "in the evening"},
582             {22, 0, TextStyle.NARROW, Locale.US, "at night"},
583 
584             {0, 0, TextStyle.NARROW, Locale.JAPAN, "\u771f\u591c\u4e2d"},
585             {0, 1, TextStyle.NARROW, Locale.JAPAN, "\u591c\u4e2d"},
586             {6, 0, TextStyle.NARROW, Locale.JAPAN, "\u671d"},
587             {12, 0, TextStyle.NARROW, Locale.JAPAN, "\u6b63\u5348"},
588             {12, 1, TextStyle.NARROW, Locale.JAPAN, "\u663c"},
589             {18, 0, TextStyle.NARROW, Locale.JAPAN, "\u5915\u65b9"},
590             {19, 0, TextStyle.NARROW, Locale.JAPAN, "\u591c"},
591             {23, 0, TextStyle.NARROW, Locale.JAPAN, "\u591c\u4e2d"},
592         };
593     }
594     @Test (dataProvider="dayPeriodFormat")
595     public void test_dayPeriodFormat(int hod, int moh, TextStyle ts, Locale l, String expected) throws Exception {
596         builder.appendDayPeriodText(ts);
597         LocalTime t = LocalTime.of(hod, moh);
598         DateTimeFormatter f = builder.toFormatter().withLocale(l);
599         assertEquals(f.format(t), expected);
600     }
601 
602     @DataProvider(name="dayPeriodParse")
603     Object[][] data_dayPeriodParse() {
604         return new Object[][] {
605                 {TextStyle.FULL, Locale.US, 0, 0, "midnight"},
606                 {TextStyle.FULL, Locale.US, 1, 30, "at night"},
607                 {TextStyle.FULL, Locale.US, 6, 0, "AM"},
608                 {TextStyle.FULL, Locale.US, 9, 0, "in the morning"},
609                 {TextStyle.FULL, Locale.US, 12, 0, "noon"},
610                 {TextStyle.FULL, Locale.US, 15, 0, "in the afternoon"},
611                 {TextStyle.FULL, Locale.US, 18, 0, "PM"},
612                 {TextStyle.FULL, Locale.US, 19, 30, "in the evening"},
613 
614                 {TextStyle.FULL, Locale.JAPAN, 0, 0, "\u771f\u591c\u4e2d"},
615                 {TextStyle.FULL, Locale.JAPAN, 1, 30, "\u591c\u4e2d"},
616                 {TextStyle.FULL, Locale.JAPAN, 6, 0, "\u5348\u524d"},
617                 {TextStyle.FULL, Locale.JAPAN, 8, 0, "\u671d"},
618                 {TextStyle.FULL, Locale.JAPAN, 12, 0, "\u6b63\u5348"},
619                 {TextStyle.FULL, Locale.JAPAN, 14, 0, "\u663c"},
620                 {TextStyle.FULL, Locale.JAPAN, 17, 30, "\u5915\u65b9"},
621                 {TextStyle.FULL, Locale.JAPAN, 18, 0, "\u5348\u5f8c"},
622                 {TextStyle.FULL, Locale.JAPAN, 21, 0, "\u591c"},
623 
624                 {TextStyle.NARROW, Locale.US, 0, 0, "mi"},
625                 {TextStyle.NARROW, Locale.US, 1, 30, "at night"},
626                 {TextStyle.NARROW, Locale.US, 6, 0, "a"},
627                 {TextStyle.NARROW, Locale.US, 9, 0, "in the morning"},
628                 {TextStyle.NARROW, Locale.US, 12, 0, "n"},
629                 {TextStyle.NARROW, Locale.US, 15, 0, "in the afternoon"},
630                 {TextStyle.NARROW, Locale.US, 18, 0, "p"},
631                 {TextStyle.NARROW, Locale.US, 19, 30, "in the evening"},
632 
633                 {TextStyle.NARROW, Locale.JAPAN, 0, 0, "\u771f\u591c\u4e2d"},
634                 {TextStyle.NARROW, Locale.JAPAN, 1, 30, "\u591c\u4e2d"},
635                 {TextStyle.NARROW, Locale.JAPAN, 6, 0, "\u5348\u524d"},
636                 {TextStyle.NARROW, Locale.JAPAN, 8, 0, "\u671d"},
637                 {TextStyle.NARROW, Locale.JAPAN, 12, 0, "\u6b63\u5348"},
638                 {TextStyle.NARROW, Locale.JAPAN, 14, 0, "\u663c"},
639                 {TextStyle.NARROW, Locale.JAPAN, 17, 30, "\u5915\u65b9"},
640                 {TextStyle.NARROW, Locale.JAPAN, 18, 0, "\u5348\u5f8c"},
641                 {TextStyle.NARROW, Locale.JAPAN, 21, 0, "\u591c"},
642         };
643     }
644     @Test (dataProvider="dayPeriodParse")
645     public void test_dayPeriodParse(TextStyle ts, Locale l, long hod, long moh, String dayPeriod) throws Exception {
646         builder.appendDayPeriodText(ts);
647         DateTimeFormatter f = builder.toFormatter().withLocale(l);
648         var p = f.parse(dayPeriod);
649         assertEquals(p.getLong(HOUR_OF_DAY), hod);
650         assertEquals(p.getLong(MINUTE_OF_HOUR), moh);
651     }
652 
653     @DataProvider(name="dayPeriodParsePattern")
654     Object[][] data_dayPeriodParsePattern() {
655         return new Object[][] {
656             {"H B", "23 at night", 23},
657             {"H B", "3 at night", 3},
658             {"K B", "11 at night", 23},
659             {"K B", "3 at night", 3},
660             {"K B", "11 in the morning", 11},
661             {"h B", "11 at night", 23},
662             {"h B", "3 at night", 3},
663             {"h B", "11 in the morning", 11},
664             {"a", "AM", 6},
665             {"a", "PM", 18},
666         };
667     }
668 
669     @Test (dataProvider="dayPeriodParsePattern")
670     public void test_dayPeriodParsePattern(String pattern, String hourDayPeriod, long expected) throws Exception {
671         builder.appendPattern(pattern);
672         DateTimeFormatter f = builder.toFormatter().withLocale(Locale.US);
673         var p = f.parse(hourDayPeriod);
674         assertEquals(p.getLong(HOUR_OF_DAY), expected);
675     }
676 
677     @DataProvider(name="dayPeriodParseMidnight")
678     Object[][] data_dayPeriodParseMidnight() {
679         return new Object[][] {
680             {"u-M-d H:m B", "2020-11-07 00:00 midnight", 7, 0},
681             {"u-M-d H:m B", "2020-11-07 24:00 midnight", 8, 0},
682         };
683     }
684 
685     @Test (dataProvider="dayPeriodParseMidnight")
686     public void test_dayPeriodParseMidnight(String pattern, String dateTime, long expectedDOM, long expectedHOD) throws Exception {
687         builder.appendPattern(pattern);
688         DateTimeFormatter f = builder.toFormatter().withLocale(Locale.US);
689         var p = f.parse(dateTime);
690         assertEquals(p.getLong(DAY_OF_MONTH), expectedDOM);
691         assertEquals(p.getLong(HOUR_OF_DAY), expectedHOD);
692     }
693 
694     @DataProvider(name="dayPeriodParseInvalid")
695     Object[][] data_dayPeriodParseInvalid() {
696         return new Object[][] {
697                 {TextStyle.FULL, ResolverStyle.SMART, Locale.US, "00:01 midnight", "00:00"},
698                 {TextStyle.FULL, ResolverStyle.SMART, Locale.US, "06:01 at night", "21:00-06:00"},
699                 {TextStyle.FULL, ResolverStyle.SMART, Locale.US, "05:59 in the morning", "06:00-12:00"},
700                 {TextStyle.FULL, ResolverStyle.SMART, Locale.US, "11:59 noon", "12:00"},
701                 {TextStyle.FULL, ResolverStyle.SMART, Locale.US, "18:00 in the afternoon", "12:00-18:00"},
702                 {TextStyle.FULL, ResolverStyle.SMART, Locale.US, "17:59 in the evening", "18:00-21:00"},
703                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "00:01 mi", "00:00"},
704                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "06:01 at night", "21:00-06:00"},
705                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "05:59 in the morning", "06:00-12:00"},
706                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "11:59 n", "12:00"},
707                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "18:00 in the afternoon", "12:00-18:00"},
708                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.US, "17:59 in the evening", "18:00-21:00"},
709 
710                 {TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d", "00:00"},
711                 {TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "04:00 \u591c\u4e2d", "23:00-04:00"},
712                 {TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "03:59 \u671d", "04:00-12:00"},
713                 {TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "12:01 \u6b63\u5348", "12:00"},
714                 {TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "16:00 \u663c", "12:00-16:00"},
715                 {TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "19:01 \u5915\u65b9", "16:00-19:00"},
716                 {TextStyle.FULL, ResolverStyle.SMART, Locale.JAPAN, "23:00 \u591c", "19:00-23:00"},
717                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d", "00:00"},
718                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "04:00 \u591c\u4e2d", "23:00-04:00"},
719                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "03:59 \u671d", "04:00-12:00"},
720                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "12:01 \u6b63\u5348", "12:00"},
721                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "16:00 \u663c", "12:00-16:00"},
722                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "19:01 \u5915\u65b9", "16:00-19:00"},
723                 {TextStyle.NARROW, ResolverStyle.SMART, Locale.JAPAN, "23:00 \u591c", "19:00-23:00"},
724 
725                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "00:01 midnight", "00:00"},
726                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "06:01 at night", "21:00-06:00"},
727                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "05:59 in the morning", "06:00-12:00"},
728                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "11:59 noon", "12:00"},
729                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "18:00 in the afternoon", "12:00-18:00"},
730                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.US, "17:59 in the evening", "18:00-21:00"},
731                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "00:01 mi", "00:00"},
732                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "06:01 at night", "21:00-06:00"},
733                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "05:59 in the morning", "06:00-12:00"},
734                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "11:59 n", "12:00"},
735                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "18:00 in the afternoon", "12:00-18:00"},
736                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.US, "17:59 in the evening", "18:00-21:00"},
737 
738                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d", "00:00"},
739                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "04:00 \u591c\u4e2d", "23:00-04:00"},
740                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "03:59 \u671d", "04:00-12:00"},
741                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "12:01 \u6b63\u5348", "12:00"},
742                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "16:00 \u663c", "12:00-16:00"},
743                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "19:01 \u5915\u65b9", "16:00-19:00"},
744                 {TextStyle.FULL, ResolverStyle.LENIENT, Locale.JAPAN, "23:00 \u591c", "19:00-23:00"},
745                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "00:01 \u771f\u591c\u4e2d", "00:00"},
746                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "04:00 \u591c\u4e2d", "23:00-04:00"},
747                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "03:59 \u671d", "04:00-12:00"},
748                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "12:01 \u6b63\u5348", "12:00"},
749                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "16:00 \u663c", "12:00-16:00"},
750                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "19:01 \u5915\u65b9", "16:00-19:00"},
751                 {TextStyle.NARROW, ResolverStyle.LENIENT, Locale.JAPAN, "23:00 \u591c", "19:00-23:00"},
752         };
753     }
754     @Test (dataProvider="dayPeriodParseInvalid")
755     public void test_dayPeriodParseInvalid(TextStyle ts, ResolverStyle rs, Locale l, String dayPeriod, String periodRange) throws Exception {
756         try {
757             builder.append(ISO_LOCAL_TIME).appendLiteral(' ').appendDayPeriodText(ts)
758                     .toFormatter()
759                     .withLocale(l)
760                     .parse(dayPeriod);
761             if (rs != ResolverStyle.LENIENT) {
762                 throw new RuntimeException("DateTimeParseException should be thrown");
763             }
764         } catch (DateTimeParseException e) {
765             assertEquals(e.getCause().getMessage(),
766                     "Conflict found: Resolved time " + dayPeriod.substring(0, 5) + " conflicts with " +
767                     "DayPeriod(" + periodRange + ")");
768         }
769     }
770 
771     @DataProvider(name="dayPeriodParsePatternInvalid")
772     Object[][] data_dayPeriodParsePatternInvalid() {
773         return new Object[][] {
774                 {"H B", ResolverStyle.SMART, "47 at night", 23, null},
775                 {"H B", ResolverStyle.SMART, "51 at night", 3, null},
776                 {"H B", ResolverStyle.SMART, "-2 at night", 22, null},
777                 {"K B", ResolverStyle.SMART, "59 at night", 23, null},
778                 {"K B", ResolverStyle.SMART, "51 at night", 3, null},
779                 {"K B", ResolverStyle.SMART, "59 in the morning", 11, null},
780                 {"K B", ResolverStyle.SMART, "-2 in the morning", 22, null},
781                 {"h B", ResolverStyle.SMART, "59 at night", 23, null},
782                 {"h B", ResolverStyle.SMART, "51 at night", 3, null},
783                 {"h B", ResolverStyle.SMART, "59 in the morning", 11, null},
784                 {"h B", ResolverStyle.SMART, "-2 in the morning", 22, null},
785 
786                 {"H B", ResolverStyle.LENIENT, "47 at night", 23, Period.ofDays(1)},
787                 {"H B", ResolverStyle.LENIENT, "51 at night", 3, Period.ofDays(2)},
788                 {"H B", ResolverStyle.LENIENT, "-2 at night", 22, Period.ofDays(-1)},
789                 {"K B", ResolverStyle.LENIENT, "59 at night", 23, Period.ofDays(2)},
790                 {"K B", ResolverStyle.LENIENT, "51 at night", 3, Period.ofDays(2)},
791                 {"K B", ResolverStyle.LENIENT, "59 in the morning", 11, Period.ofDays(2)},
792                 {"K B", ResolverStyle.LENIENT, "-2 in the morning", 22, Period.ofDays(-1)},
793                 {"h B", ResolverStyle.LENIENT, "59 at night", 23, Period.ofDays(2)},
794                 {"h B", ResolverStyle.LENIENT, "51 at night", 3, Period.ofDays(2)},
795                 {"h B", ResolverStyle.LENIENT, "59 in the morning", 11, Period.ofDays(2)},
796                 {"h B", ResolverStyle.LENIENT, "-2 in the morning", 22, Period.ofDays(-1)},
797         };
798     }
799 
800     @Test (dataProvider="dayPeriodParsePatternInvalid")
801     public void test_dayPeriodParsePatternInvalid(String pattern, ResolverStyle rs, String hourDayPeriod, long expected, Period expectedExcessDays) throws Exception {
802         try {
803             builder.appendPattern(pattern);
804             DateTimeFormatter f = builder.toFormatter().withLocale(Locale.US).withResolverStyle(rs);
805             var p = f.parse(hourDayPeriod);
806             if (rs != ResolverStyle.LENIENT) {
807                 throw new RuntimeException("DateTimeParseException should be thrown");
808             }
809             assertEquals(p.getLong(HOUR_OF_DAY), expected);
810             assertEquals(p.query(DateTimeFormatter.parsedExcessDays()), expectedExcessDays);
811         } catch (DateTimeParseException e) {
812             // exception successfully thrown
813         }
814     }
815 
816     @Test (expectedExceptions = DateTimeParseException.class)
817     public void test_dayPeriodParseStrictNoTime() {
818         builder.appendPattern("B");
819         DateTimeFormatter f = builder.toFormatter().withLocale(Locale.US).withResolverStyle(ResolverStyle.STRICT);
820         LocalTime.parse("at night", f);
821     }
822 
823     @Test
824     public void test_dayPeriodUserFieldResolution() {
825         var dtf = builder
826                 .appendValue(new TemporalField() {
827                                  @Override
828                                  public TemporalUnit getBaseUnit() {
829                                      return null;
830                                  }
831 
832                                  @Override
833                                  public TemporalUnit getRangeUnit() {
834                                      return null;
835                                  }
836 
837                                  @Override
838                                  public ValueRange range() {
839                                      return null;
840                                  }
841 
842                                  @Override
843                                  public boolean isDateBased() {
844                                      return false;
845                                  }
846 
847                                  @Override
848                                  public boolean isTimeBased() {
849                                      return false;
850                                  }
851 
852                                  @Override
853                                  public boolean isSupportedBy(TemporalAccessor temporal) {
854                                      return false;
855                                  }
856 
857                                  @Override
858                                  public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
859                                      return null;
860                                  }
861 
862                                  @Override
863                                  public long getFrom(TemporalAccessor temporal) {
864                                      return 0;
865                                  }
866 
867                                  @Override
868                                  public <R extends Temporal> R adjustInto(R temporal, long newValue) {
869                                      return null;
870                                  }
871 
872                                  @Override
873                                  public TemporalAccessor resolve(
874                                          Map<TemporalField, Long> fieldValues,
875                                          TemporalAccessor partialTemporal,
876                                          ResolverStyle resolverStyle) {
877                                      fieldValues.remove(this);
878                                      fieldValues.put(ChronoField.HOUR_OF_DAY, 6L);
879                                      return null;
880                                  }
881                              },
882                         1)
883                 .appendPattern(" B")
884                 .toFormatter()
885                 .withLocale(Locale.US);
886         assertEquals((long)dtf.parse("0 in the morning").getLong(ChronoField.HOUR_OF_DAY), 6L);
887         try {
888             dtf.parse("0 at night");
889             fail("DateTimeParseException should be thrown");
890         } catch (DateTimeParseException e) {
891             // success
892         }
893     }
894     */
895     // END Android-removed: DateTimeFormatBuilder.appendDayPeriodText is temporarily unsupported.
896     //-----------------------------------------------------------------------
897     //-----------------------------------------------------------------------
898     //-----------------------------------------------------------------------
899     @Test
test_padNext_1arg()900     public void test_padNext_1arg() {
901         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':').padNext(2).appendValue(DAY_OF_MONTH);
902         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2: 1");
903     }
904 
905     @Test(expectedExceptions=IllegalArgumentException.class)
test_padNext_1arg_invalidWidth()906     public void test_padNext_1arg_invalidWidth() throws Exception {
907         builder.padNext(0);
908     }
909 
910     //-----------------------------------------------------------------------
911     @Test
test_padNext_2arg_dash()912     public void test_padNext_2arg_dash() throws Exception {
913         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':').padNext(2, '-').appendValue(DAY_OF_MONTH);
914         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2:-1");
915     }
916 
917     @Test(expectedExceptions=IllegalArgumentException.class)
test_padNext_2arg_invalidWidth()918     public void test_padNext_2arg_invalidWidth() throws Exception {
919         builder.padNext(0, '-');
920     }
921 
922     //-----------------------------------------------------------------------
923     @Test
test_padOptional()924     public void test_padOptional() throws Exception {
925         builder.appendValue(MONTH_OF_YEAR).appendLiteral(':')
926                 .padNext(5).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd()
927                 .appendLiteral(':').appendValue(YEAR);
928         assertEquals(builder.toFormatter().format(LocalDate.of(2013, 2, 1)), "2:    1:2013");
929         assertEquals(builder.toFormatter().format(YearMonth.of(2013, 2)), "2:     :2013");
930     }
931 
932     //-----------------------------------------------------------------------
933     //-----------------------------------------------------------------------
934     //-----------------------------------------------------------------------
935     @Test
test_optionalStart_noEnd()936     public void test_optionalStart_noEnd() throws Exception {
937         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).appendValue(DAY_OF_WEEK);
938         DateTimeFormatter f = builder.toFormatter();
939         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)Value(DayOfWeek)]");
940     }
941 
942     @Test
test_optionalStart2_noEnd()943     public void test_optionalStart2_noEnd() throws Exception {
944         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalStart().appendValue(DAY_OF_WEEK);
945         DateTimeFormatter f = builder.toFormatter();
946         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]]");
947     }
948 
949     @Test
test_optionalStart_doubleStart()950     public void test_optionalStart_doubleStart() throws Exception {
951         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH);
952         DateTimeFormatter f = builder.toFormatter();
953         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
954     }
955 
956     //-----------------------------------------------------------------------
957     @Test
test_optionalEnd()958     public void test_optionalEnd() throws Exception {
959         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().appendValue(DAY_OF_WEEK);
960         DateTimeFormatter f = builder.toFormatter();
961         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)]Value(DayOfWeek)");
962     }
963 
964     @Test
test_optionalEnd2()965     public void test_optionalEnd2() throws Exception {
966         builder.appendValue(MONTH_OF_YEAR).optionalStart().appendValue(DAY_OF_MONTH)
967             .optionalStart().appendValue(DAY_OF_WEEK).optionalEnd().appendValue(DAY_OF_MONTH).optionalEnd();
968         DateTimeFormatter f = builder.toFormatter();
969         assertEquals(f.toString(), "Value(MonthOfYear)[Value(DayOfMonth)[Value(DayOfWeek)]Value(DayOfMonth)]");
970     }
971 
972     @Test
test_optionalEnd_doubleStartSingleEnd()973     public void test_optionalEnd_doubleStartSingleEnd() throws Exception {
974         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd();
975         DateTimeFormatter f = builder.toFormatter();
976         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
977     }
978 
979     @Test
test_optionalEnd_doubleStartDoubleEnd()980     public void test_optionalEnd_doubleStartDoubleEnd() throws Exception {
981         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalStart().appendValue(DAY_OF_MONTH).optionalEnd().optionalEnd();
982         DateTimeFormatter f = builder.toFormatter();
983         assertEquals(f.toString(), "Value(MonthOfYear)[[Value(DayOfMonth)]]");
984     }
985 
986     @Test
test_optionalStartEnd_immediateStartEnd()987     public void test_optionalStartEnd_immediateStartEnd() throws Exception {
988         builder.appendValue(MONTH_OF_YEAR).optionalStart().optionalEnd().appendValue(DAY_OF_MONTH);
989         DateTimeFormatter f = builder.toFormatter();
990         assertEquals(f.toString(), "Value(MonthOfYear)Value(DayOfMonth)");
991     }
992 
993     @Test(expectedExceptions=IllegalStateException.class)
test_optionalEnd_noStart()994     public void test_optionalEnd_noStart() throws Exception {
995         builder.optionalEnd();
996     }
997 
998     //-----------------------------------------------------------------------
999     //-----------------------------------------------------------------------
1000     //-----------------------------------------------------------------------
1001     @DataProvider(name="validPatterns")
dataValid()1002     Object[][] dataValid() {
1003         return new Object[][] {
1004             {"'a'", "'a'"},
1005             {"''", "''"},
1006             {"'!'", "'!'"},
1007             {"!", "'!'"},
1008 
1009             {"'hello_people,][)('", "'hello_people,][)('"},
1010             {"'hi'", "'hi'"},
1011             {"'yyyy'", "'yyyy'"},
1012             {"''''", "''"},
1013             {"'o''clock'", "'o''clock'"},
1014 
1015             {"G", "Text(Era,SHORT)"},
1016             {"GG", "Text(Era,SHORT)"},
1017             {"GGG", "Text(Era,SHORT)"},
1018             {"GGGG", "Text(Era)"},
1019             {"GGGGG", "Text(Era,NARROW)"},
1020 
1021             {"u", "Value(Year)"},
1022             {"uu", "ReducedValue(Year,2,2,2000-01-01)"},
1023             {"uuu", "Value(Year,3,19,NORMAL)"},
1024             {"uuuu", "Value(Year,4,19,EXCEEDS_PAD)"},
1025             {"uuuuu", "Value(Year,5,19,EXCEEDS_PAD)"},
1026 
1027             {"y", "Value(YearOfEra)"},
1028             {"yy", "ReducedValue(YearOfEra,2,2,2000-01-01)"},
1029             {"yyy", "Value(YearOfEra,3,19,NORMAL)"},
1030             {"yyyy", "Value(YearOfEra,4,19,EXCEEDS_PAD)"},
1031             {"yyyyy", "Value(YearOfEra,5,19,EXCEEDS_PAD)"},
1032 
1033             {"Y", "Localized(WeekBasedYear)"},
1034             {"YY", "Localized(ReducedValue(WeekBasedYear,2,2,2000-01-01))"},
1035             {"YYY", "Localized(WeekBasedYear,3,19,NORMAL)"},
1036             {"YYYY", "Localized(WeekBasedYear,4,19,EXCEEDS_PAD)"},
1037             {"YYYYY", "Localized(WeekBasedYear,5,19,EXCEEDS_PAD)"},
1038 
1039             {"M", "Value(MonthOfYear)"},
1040             {"MM", "Value(MonthOfYear,2)"},
1041             {"MMM", "Text(MonthOfYear,SHORT)"},
1042             {"MMMM", "Text(MonthOfYear)"},
1043             {"MMMMM", "Text(MonthOfYear,NARROW)"},
1044 
1045             {"L", "Value(MonthOfYear)"},
1046             {"LL", "Value(MonthOfYear,2)"},
1047             {"LLL", "Text(MonthOfYear,SHORT_STANDALONE)"},
1048             {"LLLL", "Text(MonthOfYear,FULL_STANDALONE)"},
1049             {"LLLLL", "Text(MonthOfYear,NARROW_STANDALONE)"},
1050 
1051             {"D", "Value(DayOfYear)"},
1052             {"DD", "Value(DayOfYear,2,3,NOT_NEGATIVE)"},
1053             {"DDD", "Value(DayOfYear,3)"},
1054 
1055             {"d", "Value(DayOfMonth)"},
1056             {"dd", "Value(DayOfMonth,2)"},
1057 
1058             {"F", "Value(AlignedDayOfWeekInMonth)"},
1059 
1060             {"Q", "Value(QuarterOfYear)"},
1061             {"QQ", "Value(QuarterOfYear,2)"},
1062             {"QQQ", "Text(QuarterOfYear,SHORT)"},
1063             {"QQQQ", "Text(QuarterOfYear)"},
1064             {"QQQQQ", "Text(QuarterOfYear,NARROW)"},
1065 
1066             {"q", "Value(QuarterOfYear)"},
1067             {"qq", "Value(QuarterOfYear,2)"},
1068             {"qqq", "Text(QuarterOfYear,SHORT_STANDALONE)"},
1069             {"qqqq", "Text(QuarterOfYear,FULL_STANDALONE)"},
1070             {"qqqqq", "Text(QuarterOfYear,NARROW_STANDALONE)"},
1071 
1072             {"E", "Text(DayOfWeek,SHORT)"},
1073             {"EE", "Text(DayOfWeek,SHORT)"},
1074             {"EEE", "Text(DayOfWeek,SHORT)"},
1075             {"EEEE", "Text(DayOfWeek)"},
1076             {"EEEEE", "Text(DayOfWeek,NARROW)"},
1077 
1078             {"e", "Localized(DayOfWeek,1)"},
1079             {"ee", "Localized(DayOfWeek,2)"},
1080             {"eee", "Text(DayOfWeek,SHORT)"},
1081             {"eeee", "Text(DayOfWeek)"},
1082             {"eeeee", "Text(DayOfWeek,NARROW)"},
1083 
1084             {"c", "Localized(DayOfWeek,1)"},
1085             {"ccc", "Text(DayOfWeek,SHORT_STANDALONE)"},
1086             {"cccc", "Text(DayOfWeek,FULL_STANDALONE)"},
1087             {"ccccc", "Text(DayOfWeek,NARROW_STANDALONE)"},
1088 
1089             {"a", "Text(AmPmOfDay,SHORT)"},
1090 
1091             {"H", "Value(HourOfDay)"},
1092             {"HH", "Value(HourOfDay,2)"},
1093 
1094             {"K", "Value(HourOfAmPm)"},
1095             {"KK", "Value(HourOfAmPm,2)"},
1096 
1097             {"k", "Value(ClockHourOfDay)"},
1098             {"kk", "Value(ClockHourOfDay,2)"},
1099 
1100             {"h", "Value(ClockHourOfAmPm)"},
1101             {"hh", "Value(ClockHourOfAmPm,2)"},
1102 
1103             {"m", "Value(MinuteOfHour)"},
1104             {"mm", "Value(MinuteOfHour,2)"},
1105 
1106             {"s", "Value(SecondOfMinute)"},
1107             {"ss", "Value(SecondOfMinute,2)"},
1108 
1109             {"S", "Fraction(NanoOfSecond,1,1)"},
1110             {"SS", "Fraction(NanoOfSecond,2,2)"},
1111             {"SSS", "Fraction(NanoOfSecond,3,3)"},
1112             {"SSSSSSSSS", "Fraction(NanoOfSecond,9,9)"},
1113 
1114             {"A", "Value(MilliOfDay,1,19,NOT_NEGATIVE)"},
1115             {"AA", "Value(MilliOfDay,2,19,NOT_NEGATIVE)"},
1116             {"AAA", "Value(MilliOfDay,3,19,NOT_NEGATIVE)"},
1117 
1118             {"n", "Value(NanoOfSecond,1,19,NOT_NEGATIVE)"},
1119             {"nn", "Value(NanoOfSecond,2,19,NOT_NEGATIVE)"},
1120             {"nnn", "Value(NanoOfSecond,3,19,NOT_NEGATIVE)"},
1121 
1122             {"N", "Value(NanoOfDay,1,19,NOT_NEGATIVE)"},
1123             {"NN", "Value(NanoOfDay,2,19,NOT_NEGATIVE)"},
1124             {"NNN", "Value(NanoOfDay,3,19,NOT_NEGATIVE)"},
1125 
1126             {"z", "ZoneText(SHORT)"},
1127             {"zz", "ZoneText(SHORT)"},
1128             {"zzz", "ZoneText(SHORT)"},
1129             {"zzzz", "ZoneText(FULL)"},
1130 
1131             {"VV", "ZoneId()"},
1132 
1133             {"Z", "Offset(+HHMM,'+0000')"},  // SimpleDateFormat
1134             {"ZZ", "Offset(+HHMM,'+0000')"},  // SimpleDateFormat
1135             {"ZZZ", "Offset(+HHMM,'+0000')"},  // SimpleDateFormat
1136 
1137             {"X", "Offset(+HHmm,'Z')"},  // LDML/almost SimpleDateFormat
1138             {"XX", "Offset(+HHMM,'Z')"},  // LDML/SimpleDateFormat
1139             {"XXX", "Offset(+HH:MM,'Z')"},  // LDML/SimpleDateFormat
1140             {"XXXX", "Offset(+HHMMss,'Z')"},  // LDML
1141             {"XXXXX", "Offset(+HH:MM:ss,'Z')"},  // LDML
1142 
1143             {"x", "Offset(+HHmm,'+00')"},  // LDML
1144             {"xx", "Offset(+HHMM,'+0000')"},  // LDML
1145             {"xxx", "Offset(+HH:MM,'+00:00')"},  // LDML
1146             {"xxxx", "Offset(+HHMMss,'+0000')"},  // LDML
1147             {"xxxxx", "Offset(+HH:MM:ss,'+00:00')"},  // LDML
1148 
1149             {"ppH", "Pad(Value(HourOfDay),2)"},
1150             {"pppDD", "Pad(Value(DayOfYear,2,3,NOT_NEGATIVE),3)"},
1151 
1152             {"yyyy[-MM[-dd", "Value(YearOfEra,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"},
1153             {"yyyy[-MM[-dd]]", "Value(YearOfEra,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)['-'Value(DayOfMonth,2)]]"},
1154             {"yyyy[-MM[]-dd]", "Value(YearOfEra,4,19,EXCEEDS_PAD)['-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)]"},
1155 
1156             {"yyyy-MM-dd'T'HH:mm:ss.SSS", "Value(YearOfEra,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)" +
1157                 "'T'Value(HourOfDay,2)':'Value(MinuteOfHour,2)':'Value(SecondOfMinute,2)'.'Fraction(NanoOfSecond,3,3)"},
1158 
1159             {"w", "Localized(WeekOfWeekBasedYear,1)"},
1160             {"ww", "Localized(WeekOfWeekBasedYear,2)"},
1161             {"W", "Localized(WeekOfMonth,1)"},
1162 
1163             // Android-removed: The symbol 'B' is temporarily unsupported.
1164             // {"B", "DayPeriod(SHORT)"},
1165             // {"BBBB", "DayPeriod(FULL)"},
1166             // {"BBBBB", "DayPeriod(NARROW)"},
1167         };
1168     }
1169 
1170     @Test(dataProvider="validPatterns")
test_appendPattern_valid(String input, String expected)1171     public void test_appendPattern_valid(String input, String expected) throws Exception {
1172         builder.appendPattern(input);
1173         DateTimeFormatter f = builder.toFormatter();
1174         assertEquals(f.toString(), expected);
1175     }
1176 
1177     //-----------------------------------------------------------------------
1178     @DataProvider(name="invalidPatterns")
dataInvalid()1179     Object[][] dataInvalid() {
1180         return new Object[][] {
1181             {"'"},
1182             {"'hello"},
1183             {"'hel''lo"},
1184             {"'hello''"},
1185             {"{"},
1186             {"}"},
1187             {"{}"},
1188             {"]"},
1189             {"yyyy]"},
1190             {"yyyy]MM"},
1191             {"yyyy[MM]]"},
1192 
1193             {"aa"},
1194             {"aaa"},
1195             {"aaaa"},
1196             {"aaaaa"},
1197             {"aaaaaa"},
1198             {"MMMMMM"},
1199             {"LLLLLL"},
1200             {"QQQQQQ"},
1201             {"qqqqqq"},
1202             {"EEEEEE"},
1203             {"eeeeee"},
1204             {"cc"},
1205             {"cccccc"},
1206             {"ddd"},
1207             {"DDDD"},
1208             {"FF"},
1209             {"FFF"},
1210             {"hhh"},
1211             {"HHH"},
1212             {"kkk"},
1213             {"KKK"},
1214             {"mmm"},
1215             {"sss"},
1216             {"OO"},
1217             {"OOO"},
1218             {"OOOOO"},
1219             {"XXXXXX"},
1220             {"ZZZZZZ"},
1221             {"zzzzz"},
1222             {"V"},
1223             {"VVV"},
1224             {"VVVV"},
1225             {"VVVVV"},
1226 
1227             {"RO"},
1228 
1229             {"p"},
1230             {"pp"},
1231             {"p:"},
1232 
1233             {"f"},
1234             {"ff"},
1235             {"f:"},
1236             {"fy"},
1237             {"fa"},
1238             {"fM"},
1239 
1240             {"www"},
1241             {"WW"},
1242 
1243             {"BB"},
1244             {"BBB"},
1245             {"BBBBBB"},
1246         };
1247     }
1248 
1249     @Test(dataProvider="invalidPatterns", expectedExceptions=IllegalArgumentException.class)
test_appendPattern_invalid(String input)1250     public void test_appendPattern_invalid(String input) throws Exception {
1251         try {
1252             builder.appendPattern(input);
1253         } catch (IllegalArgumentException ex) {
1254             throw ex;
1255         }
1256     }
1257 
1258     //-----------------------------------------------------------------------
1259     @DataProvider(name="localePatterns")
localizedDateTimePatterns()1260     Object[][] localizedDateTimePatterns() {
1261         // Android-changed: Adapt for changes since old CLDR version this tests were written for.
1262         return new Object[][] {
1263             {FormatStyle.FULL, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.US, "EEEE, MMMM d, y 'at' h:mm:ss\u202fa zzzz"},
1264             {FormatStyle.LONG, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.US, "MMMM d, y 'at' h:mm:ss\u202fa z"},
1265             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.US, "MMM d, y, h:mm:ss\u202fa"},
1266             {FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.US, "M/d/yy, h:mm\u202fa"},
1267             {FormatStyle.FULL, null, IsoChronology.INSTANCE, Locale.US, "EEEE, MMMM d, y"},
1268             {FormatStyle.LONG, null, IsoChronology.INSTANCE, Locale.US, "MMMM d, y"},
1269             {FormatStyle.MEDIUM, null, IsoChronology.INSTANCE, Locale.US, "MMM d, y"},
1270             {FormatStyle.SHORT, null, IsoChronology.INSTANCE, Locale.US, "M/d/yy"},
1271             {null, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.US, "h:mm:ss\u202fa zzzz"},
1272             {null, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.US, "h:mm:ss\u202fa z"},
1273             {null, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.US, "h:mm:ss\u202fa"},
1274             {null, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.US, "h:mm\u202fa"},
1275         };
1276     }
1277 
1278     @Test(dataProvider="localePatterns")
test_getLocalizedDateTimePattern(FormatStyle dateStyle, FormatStyle timeStyle, Chronology chrono, Locale locale, String expected)1279     public void test_getLocalizedDateTimePattern(FormatStyle dateStyle, FormatStyle timeStyle,
1280             Chronology chrono, Locale locale, String expected) {
1281         // Android-changed: Require ICU 72 to pass the test due to CLDR data change
1282         if (VersionInfo.ICU_VERSION.getMajor() < 72) {
1283             return;
1284         }
1285         String actual = DateTimeFormatterBuilder.getLocalizedDateTimePattern(dateStyle, timeStyle, chrono, locale);
1286         assertEquals(actual, expected, "Pattern " + convertNonAscii(actual));
1287     }
1288 
1289     @Test(expectedExceptions=java.lang.IllegalArgumentException.class)
test_getLocalizedDateTimePatternIAE()1290     public void test_getLocalizedDateTimePatternIAE() {
1291         DateTimeFormatterBuilder.getLocalizedDateTimePattern(null, null, IsoChronology.INSTANCE, Locale.US);
1292     }
1293 
1294     @Test(expectedExceptions=java.lang.NullPointerException.class)
test_getLocalizedChronoNPE()1295     public void test_getLocalizedChronoNPE() {
1296         DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT, FormatStyle.SHORT, null, Locale.US);
1297     }
1298 
1299     @Test(expectedExceptions=java.lang.NullPointerException.class)
test_getLocalizedLocaleNPE()1300     public void test_getLocalizedLocaleNPE() {
1301         DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, null);
1302     }
1303 
1304     /**
1305      * Returns a string that includes non-ascii characters after expanding
1306      * the non-ascii characters to their Java language \\uxxxx form.
1307      * @param input an input string
1308      * @return the encoded string.
1309      */
convertNonAscii(String input)1310     private String convertNonAscii(String input) {
1311         StringBuilder sb = new StringBuilder(input.length() * 6);
1312         for (int i = 0; i < input.length(); i++) {
1313             char ch = input.charAt(i);
1314             if (ch < 255) {
1315                 sb.append(ch);
1316             } else {
1317                 sb.append("\\u");
1318                 sb.append(Integer.toHexString(ch));
1319             }
1320         }
1321         return sb.toString();
1322     }
1323 
date(int y, int m, int d)1324     private static Temporal date(int y, int m, int d) {
1325         return LocalDate.of(y, m, d);
1326     }
1327 }
1328