1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2008, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.impl.jdkadapter; 10 11 import java.util.HashMap; 12 import java.util.Locale; 13 import java.util.Map; 14 import java.util.TimeZone; 15 16 import com.ibm.icu.impl.icuadapter.TimeZoneJDK; 17 import com.ibm.icu.text.DateFormatSymbols; 18 import com.ibm.icu.util.Calendar; 19 20 /** 21 * CalendarICU is an adapter class which wraps ICU4J Calendar and 22 * implements java.util.Calendar APIs. 23 */ 24 public class CalendarICU extends java.util.Calendar { 25 26 private static final long serialVersionUID = -8641226371713600671L; 27 28 private Calendar fIcuCal; 29 CalendarICU(Calendar icuCal)30 private CalendarICU(Calendar icuCal) { 31 fIcuCal = icuCal; 32 init(); 33 } 34 wrap(Calendar icuCal)35 public static java.util.Calendar wrap(Calendar icuCal) { 36 return new CalendarICU(icuCal); 37 } 38 unwrap()39 public Calendar unwrap() { 40 sync(); 41 return fIcuCal; 42 } 43 44 @Override add(int field, int amount)45 public void add(int field, int amount) { 46 sync(); 47 fIcuCal.add(field, amount); 48 } 49 50 // Note: We do not need to override followings. These methods 51 // call int compareTo(Calendar anotherCalendar) and we 52 // override the method. 53 //public boolean after(Object when) 54 //public boolean before(Object when) 55 56 // Note: Jeez! These methods are final and we cannot override them. 57 // We do not want to rewrite ICU Calendar implementation classes 58 // as subclasses of java.util.Calendar. This adapter class 59 // wraps an ICU Calendar instance and the calendar calculation 60 // is actually done independently from java.util.Calendar 61 // implementation. Thus, we need to monitor the status of 62 // superclass fields in some methods and call ICU Calendar's 63 // clear if superclass clear update the status of superclass's 64 // calendar fields. See private void sync(). 65 //public void clear() 66 //public void clear(int field) 67 68 @Override clone()69 public Object clone() { 70 sync(); 71 CalendarICU other = (CalendarICU)super.clone(); 72 other.fIcuCal = (Calendar)fIcuCal.clone(); 73 return other; 74 } 75 compareTo(Calendar anotherCalendar)76 public int compareTo(Calendar anotherCalendar) { 77 sync(); 78 long thisMillis = getTimeInMillis(); 79 long otherMillis = anotherCalendar.getTimeInMillis(); 80 return thisMillis > otherMillis ? 1 : (thisMillis == otherMillis ? 0 : -1); 81 } 82 83 // Note: These methods are supposed to be implemented by java.util.Calendar 84 // subclasses. But we actually use a instance of ICU Calendar 85 // for all calendar calculation, we do nothing here. 86 @Override complete()87 protected void complete() {} 88 @Override computeFields()89 protected void computeFields() {} 90 @Override computeTime()91 protected void computeTime() {} 92 93 @Override equals(Object obj)94 public boolean equals(Object obj) { 95 if (obj instanceof CalendarICU) { 96 sync(); 97 return ((CalendarICU)obj).fIcuCal.equals(fIcuCal); 98 } 99 return false; 100 } 101 102 @Override get(int field)103 public int get(int field) { 104 sync(); 105 return fIcuCal.get(field); 106 } 107 108 @Override getActualMaximum(int field)109 public int getActualMaximum(int field) { 110 return fIcuCal.getActualMaximum(field); 111 } 112 113 @Override getActualMinimum(int field)114 public int getActualMinimum(int field) { 115 return fIcuCal.getActualMinimum(field); 116 } 117 118 @Override getDisplayName(int field, int style, Locale locale)119 public String getDisplayName(int field, int style, Locale locale) { 120 if (field < 0 || field >= FIELD_COUNT || (style != SHORT && style != LONG && style != ALL_STYLES)) { 121 throw new IllegalArgumentException("Bad field or style."); 122 } 123 DateFormatSymbols dfs = DateFormatSymbols.getInstance(locale); 124 String[] array = getFieldStrings(field, style, dfs); 125 if (array != null) { 126 int fieldVal = get(field); 127 if (fieldVal < array.length) { 128 return array[fieldVal]; 129 } 130 } 131 return null; 132 } 133 134 @Override getDisplayNames(int field, int style, Locale locale)135 public Map<String,Integer> getDisplayNames(int field, int style, Locale locale) { 136 if (field < 0 || field >= FIELD_COUNT || (style != SHORT && style != LONG && style != ALL_STYLES)) { 137 throw new IllegalArgumentException("Bad field or style."); 138 } 139 DateFormatSymbols dfs = DateFormatSymbols.getInstance(locale); 140 if (style != ALL_STYLES) { 141 return getFieldStringsMap(field, style, dfs); 142 } 143 144 Map<String,Integer> result = getFieldStringsMap(field, SHORT, dfs); 145 if (result == null) { 146 return null; 147 } 148 if (field == MONTH || field == DAY_OF_WEEK) { 149 Map<String,Integer> longMap = getFieldStringsMap(field, LONG, dfs); 150 if (longMap != null) { 151 result.putAll(longMap); 152 } 153 } 154 return result; 155 } 156 157 @Override getFirstDayOfWeek()158 public int getFirstDayOfWeek() { 159 return fIcuCal.getFirstDayOfWeek(); 160 } 161 162 @Override getGreatestMinimum(int field)163 public int getGreatestMinimum(int field) { 164 return fIcuCal.getGreatestMinimum(field); 165 } 166 167 @Override getLeastMaximum(int field)168 public int getLeastMaximum(int field) { 169 return fIcuCal.getLeastMaximum(field); 170 } 171 172 @Override getMaximum(int field)173 public int getMaximum(int field) { 174 return fIcuCal.getMaximum(field); 175 } 176 177 @Override getMinimalDaysInFirstWeek()178 public int getMinimalDaysInFirstWeek() { 179 return fIcuCal.getMinimalDaysInFirstWeek(); 180 } 181 182 @Override getMinimum(int field)183 public int getMinimum(int field) { 184 return fIcuCal.getMinimum(field); 185 } 186 187 // Note: getTime() calls getTimeInMillis() 188 //public Date getTime() 189 190 @Override getTimeInMillis()191 public long getTimeInMillis() { 192 sync(); 193 return fIcuCal.getTimeInMillis(); 194 } 195 196 @Override getTimeZone()197 public TimeZone getTimeZone() { 198 return TimeZoneICU.wrap(fIcuCal.getTimeZone()); 199 } 200 201 @Override hashCode()202 public int hashCode() { 203 sync(); 204 return fIcuCal.hashCode(); 205 } 206 207 //protected int internalGet(int field) 208 209 @Override isLenient()210 public boolean isLenient() { 211 return fIcuCal.isLenient(); 212 } 213 214 //public boolean isSet(int field) 215 216 @Override roll(int field, boolean up)217 public void roll(int field, boolean up) { 218 sync(); 219 fIcuCal.roll(field, up); 220 } 221 222 @Override roll(int field, int amount)223 public void roll(int field, int amount) { 224 sync(); 225 fIcuCal.roll(field, amount); 226 } 227 228 @Override set(int field, int value)229 public void set(int field, int value) { 230 sync(); 231 fIcuCal.set(field, value); 232 } 233 234 // Note: These set methods call set(int field, int value) for each field. 235 // These are final, so we cannot override them, but we override 236 // set(int field, int value), so the superclass implementations 237 // still work as we want. 238 //public void set(int year, int month, int date) 239 //public void set(int year, int month, int date, int hourOfDay, int minute) 240 //public void set(int year, int month, int date, int hourOfDay, int minute, int second) 241 242 @Override setFirstDayOfWeek(int value)243 public void setFirstDayOfWeek(int value) { 244 fIcuCal.setFirstDayOfWeek(value); 245 } 246 247 @Override setLenient(boolean lenient)248 public void setLenient(boolean lenient) { 249 fIcuCal.setLenient(lenient); 250 } 251 252 @Override setMinimalDaysInFirstWeek(int value)253 public void setMinimalDaysInFirstWeek(int value) { 254 fIcuCal.setMinimalDaysInFirstWeek(value); 255 } 256 257 // Note: This method calls setTimeInMillis(long millis). 258 // This method is final, so we cannot override it, but we 259 // override setTimeInMillis(long millis), so the superclass 260 // implementation still works as we want. 261 //public void setTime(Date date) 262 263 @Override setTimeInMillis(long millis)264 public void setTimeInMillis(long millis) { 265 fIcuCal.setTimeInMillis(millis); 266 } 267 268 @Override setTimeZone(TimeZone value)269 public void setTimeZone(TimeZone value) { 270 fIcuCal.setTimeZone(TimeZoneJDK.wrap(value)); 271 } 272 273 @Override toString()274 public String toString() { 275 sync(); 276 return "CalendarICU: " + fIcuCal.toString(); 277 } 278 sync()279 private void sync() { 280 // Check if clear is called for each JDK Calendar field. 281 // If it was, then call clear for the field in the wrapped 282 // ICU Calendar. 283 for (int i = 0; i < isSet.length; i++) { 284 if (!isSet[i]) { 285 isSet[i] = true; 286 try { 287 fIcuCal.clear(i); 288 } catch (ArrayIndexOutOfBoundsException e) { 289 // More fields in JDK calendar, which is unlikely 290 } 291 } 292 } 293 } 294 init()295 private void init() { 296 // Mark "set" for all fields, so we can detect the invocation of 297 // clear() later. 298 for (int i = 0; i < isSet.length; i++) { 299 isSet[i] = true; 300 } 301 } 302 getFieldStrings(int field, int style, DateFormatSymbols dfs)303 private static String[] getFieldStrings(int field, int style, DateFormatSymbols dfs) { 304 String[] result = null; 305 switch (field) { 306 case AM_PM: 307 result = dfs.getAmPmStrings(); 308 break; 309 case DAY_OF_WEEK: 310 result = (style == LONG) ? dfs.getWeekdays() : dfs.getShortWeekdays(); 311 break; 312 case ERA: 313 //result = (style == LONG) ? dfs.getEraNames() : dfs.getEras(); 314 result = dfs.getEras(); 315 break; 316 case MONTH: 317 result = (style == LONG) ? dfs.getMonths() : dfs.getShortMonths(); 318 break; 319 } 320 return result; 321 } 322 getFieldStringsMap(int field, int style, DateFormatSymbols dfs)323 private static Map<String,Integer> getFieldStringsMap(int field, int style, DateFormatSymbols dfs) { 324 String[] strings = getFieldStrings(field, style, dfs); 325 if (strings == null) { 326 return null; 327 } 328 Map<String,Integer> res = new HashMap<String,Integer>(); 329 for (int i = 0; i < strings.length; i++) { 330 if (strings[i].length() != 0) { 331 res.put(strings[i], Integer.valueOf(i)); 332 } 333 } 334 return res; 335 } 336 } 337