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) 2010-2012, Stephen Colebourne & Michael Nascimento Santos
31  *
32  * All rights reserved.
33  *
34  * Redistribution and use in source and binary forms, with or without
35  * modification, are permitted provided that the following conditions are met:
36  *
37  *  * Redistributions of source code must retain the above copyright notice,
38  *    this list of conditions and the following disclaimer.
39  *
40  *  * Redistributions in binary form must reproduce the above copyright notice,
41  *    this list of conditions and the following disclaimer in the documentation
42  *    and/or other materials provided with the distribution.
43  *
44  *  * Neither the name of JSR-310 nor the names of its contributors
45  *    may be used to endorse or promote products derived from this software
46  *    without specific prior written permission.
47  *
48  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
49  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
50  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
51  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
52  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
53  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
54  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
55  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
56  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
57  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
58  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59  */
60 package tck.java.time.format;
61 
62 import static org.testng.Assert.assertEquals;
63 import static org.testng.Assert.assertNotNull;
64 
65 import java.text.ParsePosition;
66 import java.time.DateTimeException;
67 import java.time.LocalDateTime;
68 import java.time.ZoneId;
69 import java.time.ZoneOffset;
70 import java.time.ZonedDateTime;
71 import java.time.format.DateTimeFormatter;
72 import java.time.format.DateTimeFormatterBuilder;
73 import java.time.temporal.TemporalAccessor;
74 import java.time.temporal.TemporalQueries;
75 import java.util.Locale;
76 import java.util.Objects;
77 
78 import org.testng.annotations.BeforeMethod;
79 import org.testng.annotations.DataProvider;
80 import org.testng.annotations.Test;
81 
82 /**
83  * Test DateTimeFormatterBuilder.appendZoneId().
84  */
85 @Test
86 public class TCKZoneIdPrinterParser {
87 
88     private static final ZoneOffset OFFSET_UTC = ZoneOffset.UTC;
89     private static final ZoneOffset OFFSET_P0123 = ZoneOffset.ofHoursMinutes(1, 23);
90     private static final ZoneId EUROPE_PARIS = ZoneId.of("Europe/Paris");
91     private static final ZoneId AMERICA_NEW_YORK = ZoneId.of("America/New_York");
92     private static final LocalDateTime DT_2012_06_30_12_30_40 = LocalDateTime.of(2012, 6, 30, 12, 30, 40);
93 
94     private DateTimeFormatterBuilder builder;
95     private ParsePosition pos;
96 
97     @BeforeMethod
setUp()98     public void setUp() {
99         builder = new DateTimeFormatterBuilder();
100         pos = new ParsePosition(0);
101     }
102 
103     //-----------------------------------------------------------------------
104     @DataProvider(name="print")
data_print()105     Object[][] data_print() {
106         return new Object[][] {
107                 {DT_2012_06_30_12_30_40, EUROPE_PARIS, "Europe/Paris"},
108                 {DT_2012_06_30_12_30_40, AMERICA_NEW_YORK, "America/New_York"},
109                 {DT_2012_06_30_12_30_40, OFFSET_UTC, "Z"},
110                 {DT_2012_06_30_12_30_40, OFFSET_P0123, "+01:23"},
111         };
112     }
113 
114     @Test(dataProvider="print")
test_print(LocalDateTime ldt, ZoneId zone, String expected)115     public void test_print(LocalDateTime ldt, ZoneId zone, String expected) {
116         ZonedDateTime zdt = ldt.atZone(zone);
117         builder.appendZoneId();
118         String output = builder.toFormatter().format(zdt);
119         assertEquals(output, expected);
120     }
121 
122     @Test(dataProvider="print")
test_print_pattern_VV(LocalDateTime ldt, ZoneId zone, String expected)123     public void test_print_pattern_VV(LocalDateTime ldt, ZoneId zone, String expected) {
124         ZonedDateTime zdt = ldt.atZone(zone);
125         builder.appendPattern("VV");
126         String output = builder.toFormatter().format(zdt);
127         assertEquals(output, expected);
128     }
129 
130     //-----------------------------------------------------------------------
131     @Test(expectedExceptions=IllegalArgumentException.class)
test_print_pattern_V1rejected()132     public void test_print_pattern_V1rejected() {
133         builder.appendPattern("V");
134     }
135 
136     @Test(expectedExceptions=IllegalArgumentException.class)
test_print_pattern_V3rejected()137     public void test_print_pattern_V3rejected() {
138         builder.appendPattern("VVV");
139     }
140 
141     @Test(expectedExceptions=IllegalArgumentException.class)
test_print_pattern_V4rejected()142     public void test_print_pattern_V4rejected() {
143         builder.appendPattern("VVVV");
144     }
145 
146     @Test(expectedExceptions=IllegalArgumentException.class)
test_print_pattern_V5rejected()147     public void test_print_pattern_V5rejected() {
148         builder.appendPattern("VVVVV");
149     }
150 
151     //-----------------------------------------------------------------------
152     @DataProvider(name="parseSuccess")
data_parseSuccess()153     Object[][] data_parseSuccess() {
154         return new Object[][] {
155                 {"Z", 1, -1, ZoneId.of("Z")},
156                 {"UTC", 3, -1, ZoneId.of("UTC")},
157                 {"UT", 2, -1, ZoneId.of("UT")},
158                 {"GMT", 3, -1, ZoneId.of("GMT")},
159 
160                 {"+00:00", 6, -1, ZoneOffset.UTC},
161                 {"UTC+00:00", 9, -1, ZoneId.of("UTC")},
162                 {"UT+00:00", 8, -1, ZoneId.of("UT")},
163                 {"GMT+00:00", 9, -1, ZoneId.of("GMT")},
164                 {"-00:00", 6, -1, ZoneOffset.UTC},
165                 {"UTC-00:00", 9, -1, ZoneId.of("UTC")},
166                 {"UT-00:00", 8, -1, ZoneId.of("UT")},
167                 {"GMT-00:00", 9, -1, ZoneId.of("GMT")},
168 
169                 {"+01:30", 6, -1, ZoneOffset.ofHoursMinutes(1, 30)},
170                 {"UTC+01:30", 9, -1, ZoneId.of("UTC+01:30")},
171                 {"UT+02:30", 8, -1, ZoneId.of("UT+02:30")},
172                 {"GMT+03:30", 9, -1, ZoneId.of("GMT+03:30")},
173                 {"-01:30", 6, -1, ZoneOffset.ofHoursMinutes(-1, -30)},
174                 {"UTC-01:30", 9, -1, ZoneId.of("UTC-01:30")},
175                 {"UT-02:30", 8, -1, ZoneId.of("UT-02:30")},
176                 {"GMT-03:30", 9, -1, ZoneId.of("GMT-03:30")},
177 
178                 // fallback to UTC
179                 {"UTC-01:WW", 3, -1, ZoneId.of("UTC")},
180                 {"UT-02:WW", 2, -1, ZoneId.of("UT")},
181                 {"GMT-03:WW", 3, -1, ZoneId.of("GMT")},
182                 {"Z0", 1, -1, ZoneOffset.UTC},
183                 {"UTC1", 3, -1, ZoneId.of("UTC")},
184 
185                 // Z not parsed as zero
186                 {"UTCZ", 3, -1, ZoneId.of("UTC")},
187                 {"UTZ", 2, -1, ZoneId.of("UT")},
188                 {"GMTZ", 3, -1, ZoneId.of("GMT")},
189 
190                 // 0 not parsed
191                 {"UTC0", 3, -1, ZoneId.of("UTC")},
192                 {"UT0", 2, -1, ZoneId.of("UT")},
193 
194                 // fail to parse
195                 {"", 0, 0, null},
196                 {"A", 0, 0, null},
197                 {"UZ", 0, 0, null},
198                 {"GMA", 0, 0, null},
199                 {"0", 0, 0, null},
200                 {"+", 0, 0, null},
201                 {"-", 0, 0, null},
202 
203                 // zone IDs
204                 {"Europe/London", 13, -1, ZoneId.of("Europe/London")},
205                 {"America/New_York", 16, -1, ZoneId.of("America/New_York")},
206                 {"America/Bogusville", 0, 0, null},
207         };
208     }
209 
210     @Test(dataProvider="parseSuccess")
test_parseSuccess_plain(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected)211     public void test_parseSuccess_plain(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected) {
212         builder.appendZoneId();
213         TemporalAccessor parsed = builder.toFormatter().parseUnresolved(text, pos);
214         assertEquals(pos.getErrorIndex(), expectedErrorIndex, "Incorrect error index parsing: " + text);
215         assertEquals(pos.getIndex(), expectedIndex, "Incorrect index parsing: " + text);
216         if (expected != null) {
217             assertEquals(parsed.query(TemporalQueries.zoneId()), expected, "Incorrect zoneId parsing: " + text);
218             assertEquals(parsed.query(TemporalQueries.offset()), null, "Incorrect offset parsing: " + text);
219             assertEquals(parsed.query(TemporalQueries.zone()), expected, "Incorrect zone parsing: " + text);
220         } else {
221             assertEquals(parsed, null);
222         }
223     }
224 
225     @Test(dataProvider="parseSuccess")
test_parseSuccess_prefix(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected)226     public void test_parseSuccess_prefix(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected) {
227         builder.appendZoneId();
228         pos.setIndex(3);
229         String prefixText = "XXX" + text;
230         TemporalAccessor parsed = builder.toFormatter().parseUnresolved(prefixText, pos);
231         assertEquals(pos.getErrorIndex(), expectedErrorIndex >= 0  ? expectedErrorIndex + 3 : expectedErrorIndex, "Incorrect error index parsing: " + prefixText);
232         assertEquals(pos.getIndex(), expectedIndex + 3, "Incorrect index parsing: " + prefixText);
233         if (expected != null) {
234             assertEquals(parsed.query(TemporalQueries.zoneId()), expected, "Incorrect zoneId parsing: " + prefixText);
235             assertEquals(parsed.query(TemporalQueries.offset()), null, "Incorrect offset parsing: " + prefixText);
236             assertEquals(parsed.query(TemporalQueries.zone()), expected, "Incorrect zone parsing: " + prefixText);
237         } else {
238             assertEquals(parsed, null);
239         }
240     }
241 
242     @Test(dataProvider="parseSuccess")
test_parseSuccess_suffix(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected)243     public void test_parseSuccess_suffix(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected) {
244         builder.appendZoneId();
245         String suffixText = text + "XXX";
246         TemporalAccessor parsed = builder.toFormatter().parseUnresolved(suffixText, pos);
247         assertEquals(pos.getErrorIndex(), expectedErrorIndex, "Incorrect error index parsing: " + suffixText);
248         assertEquals(pos.getIndex(), expectedIndex, "Incorrect index parsing: " + suffixText);
249         if (expected != null) {
250             assertEquals(parsed.query(TemporalQueries.zoneId()), expected, "Incorrect zoneId parsing: " + suffixText);
251             assertEquals(parsed.query(TemporalQueries.offset()), null, "Incorrect offset parsing: " + suffixText);
252             assertEquals(parsed.query(TemporalQueries.zone()), expected, "Incorrect zone parsing: " + suffixText);
253         } else {
254             assertEquals(parsed, null);
255         }
256     }
257 
258     @Test(dataProvider="parseSuccess")
test_parseSuccess_caseSensitive(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected)259     public void test_parseSuccess_caseSensitive(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected) {
260         builder.parseCaseSensitive().appendZoneId();
261         String lcText = text.toLowerCase(Locale.ENGLISH);
262         TemporalAccessor parsed = builder.toFormatter().parseUnresolved(lcText, pos);
263         if (text.matches("[^A-Z]*[A-Z].*")) {  // if input has letters
264             assertEquals(pos.getErrorIndex() >= 0, true);
265             assertEquals(pos.getIndex(), 0);
266             assertEquals(parsed, null);
267         } else {
268             // case sensitive made no difference
269             assertEquals(pos.getIndex(), expectedIndex, "Incorrect index parsing: " + lcText);
270             assertEquals(pos.getErrorIndex(), expectedErrorIndex, "Incorrect error index parsing: " + lcText);
271             if (expected != null) {
272                 assertEquals(parsed.query(TemporalQueries.zoneId()), expected);
273                 assertEquals(parsed.query(TemporalQueries.offset()), null);
274                 assertEquals(parsed.query(TemporalQueries.zone()), expected);
275             } else {
276                 assertEquals(parsed, null);
277             }
278         }
279     }
280 
281     @Test(dataProvider="parseSuccess")
test_parseSuccess_caseInsensitive(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected)282     public void test_parseSuccess_caseInsensitive(String text, int expectedIndex, int expectedErrorIndex, ZoneId expected) {
283         builder.parseCaseInsensitive().appendZoneId();
284         String lcText = text.toLowerCase(Locale.ENGLISH);
285         TemporalAccessor parsed = builder.toFormatter().parseUnresolved(lcText, pos);
286         assertEquals(pos.getErrorIndex(), expectedErrorIndex, "Incorrect error index parsing: " + lcText);
287         assertEquals(pos.getIndex(), expectedIndex, "Incorrect index parsing: " + lcText);
288         if (expected != null) {
289             ZoneId zid = parsed.query(TemporalQueries.zoneId());
290             assertEquals(parsed.query(TemporalQueries.zoneId()), expected, "Incorrect zoneId parsing: " + lcText);
291             assertEquals(parsed.query(TemporalQueries.offset()), null, "Incorrect offset parsing: " + lcText);
292             assertEquals(parsed.query(TemporalQueries.zone()), expected, "Incorrect zone parsing: " + lcText);
293         } else {
294             assertEquals(parsed, null);
295         }
296     }
297 
298 }
299