1 /*
   2  * Copyright (c) 2019, 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 8218948
  27  * @summary TCK tests that check the time zone names between DFS.getZoneStrings()
  28  *      and SDF.format("z*")
  29  * @run main SDFTCKZoneNamesTest
  30  */
  31 import java.text.*;
  32 import java.util.Calendar;
  33 import java.util.Date;
  34 import java.util.List;
  35 import java.util.Locale;
  36 import java.util.TimeZone;
  37 
  38 public class SDFTCKZoneNamesTest {
  39 
  40     StringBuffer myFormat(Date date, SimpleDateFormat sdf) {
  41         String pattern = sdf.toPattern();
  42         StringBuffer toAppendTo = new StringBuffer("");
  43         boolean inQuote = false;
  44         char prevCh = 0;
  45         char ch;
  46         int count = 0;
  47         for (int i = 0; i < pattern.length(); i++) {
  48             ch = pattern.charAt(i);
  49             if (inQuote) {
  50                 if (ch == '\'') {
  51                     inQuote = false;
  52                     if (count == 0) toAppendTo.append(ch);
  53                     else count = 0;
  54                 } else {
  55                     toAppendTo.append(ch);
  56                     count++;
  57                 }
  58             } else { // not inQuote
  59                 if (ch == '\'') {
  60                     inQuote = true;
  61                     if (count > 0) {
  62                         toAppendTo.append(subFormat(prevCh, count, date, sdf));
  63                         count = 0;
  64                         prevCh = 0;
  65                     }
  66                 } else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z') {
  67                     if (ch != prevCh && count > 0) {
  68                         toAppendTo.append(subFormat(prevCh, count, date, sdf));
  69                         prevCh = ch;
  70                         count = 1;
  71                     } else {
  72                         if (ch != prevCh) prevCh = ch;
  73                         count++;
  74                     }
  75                 } else if (count > 0) {
  76                     toAppendTo.append(subFormat(prevCh, count, date, sdf));
  77                     toAppendTo.append(ch);
  78                     prevCh = 0;
  79                     count = 0;
  80                 } else toAppendTo.append(ch);
  81             }
  82         }
  83         if (count > 0) {
  84             toAppendTo.append(subFormat(prevCh, count, date, sdf));
  85         }
  86         return toAppendTo;
  87     }
  88 
  89     private String subFormat(char ch, int count, Date date, SimpleDateFormat sdf)
  90             throws IllegalArgumentException {
  91         int value = 0;
  92         int patternCharIndex = -1;
  93         int maxIntCount = 10;
  94         String current = "";
  95         DateFormatSymbols formatData = sdf.getDateFormatSymbols();
  96         Calendar calendar = sdf.getCalendar();
  97         calendar.setTime(date);
  98         NumberFormat nf = sdf.getNumberFormat();
  99         nf.setGroupingUsed(false);
 100 
 101         if ((patternCharIndex = "GyMdkHmsSEDFwWahKz".indexOf(ch)) == -1)
 102             throw new IllegalArgumentException("Illegal pattern character " +
 103                     "'" + ch + "'");
 104         switch (patternCharIndex) {
 105             case 0: // 'G' - ERA
 106                 value = calendar.get(Calendar.ERA);
 107                 current = formatData.getEras()[value];
 108                 break;
 109             case 1: // 'y' - YEAR
 110                 value = calendar.get(Calendar.YEAR);
 111 
 112                 if (count == 2) {
 113                     // For formatting, if the number of pattern letters is 2,
 114                     // the year is truncated to 2 digits;
 115                     current = zeroPaddingNumber(value, 2, 2, nf);
 116                 } else {
 117                     // otherwise it is interpreted as a number.
 118                     current = zeroPaddingNumber(value, count, maxIntCount, nf);
 119                 }
 120 
 121                 break;
 122             case 2: // 'M' - MONTH
 123                 value = calendar.get(Calendar.MONTH);
 124                 if (count >= 4)
 125                     // DateFormatSymbols::getMonths spec: "If the language requires different forms for formatting
 126                     // and stand-alone usages, this method returns month names in the formatting form."
 127                     // Because of that only formatting cases patterns may be tested. Like, "MMMM yyyy". Wrong
 128                     // pattern: "MMMM".
 129                     current = formatData.getMonths()[value];
 130                 else if (count == 3)
 131                     // DateFormatSymbols::getShortMonths spec: "If the language requires different forms for formatting
 132                     // and stand-alone usages, This method returns short month names in the formatting form."
 133                     // Because of that only formatting cases patterns may be tested. Like, "MMM yyyy". Wrong pattern:
 134                     // "MMM".
 135                     current = formatData.getShortMonths()[value];
 136                 else
 137                     current = zeroPaddingNumber(value + 1, count, maxIntCount, nf);
 138                 break;
 139             case 3: // 'd' - DATE
 140                 value = calendar.get(Calendar.DATE);
 141                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 142                 break;
 143             case 4: // 'k' - HOUR_OF_DAY: 1-based.  eg, 23:59 + 1 hour =>> 24:59
 144                 if ((value = calendar.get(Calendar.HOUR_OF_DAY)) == 0)
 145                     current = zeroPaddingNumber(
 146                             calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1,
 147                             count, maxIntCount, nf);
 148                 else
 149                     current = zeroPaddingNumber(value, count, maxIntCount, nf);
 150                 break;
 151             case 5: // 'H' - HOUR_OF_DAY:0-based.  eg, 23:59 + 1 hour =>> 00:59
 152                 value = calendar.get(Calendar.HOUR_OF_DAY);
 153                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 154                 break;
 155             case 6: // 'm' - MINUTE
 156                 value = calendar.get(Calendar.MINUTE);
 157                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 158                 break;
 159             case 7: // 's' - SECOND
 160                 value = calendar.get(Calendar.SECOND);
 161                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 162                 break;
 163             case 8: // 'S' - MILLISECOND
 164                 value = calendar.get(Calendar.MILLISECOND);
 165         /* 
 166         if (count > 3)
 167             value = value * (int) Math.pow(10, count - 3);
 168         else if (count == 2)
 169             value = (value + 5) / 10;
 170         else if (count == 1)
 171             value = (value + 50) / 100;
 172         */
 173                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 174                 break;
 175             case 9: // 'E' - DAY_OF_WEEK
 176                 value = calendar.get(Calendar.DAY_OF_WEEK);
 177                 if (count >= 4)
 178                     current = formatData.getWeekdays()[value];
 179                 else // count < 4, use abbreviated form if exists
 180                     current = formatData.getShortWeekdays()[value];
 181                 break;
 182             case 10:    // 'D' - DAY_OF_YEAR
 183                 value = calendar.get(Calendar.DAY_OF_YEAR);
 184                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 185                 break;
 186             case 11:   // 'F' - DAY_OF_WEEK_IN_MONTH
 187                 value = calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH);
 188                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 189                 break;
 190             case 12:    // 'w' - WEEK_OF_YEAR
 191                 value = calendar.get(Calendar.WEEK_OF_YEAR);
 192                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 193                 break;
 194             case 13:    // 'W' - WEEK_OF_MONTH
 195                 value = calendar.get(Calendar.WEEK_OF_MONTH);
 196                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 197                 break;
 198             case 14:    // 'a' - AM_PM
 199                 value = calendar.get(Calendar.AM_PM);
 200                 current = formatData.getAmPmStrings()[value];
 201                 break;
 202             case 15: // 'h' - HOUR:1-based.  eg, 11PM + 1 hour =>> 12 AM
 203                 if ((value = calendar.get(Calendar.HOUR)) == 0)
 204                     current = zeroPaddingNumber(
 205                             calendar.getLeastMaximum(Calendar.HOUR) + 1,
 206                             count, maxIntCount, nf);
 207                 else
 208                     current = zeroPaddingNumber(value, count, maxIntCount, nf);
 209                 break;
 210             case 16: // 'K' - HOUR: 0-based.  eg, 11PM + 1 hour =>> 0 AM
 211                 value = calendar.get(Calendar.HOUR);
 212                 current = zeroPaddingNumber(value, count, maxIntCount, nf);
 213                 break;
 214             case 17: // 'z' - ZONE_OFFSET
 215                 int zoneIndex = getZoneIndex(calendar.getTimeZone().getID(), formatData);
 216                 if (zoneIndex == -1) {
 217                     StringBuffer zoneString = new StringBuffer();
 218                     value = calendar.get(Calendar.ZONE_OFFSET)
 219                             + calendar.get(Calendar.DST_OFFSET);
 220                     if (value < 0) {
 221                         zoneString.append("GMT-");
 222                         value = -value; // suppress the '-' sign for text display.
 223                     } else
 224                         zoneString.append("GMT+");
 225                     zoneString.append(
 226                             zeroPaddingNumber((int) (value / (60 * 60 * 1000)), 2, 2, nf));
 227                     zoneString.append(':');
 228                     zoneString.append(
 229                             zeroPaddingNumber(
 230                                     (int) ((value % (60 * 60 * 1000)) / (60 * 1000)), 2, 2, nf));
 231                     current = zoneString.toString();
 232                 } else if (calendar.get(Calendar.DST_OFFSET) != 0) {
 233                     if (count >= 4)
 234                         current = formatData.getZoneStrings()[zoneIndex][3];
 235                     else
 236                         // count < 4, use abbreviated form if exists
 237                         current = formatData.getZoneStrings()[zoneIndex][4];
 238                 } else {
 239                     if (count >= 4)
 240                         current = formatData.getZoneStrings()[zoneIndex][1];
 241                     else
 242                         current = formatData.getZoneStrings()[zoneIndex][2];
 243                 }
 244                 break;
 245         }
 246 
 247         return current;
 248     }
 249 
 250 
 251     String zeroPaddingNumber(long value, int minDigits, int maxDigits,
 252                              NumberFormat nf) {
 253         nf.setMinimumIntegerDigits(minDigits);
 254         nf.setMaximumIntegerDigits(maxDigits);
 255         return nf.format(value);
 256     }
 257 
 258 
 259     int getZoneIndex(String ID, DateFormatSymbols dfs) {
 260         String[][] zoneStrings = dfs.getZoneStrings();
 261 
 262         for (int index = 0; index < zoneStrings.length; index++) {
 263             if (ID.equalsIgnoreCase(zoneStrings[index][0])) return index;
 264         }
 265         return -1;
 266     }
 267 
 268 
 269     final int second = 1000;
 270     final int minute = 60 * second;
 271     final int hour = 60 * minute;
 272     final int day = 24 * hour;
 273     final int month = 30 * day;
 274     final int year = 365 * day;
 275     final int someday = 30 * year + 3 * month + 19 * day + 5 * hour;
 276 
 277 
 278     /* standalone interface */
 279     public static void main(String argv[]) {
 280         Locale defaultLocale = Locale.getDefault();
 281         SDFTCKZoneNamesTest test = new SDFTCKZoneNamesTest();
 282 
 283         try {
 284             List.of(Locale.ROOT,
 285                     Locale.CHINA,
 286                     Locale.forLanguageTag("es-419"),
 287                     Locale.GERMANY,
 288                     Locale.forLanguageTag("hi-IN"),
 289                     Locale.JAPAN,
 290                     Locale.TAIWAN,
 291                     Locale.UK,
 292                     Locale.US,
 293                     Locale.forLanguageTag("uz-Cyrl-UZ"),
 294                     Locale.forLanguageTag("zh-SG"),
 295                     Locale.forLanguageTag("zh-HK"),
 296                     Locale.forLanguageTag("zh-MO")).stream()
 297                 .forEach(l -> {
 298                     System.out.printf("Testing locale: %s%n", l);
 299                     Locale.setDefault(l);
 300                     test.SimpleDateFormat0062();
 301                 });
 302         } finally {
 303             Locale.setDefault(defaultLocale);
 304         }
 305     }
 306 
 307 
 308     /**
 309      * Equivalence class partitioning
 310      * with state, input and output values orientation
 311      * for public StringBuffer format(Date date, StringBuffer result, FieldPosition fp),
 312      * <br><b>pre-conditions</b>: patterns: { "'s0mething'z mm::hh,yyyy zz",
 313      * "zzzz",
 314      * "z"} (each pattern contains letter for TIMEZONE_FIELD),
 315      * <br><b>date</b>: a Date object
 316      * <br><b>result</b>: a string
 317      * <br><b>fp</b>: a FieldPosition object with TIMEZONE_FIELD field
 318      * <br><b>output</b>: formatted date as expected.
 319      */
 320     public void SimpleDateFormat0062() {
 321         boolean passed = true;
 322         String patterns[] = {"'s0mething'z mm::hh,yyyy zz",
 323                 "zzzz",
 324                 "z"};
 325         SimpleDateFormat sdf = new SimpleDateFormat();
 326         Date date = new Date(1234567890);
 327         for (String[] tz : sdf.getDateFormatSymbols().getZoneStrings()) {
 328             sdf.setTimeZone(TimeZone.getTimeZone(tz[0]));
 329             for (int i = 0; i < patterns.length && passed; i++) {
 330                 StringBuffer result = new StringBuffer("qwerty");
 331                 FieldPosition fp = new FieldPosition(DateFormat.TIMEZONE_FIELD);
 332                 sdf.applyPattern(patterns[i]);
 333                 String expected = new
 334                         StringBuffer("qwerty").append(myFormat(date,
 335                         sdf)).toString();
 336                 String formatted = sdf.format(date, result, fp).toString();
 337 
 338                 if (!expected.equals(formatted)) {
 339                     System.out.println(
 340                             "method format(date, StringBuffer, FieldPosition) formats wrong");
 341                     System.out.println("  pattern: " + patterns[i]);
 342                     System.out.println("  time zone ID:   " + tz[0]);
 343                     System.out.println("  expected result:  " + expected);
 344                     System.out.println("  formatted result: " + formatted);
 345                     passed = false;
 346                 }
 347 
 348                 if (passed && !expected.equals(result.toString())) {
 349                     System.out.println(
 350                             "method format(Date date, StringBuffer toAppendTo, FieldPosition fp) toAppendTo is not " +
 351                                     "equal to output");
 352                     System.out.println("  pattern: " + patterns[i]);
 353                     System.out.println("  time zone ID:   " + tz[0]);
 354                     System.out.println("  toAppendTo   : " + result);
 355                     System.out.println("  formatted date: " + formatted);
 356                     passed = false;
 357                 }
 358             }
 359         }
 360         if(passed)
 361         {
 362             System.out.println("PASSED : OKAY");
 363         }else
 364         {
 365             throw new RuntimeException("FAILED");
 366         }
 367     }
 368 }