blob: cb9fdacec15d002e85542bf9a29592da5ed00f9c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.text;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Locale;
import java.util.TimeZone;
import libcore.icu.ICU;
import libcore.icu.LocaleData;
import libcore.icu.TimeZoneNames;
/**
* Encapsulates localized date-time formatting data, such as the names of the
* months, the names of the days of the week, and the time zone data.
* {@code DateFormat} and {@code SimpleDateFormat} both use
* {@code DateFormatSymbols} to encapsulate this information.
*
* <p>Typically you shouldn't use {@code DateFormatSymbols} directly. Rather, you
* are encouraged to create a date/time formatter with the {@code DateFormat}
* class's factory methods: {@code getTimeInstance}, {@code getDateInstance},
* or {@code getDateTimeInstance}. These methods automatically create a
* {@code DateFormatSymbols} for the formatter so that you don't have to. After
* the formatter is created, you may modify its format pattern using the
* {@code setPattern} method. For more information about creating formatters
* using {@code DateFormat}'s factory methods, see {@link DateFormat}.
*
* <p>Direct use of {@code DateFormatSymbols} is likely to be less efficient
* because the implementation cannot make assumptions about user-supplied/user-modifiable data
* to the same extent that it can with its own built-in data.
*
* @see DateFormat
* @see SimpleDateFormat
*/
public class DateFormatSymbols implements Serializable, Cloneable {
private static final long serialVersionUID = -5987973545549424702L;
private String localPatternChars;
String[] ampms, eras, months, shortMonths, shortWeekdays, weekdays;
// This is used to implement parts of Unicode UTS #35 not historically supported.
transient LocaleData localeData;
// Localized display names.
String[][] zoneStrings;
// Has the user called setZoneStrings?
transient boolean customZoneStrings;
/**
* Locale, necessary to lazily load time zone strings. We force the time
* zone names to load upon serialization, so this will never be needed
* post deserialization.
*/
transient final Locale locale;
/**
* Gets zone strings, initializing them if necessary. Does not create
* a defensive copy, so make sure you do so before exposing the returned
* arrays to clients.
*/
synchronized String[][] internalZoneStrings() {
if (zoneStrings == null) {
zoneStrings = TimeZoneNames.getZoneStrings(locale);
}
return zoneStrings;
}
/**
* Constructs a new {@code DateFormatSymbols} instance containing the
* symbols for the user's default locale.
* See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
*/
public DateFormatSymbols() {
this(Locale.getDefault());
}
/**
* Constructs a new {@code DateFormatSymbols} instance containing the
* symbols for the specified locale.
*
* @param locale
* the locale.
*/
public DateFormatSymbols(Locale locale) {
this.locale = LocaleData.mapInvalidAndNullLocales(locale);
this.localPatternChars = SimpleDateFormat.PATTERN_CHARS;
this.localeData = LocaleData.get(locale);
this.ampms = localeData.amPm;
this.eras = localeData.eras;
this.months = localeData.longMonthNames;
this.shortMonths = localeData.shortMonthNames;
this.weekdays = localeData.longWeekdayNames;
this.shortWeekdays = localeData.shortWeekdayNames;
}
/**
* Returns a new {@code DateFormatSymbols} instance for the user's default locale.
* See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
*
* @return an instance of {@code DateFormatSymbols}
* @since 1.6
*/
public static final DateFormatSymbols getInstance() {
return getInstance(Locale.getDefault());
}
/**
* Returns a new {@code DateFormatSymbols} for the given locale.
*
* @param locale the locale
* @return an instance of {@code DateFormatSymbols}
* @throws NullPointerException if {@code locale == null}
* @since 1.6
*/
public static final DateFormatSymbols getInstance(Locale locale) {
if (locale == null) {
throw new NullPointerException("locale == null");
}
return new DateFormatSymbols(locale);
}
/**
* Returns an array of locales for which custom {@code DateFormatSymbols} instances
* are available.
* <p>Note that Android does not support user-supplied locale service providers.
* @since 1.6
*/
public static Locale[] getAvailableLocales() {
return ICU.getAvailableDateFormatSymbolsLocales();
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
// NOTE: We don't serialize the locale we were created with, so we can't
// get back the localeData object we want. This is broken for callers that
// access this field directly (i.e, SimpleDateFormat). We should ideally
// have serialized the locale we were created with.
this.localeData = LocaleData.get(Locale.getDefault());
}
private void writeObject(ObjectOutputStream oos) throws IOException {
internalZoneStrings();
oos.defaultWriteObject();
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
/**
* Compares this object with the specified object and indicates if they are
* equal.
*
* @param object
* the object to compare with this object.
* @return {@code true} if {@code object} is an instance of
* {@code DateFormatSymbols} and has the same symbols as this
* object, {@code false} otherwise.
* @see #hashCode
*/
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof DateFormatSymbols)) {
return false;
}
DateFormatSymbols rhs = (DateFormatSymbols) object;
return localPatternChars.equals(rhs.localPatternChars) &&
Arrays.equals(ampms, rhs.ampms) &&
Arrays.equals(eras, rhs.eras) &&
Arrays.equals(months, rhs.months) &&
Arrays.equals(shortMonths, rhs.shortMonths) &&
Arrays.equals(shortWeekdays, rhs.shortWeekdays) &&
Arrays.equals(weekdays, rhs.weekdays) &&
timeZoneStringsEqual(this, rhs);
}
private static boolean timeZoneStringsEqual(DateFormatSymbols lhs, DateFormatSymbols rhs) {
// Quick check that may keep us from having to load the zone strings.
// Note that different locales may have the same strings, so the opposite check isn't valid.
if (lhs.zoneStrings == null && rhs.zoneStrings == null && lhs.locale.equals(rhs.locale)) {
return true;
}
// Make sure zone strings are loaded, then check.
return Arrays.deepEquals(lhs.internalZoneStrings(), rhs.internalZoneStrings());
}
@Override
public String toString() {
// 'locale' isn't part of the externally-visible state.
// 'zoneStrings' is so large, we just print a representative value.
return getClass().getName() +
"[amPmStrings=" + Arrays.toString(ampms) +
",customZoneStrings=" + customZoneStrings +
",eras=" + Arrays.toString(eras) +
",localPatternChars=" + localPatternChars +
",months=" + Arrays.toString(months) +
",shortMonths=" + Arrays.toString(shortMonths) +
",shortWeekdays=" + Arrays.toString(shortWeekdays) +
",weekdays=" + Arrays.toString(weekdays) +
",zoneStrings=[" + Arrays.toString(internalZoneStrings()[0]) + "...]" +
"]";
}
/**
* Returns the array of strings which represent AM and PM. Use the
* {@link java.util.Calendar} constants {@code Calendar.AM} and
* {@code Calendar.PM} as indices for the array.
*
* @return an array of strings.
*/
public String[] getAmPmStrings() {
return ampms.clone();
}
/**
* Returns the array of strings which represent BC and AD. Use the
* {@link java.util.Calendar} constants {@code GregorianCalendar.BC} and
* {@code GregorianCalendar.AD} as indices for the array.
*
* @return an array of strings.
*/
public String[] getEras() {
return eras.clone();
}
/**
* Returns the pattern characters used by {@link SimpleDateFormat} to
* specify date and time fields.
*/
public String getLocalPatternChars() {
return localPatternChars;
}
/**
* Returns the array of strings containing the full names of the months. Use
* the {@link java.util.Calendar} constants {@code Calendar.JANUARY} etc. as
* indices for the array.
*
* @return an array of strings.
*/
public String[] getMonths() {
return months.clone();
}
/**
* Returns the array of strings containing the abbreviated names of the
* months. Use the {@link java.util.Calendar} constants
* {@code Calendar.JANUARY} etc. as indices for the array.
*
* @return an array of strings.
*/
public String[] getShortMonths() {
return shortMonths.clone();
}
/**
* Returns the array of strings containing the abbreviated names of the days
* of the week. Use the {@link java.util.Calendar} constants
* {@code Calendar.SUNDAY} etc. as indices for the array.
*
* @return an array of strings.
*/
public String[] getShortWeekdays() {
return shortWeekdays.clone();
}
/**
* Returns the array of strings containing the full names of the days of the
* week. Use the {@link java.util.Calendar} constants
* {@code Calendar.SUNDAY} etc. as indices for the array.
*
* @return an array of strings.
*/
public String[] getWeekdays() {
return weekdays.clone();
}
/**
* Returns the two-dimensional array of strings containing localized names for time zones.
* Each row is an array of five strings:
* <ul>
* <li>The time zone ID, for example "America/Los_Angeles".
* This is not localized, and is used as a key into the table.
* <li>The long display name, for example "Pacific Standard Time".
* <li>The short display name, for example "PST".
* <li>The long display name for DST, for example "Pacific Daylight Time".
* This is the non-DST long name for zones that have never had DST, for
* example "Central Standard Time" for "Canada/Saskatchewan".
* <li>The short display name for DST, for example "PDT". This is the
* non-DST short name for zones that have never had DST, for example
* "CST" for "Canada/Saskatchewan".
* </ul>
*/
public String[][] getZoneStrings() {
String[][] result = clone2dStringArray(internalZoneStrings());
// If icu4c doesn't have a name, our array contains a null. TimeZone.getDisplayName
// knows how to format GMT offsets (and, unlike icu4c, has accurate data). http://b/8128460.
for (String[] zone : result) {
String id = zone[0];
if (zone[1] == null) {
zone[1] = TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.LONG, locale);
}
if (zone[2] == null) {
zone[2] = TimeZone.getTimeZone(id).getDisplayName(false, TimeZone.SHORT, locale);
}
if (zone[3] == null) {
zone[3] = TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.LONG, locale);
}
if (zone[4] == null) {
zone[4] = TimeZone.getTimeZone(id).getDisplayName(true, TimeZone.SHORT, locale);
}
}
return result;
}
private static String[][] clone2dStringArray(String[][] array) {
String[][] result = new String[array.length][];
for (int i = 0; i < array.length; ++i) {
result[i] = array[i].clone();
}
return result;
}
@Override
public int hashCode() {
String[][] zoneStrings = internalZoneStrings();
int hashCode;
hashCode = localPatternChars.hashCode();
for (String element : ampms) {
hashCode += element.hashCode();
}
for (String element : eras) {
hashCode += element.hashCode();
}
for (String element : months) {
hashCode += element.hashCode();
}
for (String element : shortMonths) {
hashCode += element.hashCode();
}
for (String element : shortWeekdays) {
hashCode += element.hashCode();
}
for (String element : weekdays) {
hashCode += element.hashCode();
}
for (String[] element : zoneStrings) {
for (int j = 0; j < element.length; j++) {
if (element[j] != null) {
hashCode += element[j].hashCode();
}
}
}
return hashCode;
}
/**
* Sets the array of strings which represent AM and PM. Use the
* {@link java.util.Calendar} constants {@code Calendar.AM} and
* {@code Calendar.PM} as indices for the array.
*
* @param data
* the array of strings for AM and PM.
*/
public void setAmPmStrings(String[] data) {
ampms = data.clone();
}
/**
* Sets the array of Strings which represent BC and AD. Use the
* {@link java.util.Calendar} constants {@code GregorianCalendar.BC} and
* {@code GregorianCalendar.AD} as indices for the array.
*
* @param data
* the array of strings for BC and AD.
*/
public void setEras(String[] data) {
eras = data.clone();
}
/**
* Sets the pattern characters used by {@link SimpleDateFormat} to specify
* date and time fields.
*
* @param data
* the string containing the pattern characters.
* @throws NullPointerException
* if {@code data} is null
*/
public void setLocalPatternChars(String data) {
if (data == null) {
throw new NullPointerException("data == null");
}
localPatternChars = data;
}
/**
* Sets the array of strings containing the full names of the months. Use
* the {@link java.util.Calendar} constants {@code Calendar.JANUARY} etc. as
* indices for the array.
*
* @param data
* the array of strings.
*/
public void setMonths(String[] data) {
months = data.clone();
}
/**
* Sets the array of strings containing the abbreviated names of the months.
* Use the {@link java.util.Calendar} constants {@code Calendar.JANUARY}
* etc. as indices for the array.
*
* @param data
* the array of strings.
*/
public void setShortMonths(String[] data) {
shortMonths = data.clone();
}
/**
* Sets the array of strings containing the abbreviated names of the days of
* the week. Use the {@link java.util.Calendar} constants
* {@code Calendar.SUNDAY} etc. as indices for the array.
*
* @param data
* the array of strings.
*/
public void setShortWeekdays(String[] data) {
shortWeekdays = data.clone();
}
/**
* Sets the array of strings containing the full names of the days of the
* week. Use the {@link java.util.Calendar} constants
* {@code Calendar.SUNDAY} etc. as indices for the array.
*
* @param data
* the array of strings.
*/
public void setWeekdays(String[] data) {
weekdays = data.clone();
}
/**
* Sets the two-dimensional array of strings containing localized names for time zones.
* See {@link #getZoneStrings} for details.
* @throws IllegalArgumentException if any row has fewer than 5 elements.
* @throws NullPointerException if {@code zoneStrings == null}.
*/
public void setZoneStrings(String[][] zoneStrings) {
if (zoneStrings == null) {
throw new NullPointerException("zoneStrings == null");
}
for (String[] row : zoneStrings) {
if (row.length < 5) {
throw new IllegalArgumentException(Arrays.toString(row) + ".length < 5");
}
}
this.zoneStrings = clone2dStringArray(zoneStrings);
this.customZoneStrings = true;
}
}