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