1. /*
  2. * @(#)DateFormatSymbols.java 1.41 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /*
  8. * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
  9. * (C) Copyright IBM Corp. 1996 - All Rights Reserved
  10. *
  11. * The original version of this source code and documentation is copyrighted
  12. * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  13. * materials are provided under terms of a License Agreement between Taligent
  14. * and Sun. This technology is protected by multiple US and International
  15. * patents. This notice and attribution to Taligent may not be removed.
  16. * Taligent is a registered trademark of Taligent, Inc.
  17. *
  18. */
  19. package java.text;
  20. import java.util.Locale;
  21. import java.util.ResourceBundle;
  22. import java.io.Serializable;
  23. import java.lang.ref.SoftReference;
  24. import java.util.Vector;
  25. import java.util.Enumeration;
  26. import sun.text.Utility;
  27. import sun.text.resources.LocaleData;
  28. import java.util.Hashtable;
  29. /**
  30. * <code>DateFormatSymbols</code> is a public class for encapsulating
  31. * localizable 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</code> and <code>SimpleDateFormat</code> both use
  34. * <code>DateFormatSymbols</code> to encapsulate this information.
  35. *
  36. * <p>
  37. * Typically you shouldn't use <code>DateFormatSymbols</code> directly.
  38. * Rather, you are encouraged to create a date-time formatter with the
  39. * <code>DateFormat</code> class's factory methods: <code>getTimeInstance</code>,
  40. * <code>getDateInstance</code>, or <code>getDateTimeInstance</code>.
  41. * These methods automatically create a <code>DateFormatSymbols</code> for
  42. * the formatter so that you don't have to. After the
  43. * formatter is created, you may modify its format pattern using the
  44. * <code>setPattern</code> method. For more information about
  45. * creating formatters using <code>DateFormat</code>'s factory methods,
  46. * see {@link DateFormat}.
  47. *
  48. * <p>
  49. * If you decide to create a date-time formatter with a specific
  50. * format pattern for a specific locale, you can do so with:
  51. * <blockquote>
  52. * <pre>
  53. * new SimpleDateFormat(aPattern, new DateFormatSymbols(aLocale)).
  54. * </pre>
  55. * </blockquote>
  56. *
  57. * <p>
  58. * <code>DateFormatSymbols</code> objects are cloneable. When you obtain
  59. * a <code>DateFormatSymbols</code> object, feel free to modify the
  60. * date-time formatting data. For instance, you can replace the localized
  61. * date-time format pattern characters with the ones that you feel easy
  62. * to remember. Or you can change the representative cities
  63. * to your favorite ones.
  64. *
  65. * <p>
  66. * New <code>DateFormatSymbols</code> subclasses may be added to support
  67. * <code>SimpleDateFormat</code> for date-time formatting for additional locales.
  68. * @see DateFormat
  69. * @see SimpleDateFormat
  70. * @see java.util.SimpleTimeZone
  71. * @version 1.41 12/19/03
  72. * @author Chen-Lieh Huang
  73. */
  74. public class DateFormatSymbols implements Serializable, Cloneable {
  75. /**
  76. * Construct a DateFormatSymbols object by loading format data from
  77. * resources for the default locale.
  78. *
  79. * @exception java.util.MissingResourceException
  80. * if the resources for the default locale cannot be
  81. * found or cannot be loaded.
  82. */
  83. public DateFormatSymbols()
  84. {
  85. initializeData(Locale.getDefault());
  86. }
  87. /**
  88. * Construct a DateFormatSymbols object by loading format data from
  89. * resources for the given locale.
  90. *
  91. * @exception java.util.MissingResourceException
  92. * if the resources for the specified locale cannot be
  93. * found or cannot be loaded.
  94. */
  95. public DateFormatSymbols(Locale locale)
  96. {
  97. initializeData(locale);
  98. }
  99. /**
  100. * Era strings. For example: "AD" and "BC". An array of 2 strings,
  101. * indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
  102. * @serial
  103. */
  104. String eras[] = null;
  105. /**
  106. * Month strings. For example: "January", "February", etc. An array
  107. * of 13 strings (some calendars have 13 months), indexed by
  108. * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
  109. * @serial
  110. */
  111. String months[] = null;
  112. /**
  113. * Short month strings. For example: "Jan", "Feb", etc. An array of
  114. * 13 strings (some calendars have 13 months), indexed by
  115. * <code>Calendar.JANUARY</code>, <code>Calendar.FEBRUARY</code>, etc.
  116. * @serial
  117. */
  118. String shortMonths[] = null;
  119. /**
  120. * Weekday strings. For example: "Sunday", "Monday", etc. An array
  121. * of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
  122. * <code>Calendar.MONDAY</code>, etc.
  123. * The element <code>weekdays[0]</code> is ignored.
  124. * @serial
  125. */
  126. String weekdays[] = null;
  127. /**
  128. * Short weekday strings. For example: "Sun", "Mon", etc. An array
  129. * of 8 strings, indexed by <code>Calendar.SUNDAY</code>,
  130. * <code>Calendar.MONDAY</code>, etc.
  131. * The element <code>shortWeekdays[0]</code> is ignored.
  132. * @serial
  133. */
  134. String shortWeekdays[] = null;
  135. /**
  136. * AM and PM strings. For example: "AM" and "PM". An array of
  137. * 2 strings, indexed by <code>Calendar.AM</code> and
  138. * <code>Calendar.PM</code>.
  139. * @serial
  140. */
  141. String ampms[] = null;
  142. /**
  143. * Localized names of time zones in this locale. This is a
  144. * two-dimensional array of strings of size <em>n</em> by <em>m</em>,
  145. * where <em>m</em> is at least 5. Each of the <em>n</em> rows is an
  146. * entry containing the localized names for a single <code>TimeZone</code>.
  147. * Each such row contains (with <code>i</code> ranging from
  148. * 0..<em>n</em>-1):
  149. * <ul>
  150. * <li><code>zoneStrings[i][0]</code> - time zone ID</li>
  151. * <li><code>zoneStrings[i][1]</code> - long name of zone in standard
  152. * time</li>
  153. * <li><code>zoneStrings[i][2]</code> - short name of zone in
  154. * standard time</li>
  155. * <li><code>zoneStrings[i][3]</code> - long name of zone in daylight
  156. * savings time</li>
  157. * <li><code>zoneStrings[i][4]</code> - short name of zone in daylight
  158. * savings time</li>
  159. * </ul>
  160. * The zone ID is <em>not</em> localized; it corresponds to the ID
  161. * value associated with a system time zone object. All other entries
  162. * are localized names. If a zone does not implement daylight savings
  163. * time, the daylight savings time names are ignored.
  164. * @see java.util.TimeZone
  165. * @serial
  166. */
  167. String zoneStrings[][] = null;
  168. /**
  169. * Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
  170. * All locales use the same these unlocalized pattern characters.
  171. */
  172. static final String patternChars = "GyMdkHmsSEDFwWahKzZ";
  173. /**
  174. * Localized date-time pattern characters. For example, a locale may
  175. * wish to use 'u' rather than 'y' to represent years in its date format
  176. * pattern strings.
  177. * This string must be exactly 18 characters long, with the index of
  178. * the characters described by <code>DateFormat.ERA_FIELD</code>,
  179. * <code>DateFormat.YEAR_FIELD</code>, etc. Thus, if the string were
  180. * "Xz...", then localized patterns would use 'X' for era and 'z' for year.
  181. * @serial
  182. */
  183. String localPatternChars = null;
  184. /* use serialVersionUID from JDK 1.1.4 for interoperability */
  185. static final long serialVersionUID = -5987973545549424702L;
  186. /**
  187. * Gets era strings. For example: "AD" and "BC".
  188. * @return the era strings.
  189. */
  190. public String[] getEras() {
  191. return duplicate(eras);
  192. }
  193. /**
  194. * Sets era strings. For example: "AD" and "BC".
  195. * @param newEras the new era strings.
  196. */
  197. public void setEras(String[] newEras) {
  198. eras = duplicate(newEras);
  199. }
  200. /**
  201. * Gets month strings. For example: "January", "February", etc.
  202. * @return the month strings.
  203. */
  204. public String[] getMonths() {
  205. return duplicate(months);
  206. }
  207. /**
  208. * Sets month strings. For example: "January", "February", etc.
  209. * @param newMonths the new month strings.
  210. */
  211. public void setMonths(String[] newMonths) {
  212. months = duplicate(newMonths);
  213. }
  214. /**
  215. * Gets short month strings. For example: "Jan", "Feb", etc.
  216. * @return the short month strings.
  217. */
  218. public String[] getShortMonths() {
  219. return duplicate(shortMonths);
  220. }
  221. /**
  222. * Sets short month strings. For example: "Jan", "Feb", etc.
  223. * @param newShortMonths the new short month strings.
  224. */
  225. public void setShortMonths(String[] newShortMonths) {
  226. shortMonths = duplicate(newShortMonths);
  227. }
  228. /**
  229. * Gets weekday strings. For example: "Sunday", "Monday", etc.
  230. * @return the weekday strings. Use <code>Calendar.SUNDAY</code>,
  231. * <code>Calendar.MONDAY</code>, etc. to index the result array.
  232. */
  233. public String[] getWeekdays() {
  234. return duplicate(weekdays);
  235. }
  236. /**
  237. * Sets weekday strings. For example: "Sunday", "Monday", etc.
  238. * @param newWeekdays the new weekday strings. The array should
  239. * be indexed by <code>Calendar.SUNDAY</code>,
  240. * <code>Calendar.MONDAY</code>, etc.
  241. */
  242. public void setWeekdays(String[] newWeekdays) {
  243. weekdays = duplicate(newWeekdays);
  244. }
  245. /**
  246. * Gets short weekday strings. For example: "Sun", "Mon", etc.
  247. * @return the short weekday strings. Use <code>Calendar.SUNDAY</code>,
  248. * <code>Calendar.MONDAY</code>, etc. to index the result array.
  249. */
  250. public String[] getShortWeekdays() {
  251. return duplicate(shortWeekdays);
  252. }
  253. /**
  254. * Sets short weekday strings. For example: "Sun", "Mon", etc.
  255. * @param newShortWeekdays the new short weekday strings. The array should
  256. * be indexed by <code>Calendar.SUNDAY</code>,
  257. * <code>Calendar.MONDAY</code>, etc.
  258. */
  259. public void setShortWeekdays(String[] newShortWeekdays) {
  260. shortWeekdays = duplicate(newShortWeekdays);
  261. }
  262. /**
  263. * Gets ampm strings. For example: "AM" and "PM".
  264. * @return the ampm strings.
  265. */
  266. public String[] getAmPmStrings() {
  267. return duplicate(ampms);
  268. }
  269. /**
  270. * Sets ampm strings. For example: "AM" and "PM".
  271. * @param newAmpms the new ampm strings.
  272. */
  273. public void setAmPmStrings(String[] newAmpms) {
  274. ampms = duplicate(newAmpms);
  275. }
  276. /**
  277. * Gets timezone strings.
  278. * @return the timezone strings.
  279. */
  280. public String[][] getZoneStrings() {
  281. String[][] aCopy = new String[zoneStrings.length][];
  282. for (int i = 0; i < zoneStrings.length; ++i)
  283. aCopy[i] = duplicate(zoneStrings[i]);
  284. return aCopy;
  285. }
  286. /**
  287. * Sets timezone strings.
  288. * @param newZoneStrings the new timezone strings.
  289. */
  290. public void setZoneStrings(String[][] newZoneStrings) {
  291. String[][] aCopy = new String[newZoneStrings.length][];
  292. for (int i = 0; i < newZoneStrings.length; ++i)
  293. aCopy[i] = duplicate(newZoneStrings[i]);
  294. zoneStrings = aCopy;
  295. }
  296. /**
  297. * Gets localized date-time pattern characters. For example: 'u', 't', etc.
  298. * @return the localized date-time pattern characters.
  299. */
  300. public String getLocalPatternChars() {
  301. return new String(localPatternChars);
  302. }
  303. /**
  304. * Sets localized date-time pattern characters. For example: 'u', 't', etc.
  305. * @param newLocalPatternChars the new localized date-time
  306. * pattern characters.
  307. */
  308. public void setLocalPatternChars(String newLocalPatternChars) {
  309. localPatternChars = new String(newLocalPatternChars);
  310. }
  311. /**
  312. * Overrides Cloneable
  313. */
  314. public Object clone()
  315. {
  316. try
  317. {
  318. DateFormatSymbols other = (DateFormatSymbols)super.clone();
  319. copyMembers(this, other);
  320. return other;
  321. } catch (CloneNotSupportedException e) {
  322. throw new InternalError();
  323. }
  324. }
  325. /**
  326. * Override hashCode.
  327. * Generates a hash code for the DateFormatSymbols object.
  328. */
  329. public int hashCode() {
  330. int hashcode = 0;
  331. for (int index = 0; index < this.zoneStrings[0].length; ++index)
  332. hashcode ^= this.zoneStrings[0][index].hashCode();
  333. return hashcode;
  334. }
  335. /**
  336. * Override equals
  337. */
  338. public boolean equals(Object obj)
  339. {
  340. if (this == obj) return true;
  341. if (obj == null || getClass() != obj.getClass()) return false;
  342. DateFormatSymbols that = (DateFormatSymbols) obj;
  343. return (Utility.arrayEquals(eras, that.eras)
  344. && Utility.arrayEquals(months, that.months)
  345. && Utility.arrayEquals(shortMonths, that.shortMonths)
  346. && Utility.arrayEquals(weekdays, that.weekdays)
  347. && Utility.arrayEquals(shortWeekdays, that.shortWeekdays)
  348. && Utility.arrayEquals(ampms, that.ampms)
  349. && Utility.arrayEquals(zoneStrings, that.zoneStrings)
  350. && Utility.arrayEquals(localPatternChars,
  351. that.localPatternChars));
  352. }
  353. // =======================privates===============================
  354. /**
  355. * Useful constant for defining timezone offsets.
  356. */
  357. static final int millisPerHour = 60*60*1000;
  358. /**
  359. * Cache to hold the LocaleElements and DateFormatZoneData ResourceBundles
  360. * of a Locale.
  361. */
  362. private static Hashtable cachedLocaleData = new Hashtable(3);
  363. /**
  364. * cache to hold time zone localized strings. Keyed by Locale
  365. */
  366. private static Hashtable cachedZoneData = new Hashtable();
  367. /**
  368. * Look up resource data for the desiredLocale in the cache; update the
  369. * cache if necessary.
  370. */
  371. private ResourceBundle[] cacheLookup(Locale desiredLocale) {
  372. ResourceBundle[] rbs = new ResourceBundle[2];
  373. SoftReference[] data
  374. = (SoftReference[])cachedLocaleData.get(desiredLocale);
  375. if (data == null) {
  376. rbs[0] = LocaleData.getLocaleElements(desiredLocale);
  377. rbs[1] = LocaleData.getDateFormatZoneData(desiredLocale);
  378. data = new SoftReference[] { new SoftReference(rbs[0]),
  379. new SoftReference(rbs[1]) };
  380. cachedLocaleData.put(desiredLocale, data);
  381. } else {
  382. ResourceBundle r;
  383. if ((r = (ResourceBundle)data[0].get()) == null) {
  384. r = LocaleData.getLocaleElements(desiredLocale);
  385. data[0] = new SoftReference(r);
  386. }
  387. rbs[0] = r;
  388. if ((r = (ResourceBundle)data[1].get()) == null) {
  389. r = LocaleData.getDateFormatZoneData(desiredLocale);
  390. data[1] = new SoftReference(r);
  391. }
  392. rbs[1] = r;
  393. }
  394. return rbs;
  395. }
  396. /**
  397. * Load time zone localized strings. Enumerate all keys (except
  398. * "localPatternChars" and "zoneStrings").
  399. */
  400. private String[][] loadZoneStrings(Locale desiredLocale,
  401. ResourceBundle rsrc)
  402. {
  403. String[][] zones;
  404. SoftReference data = (SoftReference)cachedZoneData.get(desiredLocale);
  405. if (data == null || ((zones = (String[][])data.get()) == null)) {
  406. Vector vec = new Vector();
  407. Enumeration keys = rsrc.getKeys();
  408. while(keys.hasMoreElements()) {
  409. String key = (String)keys.nextElement();
  410. if (!key.equals("localPatternChars") &&
  411. !key.equals("zoneStrings")) {
  412. vec.add(rsrc.getObject(key));
  413. }
  414. }
  415. zones = new String[vec.size()][];
  416. vec.toArray(zones);
  417. data = new SoftReference(zones);
  418. cachedZoneData.put(desiredLocale, data);
  419. }
  420. return zones;
  421. }
  422. private void initializeData(Locale desiredLocale)
  423. {
  424. int i;
  425. ResourceBundle[] rbs = cacheLookup(desiredLocale);
  426. ResourceBundle resource = rbs[0];
  427. ResourceBundle zoneResource = rbs[1];
  428. // FIXME: cache only ResourceBundle. Hence every time, will do
  429. // getObject(). This won't be necessary if the Resource itself
  430. // is cached.
  431. eras = (String[])resource.getObject("Eras");
  432. months = resource.getStringArray("MonthNames");
  433. shortMonths = resource.getStringArray("MonthAbbreviations");
  434. String[] lWeekdays = resource.getStringArray("DayNames");
  435. weekdays = new String[8];
  436. weekdays[0] = ""; // 1-based
  437. for (i=0; i<lWeekdays.length; i++)
  438. weekdays[i+1] = lWeekdays[i];
  439. String[] sWeekdays = resource.getStringArray("DayAbbreviations");
  440. shortWeekdays = new String[8];
  441. shortWeekdays[0] = ""; // 1-based
  442. for (i=0; i<sWeekdays.length; i++)
  443. shortWeekdays[i+1] = sWeekdays[i];
  444. ampms = resource.getStringArray("AmPmMarkers");
  445. zoneStrings = (String[][])loadZoneStrings(desiredLocale,
  446. zoneResource);
  447. localPatternChars
  448. = (String) zoneResource.getObject("localPatternChars");
  449. }
  450. /**
  451. * Package private: used by SimpleDateFormat
  452. * Gets the index for the given time zone ID to obtain the timezone
  453. * strings for formatting. The time zone ID is just for programmatic
  454. * lookup. NOT LOCALIZED!!!
  455. * @param ID the given time zone ID.
  456. * @return the index of the given time zone ID. Returns -1 if
  457. * the given time zone ID can't be located in the DateFormatSymbols object.
  458. * @see java.util.SimpleTimeZone
  459. */
  460. final int getZoneIndex (String ID)
  461. {
  462. for (int index=0; index<zoneStrings.length; index++)
  463. {
  464. if (ID.equalsIgnoreCase(zoneStrings[index][0])) return index;
  465. }
  466. return -1;
  467. }
  468. /**
  469. * Clones an array of Strings.
  470. * @param srcArray the source array to be cloned.
  471. * @param count the number of elements in the given source array.
  472. * @return a cloned array.
  473. */
  474. private final String[] duplicate(String[] srcArray)
  475. {
  476. String[] dstArray = new String[srcArray.length];
  477. System.arraycopy(srcArray, 0, dstArray, 0, srcArray.length);
  478. return dstArray;
  479. }
  480. /**
  481. * Clones all the data members from the source DateFormatSymbols to
  482. * the target DateFormatSymbols. This is only for subclasses.
  483. * @param src the source DateFormatSymbols.
  484. * @param dst the target DateFormatSymbols.
  485. */
  486. private final void copyMembers(DateFormatSymbols src, DateFormatSymbols dst)
  487. {
  488. dst.eras = duplicate(src.eras);
  489. dst.months = duplicate(src.months);
  490. dst.shortMonths = duplicate(src.shortMonths);
  491. dst.weekdays = duplicate(src.weekdays);
  492. dst.shortWeekdays = duplicate(src.shortWeekdays);
  493. dst.ampms = duplicate(src.ampms);
  494. for (int i = 0; i < dst.zoneStrings.length; ++i)
  495. dst.zoneStrings[i] = duplicate(src.zoneStrings[i]);
  496. dst.localPatternChars = new String (src.localPatternChars);
  497. }
  498. /**
  499. * Compares the equality of the two arrays of String.
  500. * @param current this String array.
  501. * @param other that String array.
  502. */
  503. private final boolean equals(String[] current, String[] other)
  504. {
  505. int count = current.length;
  506. for (int i = 0; i < count; ++i)
  507. if (!current[i].equals(other[i]))
  508. return false;
  509. return true;
  510. }
  511. }