1/*
2 * Copyright (c) 2010, 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 * @test
26 * @bug 4919632
27 * @summary Unit test for ISO8601 time zone format support
28 */
29
30import java.text.*;
31import java.util.*;
32
33public class ISO8601ZoneTest {
34    static final Date TIMESTAMP = new Date(1283758039020L);
35
36    static final String[][] formatData = {
37        // time zone name, expected output at TIMESTAMP
38        { "America/Los_Angeles", "2010-09-06T00:27:19.020-07", },
39        { "America/Los_Angeles", "2010-09-06T00:27:19.020-0700", },
40        { "America/Los_Angeles", "2010-09-06T00:27:19.020-07:00", },
41        { "Australia/Sydney", "2010-09-06T17:27:19.020+10", },
42        { "Australia/Sydney", "2010-09-06T17:27:19.020+1000", },
43        { "Australia/Sydney", "2010-09-06T17:27:19.020+10:00", },
44        { "GMT-07:00", "2010-09-06T00:27:19.020-07", },
45        { "GMT-07:00", "2010-09-06T00:27:19.020-0700", },
46        { "GMT-07:00", "2010-09-06T00:27:19.020-07:00", },
47        { "UTC", "2010-09-06T07:27:19.020Z", },
48        { "UTC", "2010-09-06T07:27:19.020Z", },
49        { "UTC", "2010-09-06T07:27:19.020Z", },
50    };
51
52    static final String[] zones = {
53        "America/Los_Angeles", "Australia/Sydney", "GMT-07:00",
54        "UTC", "GMT+05:30", "GMT-01:23",
55    };
56
57    static final String[] isoZoneFormats = {
58        "yyyy-MM-dd'T'HH:mm:ss.SSSX",
59        "yyyy-MM-dd'T'HH:mm:ss.SSSXX",
60        "yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
61    };
62
63    // badData[][0] - format
64    // badData[][1] - (bad) text to be parsed
65    // badData[][2] - subtext at the end of which a parse error is detected
66    static final String[][] badData = {
67        { "X", "1", "1" },
68        { "X", "+1", "+1" },
69        { "X", "-2", "-2" },
70        { "X", "-24", "-2" },
71        { "X", "+24", "+2" },
72
73        { "XX", "9", "9" },
74        { "XX", "23", "2" },
75        { "XX", "234", "2" },
76        { "XX", "3456", "3" },
77        { "XX", "23456", "2" },
78        { "XX", "+1", "+1" },
79        { "XX", "-12", "-12" },
80        { "XX", "+123", "+123" },
81        { "XX", "-12:34", "-12" },
82        { "XX", "+12:34", "+12" },
83        { "XX", "-2423", "-2" },
84        { "XX", "+2423", "+2" },
85        { "XX", "-1260", "-126" },
86        { "XX", "+1260", "+126" },
87
88        { "XXX", "9", "9" },
89        { "XXX", "23", "2" },
90        { "XXX", "234", "2" },
91        { "XXX", "3456", "3" },
92        { "XXX", "23456", "2" },
93        { "XXX", "2:34", "2" },
94        { "XXX", "12:4", "1" },
95        { "XXX", "12:34", "1" },
96        { "XXX", "-1", "-1" },
97        { "XXX", "+1", "+1" },
98        { "XXX", "-12", "-12" },
99        { "XXX", "+12", "+12" },
100        { "XXX", "-123", "-12" },
101        { "XXX", "+123", "+12" },
102        { "XXX", "-1234", "-12" },
103        { "XXX", "+1234", "+12" },
104        { "XXX", "+24:23", "+2" },
105        { "XXX", "+12:60", "+12:6" },
106        { "XXX", "+1:23", "+1" },
107        { "XXX", "+12:3", "+12:3" },
108    };
109
110    static String[] badFormats = {
111        "XXXX", "XXXXX", "XXXXXX",
112    };
113
114    public static void main(String[] args) throws Exception {
115        TimeZone tz = TimeZone.getDefault();
116        Locale loc = Locale.getDefault();
117        Locale.setDefault(Locale.US);
118
119        try {
120            for (int i = 0; i < formatData.length; i++) {
121                TimeZone.setDefault(TimeZone.getTimeZone(formatData[i][0]));
122                formatTest(isoZoneFormats[i % isoZoneFormats.length],
123                           formatData[i][1]);
124            }
125
126            for (String zone : zones) {
127                TimeZone.setDefault(TimeZone.getTimeZone(zone));
128                for (String fmt : isoZoneFormats) {
129                    roundtripTest(fmt);
130                    SimpleDateFormat f = new SimpleDateFormat(fmt);
131                }
132
133            }
134
135            for (String[] d : badData) {
136                badDataParsing(d[0], d[1], d[2].length());
137            }
138
139            for (String fmt : badFormats) {
140                badFormat(fmt);
141            }
142        } finally {
143            TimeZone.setDefault(tz);
144            Locale.setDefault(loc);
145        }
146
147    }
148
149    static void formatTest(String fmt, String expected) throws Exception {
150        SimpleDateFormat sdf = new SimpleDateFormat(fmt);
151        String s = sdf.format(TIMESTAMP);
152        if (!expected.equals(s)) {
153            throw new RuntimeException("formatTest: got " + s
154                                       + ", expected " + expected);
155        }
156
157        Date d = sdf.parse(s);
158        if (d.getTime() != TIMESTAMP.getTime()) {
159            throw new RuntimeException("formatTest: parse(" + s
160                                       + "), got " + d.getTime()
161                                       + ", expected " + TIMESTAMP.getTime());
162        }
163
164        ParsePosition pos = new ParsePosition(0);
165        d = sdf.parse(s + "123", pos);
166        if (d.getTime() != TIMESTAMP.getTime()) {
167            throw new RuntimeException("formatTest: parse(" + s
168                                       + "), got " + d.getTime()
169                                       + ", expected " + TIMESTAMP.getTime());
170        }
171        if (pos.getIndex() != s.length()) {
172            throw new RuntimeException("formatTest: wrong resulting parse position: "
173                                       + pos.getIndex() + ", expected " + s.length());
174        }
175    }
176
177    static void roundtripTest(String fmt) throws Exception {
178        SimpleDateFormat sdf = new SimpleDateFormat(fmt);
179        Date date = new Date();
180
181        int fractionalHour = sdf.getTimeZone().getOffset(date.getTime());
182        fractionalHour %= 3600000; // fraction of hour
183
184        String s = sdf.format(date);
185        Date pd = sdf.parse(s);
186        long diffsInMillis = pd.getTime() - date.getTime();
187        if (diffsInMillis != 0) {
188            if (diffsInMillis != fractionalHour) {
189                throw new RuntimeException("fmt= " + fmt
190                                           + ", diff="+diffsInMillis
191                                           + ", fraction=" + fractionalHour);
192            }
193        }
194    }
195
196
197    static void badDataParsing(String fmt, String text, int expectedErrorIndex) {
198        SimpleDateFormat sdf = new SimpleDateFormat(fmt);
199        try {
200            sdf.parse(text);
201            throw new RuntimeException("didn't throw an exception: fmt=" + fmt
202                                       + ", text=" + text);
203        } catch (ParseException e) {
204            // OK
205        }
206
207        ParsePosition pos = new ParsePosition(0);
208        Date d = sdf.parse(text, pos);
209        int errorIndex = pos.getErrorIndex();
210        if (d != null || errorIndex != expectedErrorIndex) {
211            throw new RuntimeException("Bad error index=" + errorIndex
212                                       + ", expected=" + expectedErrorIndex
213                                       + ", fmt=" + fmt + ", text=" + text);
214        }
215    }
216
217    static void badFormat(String fmt) {
218        try {
219            SimpleDateFormat sdf = new SimpleDateFormat(fmt);
220            throw new RuntimeException("Constructor didn't throw an exception: fmt=" + fmt);
221        } catch (IllegalArgumentException e) {
222            // OK
223        }
224        try {
225            SimpleDateFormat sdf = new SimpleDateFormat();
226            sdf.applyPattern(fmt);
227            throw new RuntimeException("applyPattern didn't throw an exception: fmt=" + fmt);
228        } catch (IllegalArgumentException e) {
229            // OK
230        }
231    }
232}
233