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