1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.text; 19 20 import java.io.IOException; 21 import java.io.ObjectInputStream; 22 import java.io.ObjectOutputStream; 23 import java.io.Serializable; 24 import java.util.Arrays; 25 import java.util.Locale; 26 import libcore.icu.ICU; 27 import libcore.icu.LocaleData; 28 import libcore.icu.TimeZones; 29 30 /** 31 * Encapsulates localized date-time formatting data, such as the names of the 32 * months, the names of the days of the week, and the time zone data. 33 * {@code DateFormat} and {@code SimpleDateFormat} both use 34 * {@code DateFormatSymbols} to encapsulate this information. 35 * 36 * <p>Typically you shouldn't use {@code DateFormatSymbols} directly. Rather, you 37 * are encouraged to create a date/time formatter with the {@code DateFormat} 38 * class's factory methods: {@code getTimeInstance}, {@code getDateInstance}, 39 * or {@code getDateTimeInstance}. These methods automatically create a 40 * {@code DateFormatSymbols} for the formatter so that you don't have to. After 41 * the formatter is created, you may modify its format pattern using the 42 * {@code setPattern} method. For more information about creating formatters 43 * using {@code DateFormat}'s factory methods, see {@link DateFormat}. 44 * 45 * <p>Direct use of {@code DateFormatSymbols} is likely to be less efficient 46 * because the implementation cannot make assumptions about user-supplied/user-modifiable data 47 * to the same extent that it can with its own built-in data. 48 * 49 * @see DateFormat 50 * @see SimpleDateFormat 51 */ 52 public class DateFormatSymbols implements Serializable, Cloneable { 53 54 private static final long serialVersionUID = -5987973545549424702L; 55 56 private String localPatternChars; 57 58 String[] ampms, eras, months, shortMonths, shortWeekdays, weekdays; 59 60 // These are used to implement ICU/Android extensions. 61 transient String[] longStandAloneMonths; 62 transient String[] shortStandAloneMonths; 63 transient String[] longStandAloneWeekdays; 64 transient String[] shortStandAloneWeekdays; 65 66 // Localized display names. 67 String[][] zoneStrings; 68 // Has the user called setZoneStrings? 69 transient boolean customZoneStrings; 70 71 /** 72 * Locale, necessary to lazily load time zone strings. We force the time 73 * zone names to load upon serialization, so this will never be needed 74 * post deserialization. 75 */ 76 transient final Locale locale; 77 78 /** 79 * Gets zone strings, initializing them if necessary. Does not create 80 * a defensive copy, so make sure you do so before exposing the returned 81 * arrays to clients. 82 */ internalZoneStrings()83 synchronized String[][] internalZoneStrings() { 84 if (zoneStrings == null) { 85 zoneStrings = TimeZones.getZoneStrings(locale); 86 } 87 return zoneStrings; 88 } 89 90 /** 91 * Constructs a new {@code DateFormatSymbols} instance containing the 92 * symbols for the user's default locale. 93 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 94 */ DateFormatSymbols()95 public DateFormatSymbols() { 96 this(Locale.getDefault()); 97 } 98 99 /** 100 * Constructs a new {@code DateFormatSymbols} instance containing the 101 * symbols for the specified locale. 102 * 103 * @param locale 104 * the locale. 105 */ DateFormatSymbols(Locale locale)106 public DateFormatSymbols(Locale locale) { 107 this.locale = locale; 108 this.localPatternChars = SimpleDateFormat.PATTERN_CHARS; 109 LocaleData localeData = LocaleData.get(locale); 110 this.ampms = localeData.amPm; 111 this.eras = localeData.eras; 112 this.months = localeData.longMonthNames; 113 this.shortMonths = localeData.shortMonthNames; 114 this.weekdays = localeData.longWeekdayNames; 115 this.shortWeekdays = localeData.shortWeekdayNames; 116 117 // ICU/Android extensions. 118 this.longStandAloneMonths = localeData.longStandAloneMonthNames; 119 this.shortStandAloneMonths = localeData.shortStandAloneMonthNames; 120 this.longStandAloneWeekdays = localeData.longStandAloneWeekdayNames; 121 this.shortStandAloneWeekdays = localeData.shortStandAloneWeekdayNames; 122 } 123 124 /** 125 * Returns a new {@code DateFormatSymbols} instance for the user's default locale. 126 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 127 * 128 * @return an instance of {@code DateFormatSymbols} 129 * @since 1.6 130 */ getInstance()131 public static final DateFormatSymbols getInstance() { 132 return getInstance(Locale.getDefault()); 133 } 134 135 /** 136 * Returns a new {@code DateFormatSymbols} for the given locale. 137 * 138 * @param locale the locale 139 * @return an instance of {@code DateFormatSymbols} 140 * @throws NullPointerException if {@code locale == null} 141 * @since 1.6 142 */ getInstance(Locale locale)143 public static final DateFormatSymbols getInstance(Locale locale) { 144 if (locale == null) { 145 throw new NullPointerException(); 146 } 147 return new DateFormatSymbols(locale); 148 } 149 150 /** 151 * Returns an array of locales for which custom {@code DateFormatSymbols} instances 152 * are available. 153 * <p>Note that Android does not support user-supplied locale service providers. 154 * @since 1.6 155 */ getAvailableLocales()156 public static Locale[] getAvailableLocales() { 157 return ICU.getAvailableDateFormatSymbolsLocales(); 158 } 159 readObject(ObjectInputStream ois)160 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 161 ois.defaultReadObject(); 162 163 // The RI doesn't have these fields, so we'll have to fall back and do the best we can. 164 longStandAloneMonths = months; 165 shortStandAloneMonths = shortMonths; 166 longStandAloneWeekdays = weekdays; 167 shortStandAloneWeekdays = shortWeekdays; 168 } 169 writeObject(ObjectOutputStream oos)170 private void writeObject(ObjectOutputStream oos) throws IOException { 171 internalZoneStrings(); 172 oos.defaultWriteObject(); 173 } 174 175 @Override clone()176 public Object clone() { 177 try { 178 return super.clone(); 179 } catch (CloneNotSupportedException e) { 180 throw new AssertionError(); 181 } 182 } 183 184 /** 185 * Compares this object with the specified object and indicates if they are 186 * equal. 187 * 188 * @param object 189 * the object to compare with this object. 190 * @return {@code true} if {@code object} is an instance of 191 * {@code DateFormatSymbols} and has the same symbols as this 192 * object, {@code false} otherwise. 193 * @see #hashCode 194 */ 195 @Override equals(Object object)196 public boolean equals(Object object) { 197 if (this == object) { 198 return true; 199 } 200 if (!(object instanceof DateFormatSymbols)) { 201 return false; 202 } 203 DateFormatSymbols rhs = (DateFormatSymbols) object; 204 return localPatternChars.equals(rhs.localPatternChars) && 205 Arrays.equals(ampms, rhs.ampms) && 206 Arrays.equals(eras, rhs.eras) && 207 Arrays.equals(months, rhs.months) && 208 Arrays.equals(shortMonths, rhs.shortMonths) && 209 Arrays.equals(shortWeekdays, rhs.shortWeekdays) && 210 Arrays.equals(weekdays, rhs.weekdays) && 211 timeZoneStringsEqual(this, rhs); 212 } 213 timeZoneStringsEqual(DateFormatSymbols lhs, DateFormatSymbols rhs)214 private static boolean timeZoneStringsEqual(DateFormatSymbols lhs, DateFormatSymbols rhs) { 215 // Quick check that may keep us from having to load the zone strings. 216 // Note that different locales may have the same strings, so the opposite check isn't valid. 217 if (lhs.zoneStrings == null && rhs.zoneStrings == null && lhs.locale.equals(rhs.locale)) { 218 return true; 219 } 220 // Make sure zone strings are loaded, then check. 221 return Arrays.deepEquals(lhs.internalZoneStrings(), rhs.internalZoneStrings()); 222 } 223 224 @Override toString()225 public String toString() { 226 // 'locale' isn't part of the externally-visible state. 227 // 'zoneStrings' is so large, we just print a representative value. 228 return getClass().getName() + 229 "[amPmStrings=" + Arrays.toString(ampms) + 230 ",customZoneStrings=" + customZoneStrings + 231 ",eras=" + Arrays.toString(eras) + 232 ",localPatternChars=" + localPatternChars + 233 ",months=" + Arrays.toString(months) + 234 ",shortMonths=" + Arrays.toString(shortMonths) + 235 ",shortWeekdays=" + Arrays.toString(shortWeekdays) + 236 ",weekdays=" + Arrays.toString(weekdays) + 237 ",zoneStrings=[" + Arrays.toString(internalZoneStrings()[0]) + "...]" + 238 "]"; 239 } 240 241 /** 242 * Returns the array of strings which represent AM and PM. Use the 243 * {@link java.util.Calendar} constants {@code Calendar.AM} and 244 * {@code Calendar.PM} as indices for the array. 245 * 246 * @return an array of strings. 247 */ getAmPmStrings()248 public String[] getAmPmStrings() { 249 return ampms.clone(); 250 } 251 252 /** 253 * Returns the array of strings which represent BC and AD. Use the 254 * {@link java.util.Calendar} constants {@code GregorianCalendar.BC} and 255 * {@code GregorianCalendar.AD} as indices for the array. 256 * 257 * @return an array of strings. 258 */ getEras()259 public String[] getEras() { 260 return eras.clone(); 261 } 262 263 /** 264 * Returns the pattern characters used by {@link SimpleDateFormat} to 265 * specify date and time fields. 266 * 267 * @return a string containing the pattern characters. 268 */ getLocalPatternChars()269 public String getLocalPatternChars() { 270 return localPatternChars; 271 } 272 273 /** 274 * Returns the array of strings containing the full names of the months. Use 275 * the {@link java.util.Calendar} constants {@code Calendar.JANUARY} etc. as 276 * indices for the array. 277 * 278 * @return an array of strings. 279 */ getMonths()280 public String[] getMonths() { 281 return months.clone(); 282 } 283 284 /** 285 * Returns the array of strings containing the abbreviated names of the 286 * months. Use the {@link java.util.Calendar} constants 287 * {@code Calendar.JANUARY} etc. as indices for the array. 288 * 289 * @return an array of strings. 290 */ getShortMonths()291 public String[] getShortMonths() { 292 return shortMonths.clone(); 293 } 294 295 /** 296 * Returns the array of strings containing the abbreviated names of the days 297 * of the week. Use the {@link java.util.Calendar} constants 298 * {@code Calendar.SUNDAY} etc. as indices for the array. 299 * 300 * @return an array of strings. 301 */ getShortWeekdays()302 public String[] getShortWeekdays() { 303 return shortWeekdays.clone(); 304 } 305 306 /** 307 * Returns the array of strings containing the full names of the days of the 308 * week. Use the {@link java.util.Calendar} constants 309 * {@code Calendar.SUNDAY} etc. as indices for the array. 310 * 311 * @return an array of strings. 312 */ getWeekdays()313 public String[] getWeekdays() { 314 return weekdays.clone(); 315 } 316 317 /** 318 * Returns the two-dimensional array of strings containing localized names for time zones. 319 * Each row is an array of five strings: 320 * <ul> 321 * <li>The time zone ID, for example "America/Los_Angeles". 322 * This is not localized, and is used as a key into the table. 323 * <li>The long display name, for example "Pacific Standard Time". 324 * <li>The short display name, for example "PST". 325 * <li>The long display name for DST, for example "Pacific Daylight Time". 326 * This is the non-DST long name for zones that have never had DST, for 327 * example "Central Standard Time" for "Canada/Saskatchewan". 328 * <li>The short display name for DST, for example "PDT". This is the 329 * non-DST short name for zones that have never had DST, for example 330 * "CST" for "Canada/Saskatchewan". 331 * </ul> 332 */ getZoneStrings()333 public String[][] getZoneStrings() { 334 return clone2dStringArray(internalZoneStrings()); 335 } 336 clone2dStringArray(String[][] array)337 private static String[][] clone2dStringArray(String[][] array) { 338 String[][] result = new String[array.length][]; 339 for (int i = 0; i < array.length; ++i) { 340 result[i] = array[i].clone(); 341 } 342 return result; 343 } 344 345 @Override hashCode()346 public int hashCode() { 347 String[][] zoneStrings = internalZoneStrings(); 348 int hashCode; 349 hashCode = localPatternChars.hashCode(); 350 for (String element : ampms) { 351 hashCode += element.hashCode(); 352 } 353 for (String element : eras) { 354 hashCode += element.hashCode(); 355 } 356 for (String element : months) { 357 hashCode += element.hashCode(); 358 } 359 for (String element : shortMonths) { 360 hashCode += element.hashCode(); 361 } 362 for (String element : shortWeekdays) { 363 hashCode += element.hashCode(); 364 } 365 for (String element : weekdays) { 366 hashCode += element.hashCode(); 367 } 368 for (String[] element : zoneStrings) { 369 for (int j = 0; j < element.length; j++) { 370 if (element[j] != null) { 371 hashCode += element[j].hashCode(); 372 } 373 } 374 } 375 return hashCode; 376 } 377 378 /** 379 * Sets the array of strings which represent AM and PM. Use the 380 * {@link java.util.Calendar} constants {@code Calendar.AM} and 381 * {@code Calendar.PM} as indices for the array. 382 * 383 * @param data 384 * the array of strings for AM and PM. 385 */ setAmPmStrings(String[] data)386 public void setAmPmStrings(String[] data) { 387 ampms = data.clone(); 388 } 389 390 /** 391 * Sets the array of Strings which represent BC and AD. Use the 392 * {@link java.util.Calendar} constants {@code GregorianCalendar.BC} and 393 * {@code GregorianCalendar.AD} as indices for the array. 394 * 395 * @param data 396 * the array of strings for BC and AD. 397 */ setEras(String[] data)398 public void setEras(String[] data) { 399 eras = data.clone(); 400 } 401 402 /** 403 * Sets the pattern characters used by {@link SimpleDateFormat} to specify 404 * date and time fields. 405 * 406 * @param data 407 * the string containing the pattern characters. 408 * @throws NullPointerException 409 * if {@code data} is null 410 */ setLocalPatternChars(String data)411 public void setLocalPatternChars(String data) { 412 if (data == null) { 413 throw new NullPointerException(); 414 } 415 localPatternChars = data; 416 } 417 418 /** 419 * Sets the array of strings containing the full names of the months. Use 420 * the {@link java.util.Calendar} constants {@code Calendar.JANUARY} etc. as 421 * indices for the array. 422 * 423 * @param data 424 * the array of strings. 425 */ setMonths(String[] data)426 public void setMonths(String[] data) { 427 months = data.clone(); 428 } 429 430 /** 431 * Sets the array of strings containing the abbreviated names of the months. 432 * Use the {@link java.util.Calendar} constants {@code Calendar.JANUARY} 433 * etc. as indices for the array. 434 * 435 * @param data 436 * the array of strings. 437 */ setShortMonths(String[] data)438 public void setShortMonths(String[] data) { 439 shortMonths = data.clone(); 440 } 441 442 /** 443 * Sets the array of strings containing the abbreviated names of the days of 444 * the week. Use the {@link java.util.Calendar} constants 445 * {@code Calendar.SUNDAY} etc. as indices for the array. 446 * 447 * @param data 448 * the array of strings. 449 */ setShortWeekdays(String[] data)450 public void setShortWeekdays(String[] data) { 451 shortWeekdays = data.clone(); 452 } 453 454 /** 455 * Sets the array of strings containing the full names of the days of the 456 * week. Use the {@link java.util.Calendar} constants 457 * {@code Calendar.SUNDAY} etc. as indices for the array. 458 * 459 * @param data 460 * the array of strings. 461 */ setWeekdays(String[] data)462 public void setWeekdays(String[] data) { 463 weekdays = data.clone(); 464 } 465 466 /** 467 * Sets the two-dimensional array of strings containing localized names for time zones. 468 * See {@link #getZoneStrings} for details. 469 * @throws IllegalArgumentException if any row has fewer than 5 elements. 470 * @throws NullPointerException if {@code zoneStrings == null}. 471 */ setZoneStrings(String[][] zoneStrings)472 public void setZoneStrings(String[][] zoneStrings) { 473 if (zoneStrings == null) { 474 throw new NullPointerException(); 475 } 476 for (String[] row : zoneStrings) { 477 if (row.length < 5) { 478 throw new IllegalArgumentException(Arrays.toString(row) + ".length < 5"); 479 } 480 } 481 this.zoneStrings = clone2dStringArray(zoneStrings); 482 this.customZoneStrings = true; 483 } 484 } 485