1 /*
  2  * Copyright (c) 2001, 2023, 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 4322313 4833268 6302990 6304305
 27  * @summary Make sure that new implementation for
 28  * SimpleDateFormat.parse('z' or 'Z') and format('z' or 'Z') work correctly.
 29  * @run junit Bug4322313
 30  */
 31 
 32 import java.io.*;
 33 import java.text.*;
 34 import java.util.*;
 35 
 36 import org.junit.jupiter.api.Test;
 37 
 38 import static org.junit.jupiter.api.Assertions.fail;
 39 
 40 public class Bug4322313 {
 41 
 42     @Test
 43     public void Test4322313() {
 44         Locale savedLocale = Locale.getDefault();
 45         TimeZone savedTimeZone = TimeZone.getDefault();
 46         boolean err = false;
 47         long mpm = 60 * 1000;   /* Milliseconds per a minute */
 48 
 49         Locale[] locs = {Locale.US, Locale.JAPAN, Locale.UK, Locale.of("ar")};
 50 
 51         String[] formats = {
 52             "z",
 53             "Z",
 54         };
 55 
 56         Object[][] valids = {
 57           /* given ID      offset                format('z'), ('Z')    index */
 58             {"GMT+03:04",  -184L * mpm, "GMT+03:04", "+0304", 9},
 59             {"GMT+13:42",  -822L * mpm, "GMT+13:42", "+1342", 9},
 60             {"GMT+00:00",   0L,         "GMT+00:00", "+0000", 9},
 61             {"GMT+1:11",   -71L * mpm,  "GMT+01:11", "+0111", 8},
 62             {"GMT +13:42",  0L,         "GMT",       "+0000", 3},
 63             {" GMT",        0L,         "GMT",       "+0000", 4},
 64             {"+0304",      -184L * mpm, "GMT+03:04", "+0304", 5},
 65             {"+1342",      -822L * mpm, "GMT+13:42", "+1342", 5},
 66             {"+0000",       0L,         "GMT+00:00", "+0000", 5},
 67             {" +1342",     -822L * mpm, "GMT+13:42", "+1342", 6},
 68             /* ISO-LATIN-1 digits */
 69             {"GMT+\u0030\u0031:\u0032\u0033", -83L * mpm, "GMT+01:23", "+0123", 9},
 70 
 71            /* In fact, this test case is skipped because TimeZone class can't
 72             * recognize TimeZone IDs like "+00234" or "-00234".
 73             */
 74             {"+00234",     -23L * mpm, "GMT+00:23", "+0023", 5},
 75 
 76             {"GMT-03:04",  184L * mpm, "GMT-03:04", "-0304", 9},
 77             {"GMT-13:42",  822L * mpm, "GMT-13:42", "-1342", 9},
 78             {"GMT-00:00",  0L,         "GMT+00:00", "+0000", 9},
 79             {"GMT-1:11",   71L * mpm,  "GMT-01:11", "-0111", 8},
 80             {"GMT -13:42", 0L,         "GMT",       "+0000", 3},
 81             {"-0304",      184L * mpm, "GMT-03:04", "-0304", 5},
 82             {"-1342",      822L * mpm, "GMT-13:42", "-1342", 5},
 83             {" -1342",     822L * mpm, "GMT-13:42", "-1342", 6},
 84             /* ISO-LATIN-1 digits */
 85             {"GMT-\u0030\u0031:\u0032\u0033", 83L * mpm, "GMT-01:23", "-0123", 9},
 86            /* In fact, this test case is skipped because TimeZone class can't
 87             * recognize TimeZone IDs like "+00234" or "-00234".
 88             */
 89             {"-00234",     23L * mpm,  "GMT+00:23", "-0023", 5},
 90         };
 91 
 92         Object[][] invalids = {
 93           /* given ID       error index   */
 94             {"GMT+8",       5},
 95             {"GMT+18",      6},
 96             {"GMT+208",     6},
 97             {"GMT+0304",    6},
 98             {"GMT+42195",   5},
 99             {"GMT+5:8",     7},
100             {"GMT+23:60",   8},
101             {"GMT+11:1",    8},
102             {"GMT+24:13",   5},
103             {"GMT+421:950", 5},
104             {"GMT+0a:0A",   5},
105             {"GMT+ 13:42",  4},
106             {"GMT+13 :42",  6},
107             {"GMT+13: 42",  7},
108             {"GMT+-13:42",  4},
109             {"G M T",       0},
110             {"+8",          2},
111             {"+18",         3},
112             {"+208",        4},
113             {"+2360",       4},
114             {"+2413",       2},
115             {"+42195",      2},
116             {"+0AbC",       2},
117             {"+ 1342",      1},
118             {"+-1342",      1},
119             {"1342",        0},
120           /* Arabic-Indic digits */
121             {"GMT+\u0660\u0661:\u0662\u0663", 4},
122           /* Extended Arabic-Indic digits */
123             {"GMT+\u06f0\u06f1:\u06f2\u06f3", 4},
124           /* Devanagari digits */
125             {"GMT+\u0966\u0967:\u0968\u0969", 4},
126           /* Fullwidth digits */
127             {"GMT+\uFF10\uFF11:\uFF12\uFF13", 4},
128 
129             {"GMT-8",       5},
130             {"GMT-18",      6},
131             {"GMT-208",     6},
132             {"GMT-0304",    6},
133             {"GMT-42195",   5},
134             {"GMT-5:8",     7},
135             {"GMT-23:60",   8},
136             {"GMT-11:1",    8},
137             {"GMT-24:13",   5},
138             {"GMT-421:950", 5},
139             {"GMT-0a:0A",   5},
140             {"GMT- 13:42",  4},
141             {"GMT-13 :42",  6},
142             {"GMT-13: 42",  7},
143             {"GMT-+13:42",  4},
144             {"-8",          2},
145             {"-18",         3},
146             {"-208",        4},
147             {"-2360",       4},
148             {"-2413",       2},
149             {"-42195",      2},
150             {"-0AbC",       2},
151             {"- 1342",      1},
152             {"--1342",      1},
153             {"-802",        2},
154           /* Arabic-Indic digits */
155             {"GMT-\u0660\u0661:\u0662\u0663", 4},
156           /* Extended Arabic-Indic digits */
157             {"GMT-\u06f0\u06f1:\u06f2\u06f3", 4},
158           /* Devanagari digits */
159             {"GMT-\u0966\u0967:\u0968\u0969", 4},
160           /* Fullwidth digits */
161             {"GMT-\uFF10\uFF11:\uFF12\uFF13", 4},
162         };
163 
164         try {
165             for (int i=0; i < locs.length; i++) {
166                 Locale locale = locs[i];
167                 Locale.setDefault(locale);
168 
169                 for (int j=0; j < formats.length; j++) {
170                     TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
171                     SimpleDateFormat sdf = new SimpleDateFormat(formats[j]);
172                     Date date;
173 
174                     /* Okay case */
175                     for (int k=0; k < valids.length; k++) {
176                         ParsePosition pos = new ParsePosition(0);
177                         try {
178                             date = sdf.parse((String)valids[k][0], pos);
179                         }
180                         catch (Exception e) {
181                             err = true;
182                             System.err.println("\tParse  Error [Locale=" +
183                                 locale + ", " + formats[j] +
184                                 "/\"" + valids[k][0] +
185                                 "\"] Unexpected Exception occurred: " + e);
186                             continue;
187                         }
188 
189                         int offset = pos.getIndex();
190                         if (offset != ((Integer)valids[k][4]).intValue()) {
191                             err = true;
192                             System.err.println("\tParse  Error [Locale=" +
193                                 locale + ", " + formats[j] +
194                                 "/\"" + valids[k][0] +
195                                 "\"] invalid index: expected:" + valids[k][4] +
196                                 ", got:" + offset);
197                         }
198 
199                         if (date.getTime() != ((Long)valids[k][1]).longValue()) {
200                             err = true;
201                             System.err.println("\tParse  Error [Locale=" +
202                                 locale + ", " + formats[j] +
203                                 "/\"" + valids[k][0] +
204                                 "\"] expected:" + valids[k][1] +
205                                 ", got:" + date.getTime() + ", " + date);
206                         } else {
207 /*
208                             System.out.println("\tParse  Okay  [Locale=" +
209                                 locale) + ", " + formats[j] +
210                                 "/\"" + valids[k][0] +
211                                 "\"] expected:" + valids[k][1] +
212                                 ", got:" + date.getTime() + ", " + date);
213 */
214 
215                             try {
216                                 date = sdf.parse((String)valids[k][0]);
217                             }
218                             catch (Exception e) {
219                                 err = true;
220                                 System.err.println("\tParse  Error [Locale=" +
221                                     locale + ", " + formats[j] +
222                                     "/\"" + valids[k][0] +
223                                     "\"] Unexpected Exception occurred: " + e);
224                                 continue;
225                             }
226 
227                             /* Since TimeZone.getTimeZone() don't treat
228                              * "+00234" or "-00234" as a valid ID, skips.
229                              */
230                             if (((String)valids[k][0]).length() == 6) {
231                                 continue;
232                             }
233 
234                             /* Since TimeZone.getTimeZone() don't recognize
235                              * +hhmm/-hhmm format, add "GMT" as prefix.
236                              */
237                             sdf.setTimeZone(TimeZone.getTimeZone(
238                                 (((((String)valids[k][0]).charAt(0) != 'G') ?
239                                 "GMT" : "") + valids[k][0])));
240                             StringBuffer s = new StringBuffer();
241                             sdf.format(date, s, new FieldPosition(0));
242                             sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
243 
244                             String got = s.toString();
245                             String expected = (String)valids[k][2+j];
246                             if (!got.equals(expected) &&
247                                 // special case to allow the difference between
248                                 // DateFormatSymbols.getZoneStrings() and
249                                 // TimeZone.getDisplayName() for "GMT+-00:00"
250                                 !(got.equals("GMT-00:00") &&
251                                   expected.equals("GMT+00:00"))) {
252                                 err = true;
253                                 System.err.println("\tFormat Error [Locale=" +
254                                     locale + ", " +
255                                     formats[j] + "/\"" + valids[k][0] +
256                                     "\"] expected:" + valids[k][2+j] +
257                                     ", got:" + s + ", " + date);
258                             } else {
259 /*
260                                 System.out.println("\tFormat Okay  [Locale=" +
261                                     locale + ", " +
262                                     formats[j] + "/\"" + valids[k][0] +
263                                     "\"] expected:" + valids[k][2+j] +
264                                     ", got:" + s + ", " + date);
265 */
266                             }
267                         }
268                     }
269 
270                     /* Error case 1
271                      *   using SimpleDateFormat.parse(String, ParsePosition)
272                      */
273                     for (int k=0; k < invalids.length; k++) {
274                         ParsePosition pos = new ParsePosition(0);
275                         try {
276                             date = sdf.parse((String)invalids[k][0], pos);
277                             if (date != null) {
278                                 err = true;
279                                 System.err.println("\tParse  Error [Locale=" +
280                                     locale + ", " + formats[j] +
281                                     "/\"" + invalids[k][0] +
282                                     "\"] expected:null , got:" + date);
283                             }
284                             int offset = pos.getErrorIndex();
285                             if (offset != ((Integer)invalids[k][1]).intValue()) {
286                                 err = true;
287                                 System.err.println("\tParse  Error [Locale=" +
288                                     locale + ", " + formats[j] +
289                                     "/\"" + invalids[k][0] +
290                                     "\"] incorrect offset. expected:" +
291                                     invalids[k][1] + ", got: " + offset);
292                             } else {
293 /*
294                                 System.out.println("\tParse  Okay  [Locale=" +
295                                     locale + ", " + formats[j] +
296                                     "/\"" + invalids[k][0] +
297                                     "\"] correct offset: " + offset);
298 */
299                             }
300                         }
301                         catch (Exception e) {
302                             err = true;
303                             System.err.println("\tParse  Error [Locale=" +
304                                 locale + ", " + formats[j] +
305                                 "/\"" + invalids[k][0] +
306                                 "\"] Unexpected Exception occurred: " + e);
307                         }
308                     }
309 
310                     /* Error case 2
311                      *   using DateFormat.parse(String)
312                      */
313                     boolean correctParseException = false;
314                     for (int k=0; k < invalids.length; k++) {
315                         try {
316                             date = sdf.parse((String)invalids[k][0]);
317                         }
318                         catch (ParseException e) {
319                             correctParseException = true;
320                             int offset = e.getErrorOffset();
321                             if (offset != ((Integer)invalids[k][1]).intValue()) {
322                                 err = true;
323                                 System.err.println("\tParse  Error [Locale=" +
324                                     locale + ", " + formats[j] +
325                                     "/\"" + invalids[k][0] +
326                                     "\"] Expected exception occurred with an incorrect offset. expected:" +
327                                     invalids[k][1] + ", got: " + offset);
328                             } else {
329 /*
330                                 System.out.println("\tParse  Okay  [Locale=" +
331                                     locale + ", " + formats[j] +
332                                     "/\"" + invalids[k][0] +
333                                     "\"] Expected exception occurred with an correct offset: "
334                                     + offset);
335 */
336                             }
337                         }
338                         catch (Exception e) {
339                             err = true;
340                             System.err.println("\tParse  Error [Locale=" +
341                                 locale + ", " + formats[j] +
342                                 "/\"" + invalids[k][0] +
343                                 "\"] Invalid exception occurred: " + e);
344                         }
345                         finally {
346                             if (!correctParseException) {
347                                 err = true;
348                                 System.err.println("\tParse  Error: [Locale=" +
349                                     locale + ", " + formats[j] +
350                                     "/\"" + invalids[k][0] +
351                                     "\"] Expected exception didn't occur.");
352                             }
353                         }
354                     }
355                 }
356             }
357         }
358         finally {
359             Locale.setDefault(savedLocale);
360             TimeZone.setDefault(savedTimeZone);
361             if (err) {
362                 fail("SimpleDateFormat.parse()/format() test failed");
363             }
364         }
365     }
366 
367 }