/* ******************************************************************************* * Copyright (C) 2008, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.impl.jdkadapter; import java.text.AttributedCharacterIterator; import java.text.AttributedString; import java.text.CharacterIterator; import java.text.DateFormatSymbols; import java.text.FieldPosition; import java.text.NumberFormat; import java.text.ParsePosition; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TimeZone; import com.ibm.icu.impl.icuadapter.NumberFormatJDK; import com.ibm.icu.impl.icuadapter.TimeZoneJDK; import com.ibm.icu.text.DateFormat; import com.ibm.icu.text.SimpleDateFormat; /** * SimpleDateFormatICU is an adapter class which wraps ICU4J SimpleDateFormat and * implements java.text.SimpleDateFormat APIs. */ public class SimpleDateFormatICU extends java.text.SimpleDateFormat { private static final long serialVersionUID = -2060890659010258983L; private SimpleDateFormat fIcuSdf; private SimpleDateFormatICU(SimpleDateFormat icuSdf) { fIcuSdf = icuSdf; } public static java.text.SimpleDateFormat wrap(SimpleDateFormat icuSdf) { return new SimpleDateFormatICU(icuSdf); } // Methods overriding java.text.SimpleDateFormat @Override public void applyLocalizedPattern(String pattern) { fIcuSdf.applyLocalizedPattern(pattern); } @Override public void applyPattern(String pattern) { fIcuSdf.applyPattern(pattern); } @Override public Object clone() { SimpleDateFormatICU other = (SimpleDateFormatICU)super.clone(); other.fIcuSdf = (SimpleDateFormat)this.fIcuSdf.clone(); return other; } @Override public boolean equals(Object obj) { if (obj instanceof SimpleDateFormatICU) { return ((SimpleDateFormatICU)obj).fIcuSdf.equals(this.fIcuSdf); } return false; } @Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) { return fIcuSdf.format(date, toAppendTo, pos); } @Override public AttributedCharacterIterator formatToCharacterIterator(Object obj) { AttributedCharacterIterator aci = fIcuSdf.formatToCharacterIterator(obj); // Create a new AttributedString StringBuilder sb = new StringBuilder(aci.getEndIndex() - aci.getBeginIndex()); char c = aci.first(); while (true) { sb.append(c); c = aci.next(); if (c == CharacterIterator.DONE) { break; } } AttributedString resstr = new AttributedString(sb.toString()); // Mapping attributes Map attributes = null; int index = aci.getBeginIndex(); int residx = 0; while (true) { if (aci.setIndex(index) == CharacterIterator.DONE) { break; } attributes = aci.getAttributes(); if (attributes != null) { int end = aci.getRunLimit(); Map jdkAttributes = new HashMap(); Set keys = attributes.keySet(); for (AttributedCharacterIterator.Attribute key : keys) { AttributedCharacterIterator.Attribute jdkKey = mapAttribute(key); Object jdkVal = attributes.get(key); if (jdkVal instanceof AttributedCharacterIterator.Attribute) { jdkVal = mapAttribute((AttributedCharacterIterator.Attribute)jdkVal); } jdkAttributes.put(jdkKey, jdkVal); } int resend = residx + (end - index); resstr.addAttributes(jdkAttributes, residx, resend); index = end; residx = resend; } } return resstr.getIterator(); } @Override public Date get2DigitYearStart() { return fIcuSdf.get2DigitYearStart(); } @Override public DateFormatSymbols getDateFormatSymbols() { return DateFormatSymbolsICU.wrap(fIcuSdf.getDateFormatSymbols()); } @Override public int hashCode() { return fIcuSdf.hashCode(); } @Override public Date parse(String text, ParsePosition pos) { return fIcuSdf.parse(text, pos); } @Override public void set2DigitYearStart(Date startDate) { fIcuSdf.set2DigitYearStart(startDate); } @Override public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) { com.ibm.icu.text.DateFormatSymbols icuDfs = null; if (newFormatSymbols instanceof DateFormatSymbolsICU) { icuDfs = ((DateFormatSymbolsICU)newFormatSymbols).unwrap(); } else if (fIcuSdf.getCalendar() instanceof com.ibm.icu.util.GregorianCalendar) { // Java 6 uses DateFormatSymbols exclusively for Gregorian // calendar. String[] newJDK, curICU, newICU; icuDfs = fIcuSdf.getDateFormatSymbols(); // Eras newJDK = newFormatSymbols.getEras(); curICU = icuDfs.getEras(); newICU = copySymbols(newJDK, curICU, true); // Months newJDK = newFormatSymbols.getMonths(); curICU = icuDfs.getMonths(); newICU = copySymbols(newJDK, curICU, false); icuDfs.setMonths(newICU); // ShortMonths newJDK = newFormatSymbols.getShortMonths(); curICU = icuDfs.getShortMonths(); newICU = copySymbols(newJDK, curICU, false); icuDfs.setShortMonths(newICU); // Weekdays newJDK = newFormatSymbols.getWeekdays(); curICU = icuDfs.getWeekdays(); newICU = copySymbols(newJDK, curICU, false); icuDfs.setWeekdays(newICU); // ShortWeekdays newJDK = newFormatSymbols.getShortWeekdays(); curICU = icuDfs.getShortWeekdays(); newICU = copySymbols(newJDK, curICU, false); icuDfs.setShortWeekdays(newICU); // AmPm newJDK = newFormatSymbols.getAmPmStrings(); curICU = icuDfs.getAmPmStrings(); newICU = copySymbols(newJDK, curICU, false); icuDfs.setAmPmStrings(newICU); } else { // For other calendars, JDK's standard DateFormatSymbols // cannot be used. throw new UnsupportedOperationException("JDK DateFormatSymbols cannot be used for the calendar type."); } fIcuSdf.setDateFormatSymbols(icuDfs); } @Override public String toLocalizedPattern() { return fIcuSdf.toLocalizedPattern(); } @Override public String toPattern() { return fIcuSdf.toLocalizedPattern(); } // Methods overriding java.text.DateFormat @Override public Calendar getCalendar() { return CalendarICU.wrap(fIcuSdf.getCalendar()); } @Override public NumberFormat getNumberFormat() { com.ibm.icu.text.NumberFormat nfmt = fIcuSdf.getNumberFormat(); if (nfmt instanceof NumberFormatJDK) { return ((NumberFormatJDK)nfmt).unwrap(); } if (nfmt instanceof com.ibm.icu.text.DecimalFormat) { return DecimalFormatICU.wrap((com.ibm.icu.text.DecimalFormat)nfmt); } return NumberFormatICU.wrap(nfmt); } @Override public TimeZone getTimeZone() { return getCalendar().getTimeZone(); } @Override public boolean isLenient() { return fIcuSdf.isLenient(); } private static final long SAMPLE_TIME = 962409600000L; //2000-07-01T00:00:00Z private static final int JAPANESE_YEAR = 12; // Japanese calendar year @ SAMPLE_TIME private static final int THAI_YEAR = 2543; // Thai Buddhist calendar year @ SAMPLE_TIME @Override public void setCalendar(Calendar newCalendar) { com.ibm.icu.util.Calendar icuCal = null; if (newCalendar instanceof CalendarICU) { icuCal = ((CalendarICU)newCalendar).unwrap(); } else { // Note: There is no easy way to implement ICU Calendar with // JDK Calendar implementation. For now, this code assumes // the given calendar is either Gregorian, Buddhist or // JapaneseImperial. Once the type is detected, this code // creates an instance of ICU Calendar with the same type. com.ibm.icu.util.TimeZone icuTz = TimeZoneJDK.wrap(newCalendar.getTimeZone()); if (newCalendar instanceof GregorianCalendar) { icuCal = new com.ibm.icu.util.GregorianCalendar(icuTz); } else { newCalendar.setTimeInMillis(SAMPLE_TIME); int year = newCalendar.get(Calendar.YEAR); if (year == JAPANESE_YEAR) { icuCal = new com.ibm.icu.util.JapaneseCalendar(icuTz); } else if (year == THAI_YEAR) { icuCal = new com.ibm.icu.util.BuddhistCalendar(icuTz); } else { // We cannot support the case throw new UnsupportedOperationException("Unsupported calendar type by ICU Calendar adapter."); } } // Copy the original calendar settings icuCal.setFirstDayOfWeek(newCalendar.getFirstDayOfWeek()); icuCal.setLenient(newCalendar.isLenient()); icuCal.setMinimalDaysInFirstWeek(newCalendar.getMinimalDaysInFirstWeek()); } fIcuSdf.setCalendar(icuCal); } @Override public void setLenient(boolean lenient) { fIcuSdf.setLenient(lenient); } @Override public void setNumberFormat(NumberFormat newNumberFormat) { if (newNumberFormat instanceof DecimalFormatICU) { fIcuSdf.setNumberFormat(((DecimalFormatICU)newNumberFormat).unwrap()); } else if (newNumberFormat instanceof NumberFormatICU) { fIcuSdf.setNumberFormat(((NumberFormatICU)newNumberFormat).unwrap()); } else { fIcuSdf.setNumberFormat(NumberFormatJDK.wrap(newNumberFormat)); } } @Override public void setTimeZone(TimeZone zone) { fIcuSdf.setTimeZone(TimeZoneJDK.wrap(zone)); } private String[] copySymbols(String[] newData, String[] curData, boolean alignEnd) { if (newData.length >= curData.length) { return newData; } int startOffset = alignEnd ? curData.length - newData.length : 0; System.arraycopy(newData, 0, curData, startOffset, newData.length); return curData; } private static AttributedCharacterIterator.Attribute mapAttribute(AttributedCharacterIterator.Attribute icuAttribute) { AttributedCharacterIterator.Attribute jdkAttribute = icuAttribute; if (icuAttribute == DateFormat.Field.AM_PM) { jdkAttribute = java.text.DateFormat.Field.AM_PM; } else if (icuAttribute == DateFormat.Field.DAY_OF_MONTH) { jdkAttribute = java.text.DateFormat.Field.DAY_OF_MONTH; } else if (icuAttribute == DateFormat.Field.DAY_OF_WEEK) { jdkAttribute = java.text.DateFormat.Field.DAY_OF_WEEK; } else if (icuAttribute == DateFormat.Field.DAY_OF_WEEK_IN_MONTH) { jdkAttribute = java.text.DateFormat.Field.DAY_OF_WEEK_IN_MONTH; } else if (icuAttribute == DateFormat.Field.DAY_OF_YEAR) { jdkAttribute = java.text.DateFormat.Field.DAY_OF_YEAR; } else if (icuAttribute == DateFormat.Field.ERA) { jdkAttribute = java.text.DateFormat.Field.ERA; } else if (icuAttribute == DateFormat.Field.HOUR_OF_DAY0) { jdkAttribute = java.text.DateFormat.Field.HOUR_OF_DAY0; } else if (icuAttribute == DateFormat.Field.HOUR_OF_DAY1) { jdkAttribute = java.text.DateFormat.Field.HOUR_OF_DAY1; } else if (icuAttribute == DateFormat.Field.HOUR0) { jdkAttribute = java.text.DateFormat.Field.HOUR0; } else if (icuAttribute == DateFormat.Field.HOUR1) { jdkAttribute = java.text.DateFormat.Field.HOUR1; } else if (icuAttribute == DateFormat.Field.MILLISECOND) { jdkAttribute = java.text.DateFormat.Field.MILLISECOND; } else if (icuAttribute == DateFormat.Field.MINUTE) { jdkAttribute = java.text.DateFormat.Field.MINUTE; } else if (icuAttribute == DateFormat.Field.MONTH) { jdkAttribute = java.text.DateFormat.Field.MONTH; } else if (icuAttribute == DateFormat.Field.SECOND) { jdkAttribute = java.text.DateFormat.Field.SECOND; } else if (icuAttribute == DateFormat.Field.TIME_ZONE) { jdkAttribute = java.text.DateFormat.Field.TIME_ZONE; } else if (icuAttribute == DateFormat.Field.WEEK_OF_MONTH) { jdkAttribute = java.text.DateFormat.Field.WEEK_OF_MONTH; } else if (icuAttribute == DateFormat.Field.WEEK_OF_YEAR) { jdkAttribute = java.text.DateFormat.Field.WEEK_OF_YEAR; } else if (icuAttribute == DateFormat.Field.YEAR) { jdkAttribute = java.text.DateFormat.Field.YEAR; } // There are other DateFormat.Field constants defined in // ICU4J DateFormat below. // // DOW_LOCAL // EXTENDED_YEAR // JULIAN_DAY // MILLISECONDS_IN_DAY // QUARTER // YEAR_WOY // // However, the corresponding pattern characters are not used by // the default factory method - getXXXInstance. So these constants // are only used when user intentionally set a pattern including // these ICU4J specific pattern letters. Even it happens, // ICU4J's DateFormat.Field extends java.text.Format.Field, so // it does not break the contract of formatToCharacterIterator. return jdkAttribute; } }