1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2008-2014, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.impl; 10 11 import java.io.IOException; 12 import java.io.ObjectInputStream; 13 import java.lang.reflect.InvocationTargetException; 14 import java.lang.reflect.Method; 15 import java.util.Date; 16 import java.util.TreeSet; 17 18 import com.ibm.icu.util.TimeZone; 19 20 /** 21 * JavaTimeZone inherits com.ibm.icu.util.TimeZone and wraps java.util.TimeZone. 22 * We used to have JDKTimeZone which wrapped Java TimeZone and used it as primary 23 * TimeZone implementation until ICU4J 3.4.1. This class works exactly like 24 * JDKTimeZone and allows ICU users who use ICU4J and JDK date/time/calendar 25 * services in mix to maintain only JDK timezone rules. 26 * 27 * This TimeZone subclass is returned by the TimeZone factory method getTimeZone(String) 28 * when the default timezone type in TimeZone class is TimeZone.TIMEZONE_JDK. 29 */ 30 public class JavaTimeZone extends TimeZone { 31 32 private static final long serialVersionUID = 6977448185543929364L; 33 34 private static final TreeSet<String> AVAILABLESET; 35 36 private java.util.TimeZone javatz; 37 private transient java.util.Calendar javacal; 38 private static Method mObservesDaylightTime; 39 40 static { 41 AVAILABLESET = new TreeSet<String>(); 42 String[] availableIds = java.util.TimeZone.getAvailableIDs(); 43 for (int i = 0; i < availableIds.length; i++) { 44 AVAILABLESET.add(availableIds[i]); 45 } 46 47 try { 48 mObservesDaylightTime = java.util.TimeZone.class.getMethod("observesDaylightTime", (Class[]) null); 49 } catch (NoSuchMethodException e) { 50 // Java 6 or older 51 } catch (SecurityException e) { 52 // not visible 53 } 54 } 55 56 /** 57 * Constructs a JavaTimeZone with the default Java TimeZone 58 */ 59 public JavaTimeZone() { 60 this(java.util.TimeZone.getDefault(), null); 61 } 62 63 /** 64 * Constructs a JavaTimeZone with the specified Java TimeZone and ID. 65 * @param jtz the Java TimeZone 66 * @param id the ID of the zone. if null, the zone ID is initialized 67 * by the given Java TimeZone's ID. 68 */ 69 public JavaTimeZone(java.util.TimeZone jtz, String id) { 70 if (id == null) { 71 id = jtz.getID(); 72 } 73 javatz = jtz; 74 setID(id); 75 javacal = new java.util.GregorianCalendar(javatz); 76 } 77 78 /** 79 * Creates an instance of JavaTimeZone with the given timezone ID. 80 * @param id A timezone ID, either a system ID or a custom ID. 81 * @return An instance of JavaTimeZone for the given ID, or null 82 * when the ID cannot be understood. 83 */ 84 public static JavaTimeZone createTimeZone(String id) { 85 java.util.TimeZone jtz = null; 86 87 if (AVAILABLESET.contains(id)) { 88 jtz = java.util.TimeZone.getTimeZone(id); 89 } 90 91 if (jtz == null) { 92 // Use ICU's canonical ID mapping 93 boolean[] isSystemID = new boolean[1]; 94 String canonicalID = TimeZone.getCanonicalID(id, isSystemID); 95 if (isSystemID[0] && AVAILABLESET.contains(canonicalID)) { 96 jtz = java.util.TimeZone.getTimeZone(canonicalID); 97 } 98 } 99 100 if (jtz == null) { 101 return null; 102 } 103 104 return new JavaTimeZone(jtz, id); 105 } 106 107 /* (non-Javadoc) 108 * @see com.ibm.icu.util.TimeZone#getOffset(int, int, int, int, int, int) 109 */ 110 @Override 111 public int getOffset(int era, int year, int month, int day, int dayOfWeek, int milliseconds) { 112 return javatz.getOffset(era, year, month, day, dayOfWeek, milliseconds); 113 } 114 115 /* (non-Javadoc) 116 * @see com.ibm.icu.util.TimeZone#getOffset(long, boolean, int[]) 117 */ 118 @Override 119 public void getOffset(long date, boolean local, int[] offsets) { 120 synchronized (javacal) { 121 if (local) { 122 int fields[] = new int[6]; 123 Grego.timeToFields(date, fields); 124 int hour, min, sec, mil; 125 int tmp = fields[5]; 126 mil = tmp % 1000; 127 tmp /= 1000; 128 sec = tmp % 60; 129 tmp /= 60; 130 min = tmp % 60; 131 hour = tmp / 60; 132 javacal.clear(); 133 javacal.set(fields[0], fields[1], fields[2], hour, min, sec); 134 javacal.set(java.util.Calendar.MILLISECOND, mil); 135 136 int doy1, hour1, min1, sec1, mil1; 137 doy1 = javacal.get(java.util.Calendar.DAY_OF_YEAR); 138 hour1 = javacal.get(java.util.Calendar.HOUR_OF_DAY); 139 min1 = javacal.get(java.util.Calendar.MINUTE); 140 sec1 = javacal.get(java.util.Calendar.SECOND); 141 mil1 = javacal.get(java.util.Calendar.MILLISECOND); 142 143 if (fields[4] != doy1 || hour != hour1 || min != min1 || sec != sec1 || mil != mil1) { 144 // Calendar field(s) were changed due to the adjustment for non-existing time 145 // Note: This code does not support non-existing local time at year boundary properly. 146 // But, it should work fine for real timezones. 147 int dayDelta = Math.abs(doy1 - fields[4]) > 1 ? 1 : doy1 - fields[4]; 148 int delta = ((((dayDelta * 24) + hour1 - hour) * 60 + min1 - min) * 60 + sec1 - sec) * 1000 + mil1 - mil; 149 150 // In this case, we use the offsets before the transition 151 javacal.setTimeInMillis(javacal.getTimeInMillis() - delta - 1); 152 } 153 } else { 154 javacal.setTimeInMillis(date); 155 } 156 offsets[0] = javacal.get(java.util.Calendar.ZONE_OFFSET); 157 offsets[1] = javacal.get(java.util.Calendar.DST_OFFSET); 158 } 159 } 160 161 /* (non-Javadoc) 162 * @see com.ibm.icu.util.TimeZone#getRawOffset() 163 */ 164 @Override 165 public int getRawOffset() { 166 return javatz.getRawOffset(); 167 } 168 169 /* (non-Javadoc) 170 * @see com.ibm.icu.util.TimeZone#inDaylightTime(java.util.Date) 171 */ 172 @Override 173 public boolean inDaylightTime(Date date) { 174 return javatz.inDaylightTime(date); 175 } 176 177 /* (non-Javadoc) 178 * @see com.ibm.icu.util.TimeZone#setRawOffset(int) 179 */ 180 @Override 181 public void setRawOffset(int offsetMillis) { 182 if (isFrozen()) { 183 throw new UnsupportedOperationException("Attempt to modify a frozen JavaTimeZone instance."); 184 } 185 javatz.setRawOffset(offsetMillis); 186 } 187 188 /* (non-Javadoc) 189 * @see com.ibm.icu.util.TimeZone#useDaylightTime() 190 */ 191 @Override 192 public boolean useDaylightTime() { 193 return javatz.useDaylightTime(); 194 } 195 196 /* (non-Javadoc) 197 * @see com.ibm.icu.util.TimeZone#observesDaylightTime() 198 */ 199 @Override 200 public boolean observesDaylightTime() { 201 if (mObservesDaylightTime != null) { 202 // Java 7+ 203 try { 204 return (Boolean)mObservesDaylightTime.invoke(javatz, (Object[]) null); 205 } catch (IllegalAccessException e) { 206 } catch (IllegalArgumentException e) { 207 } catch (InvocationTargetException e) { 208 } 209 } 210 return super.observesDaylightTime(); 211 } 212 213 /* (non-Javadoc) 214 * @see com.ibm.icu.util.TimeZone#getDSTSavings() 215 */ 216 @Override 217 public int getDSTSavings() { 218 return javatz.getDSTSavings(); 219 } 220 221 public java.util.TimeZone unwrap() { 222 return javatz; 223 } 224 225 /* (non-Javadoc) 226 * @see com.ibm.icu.util.TimeZone#clone() 227 */ 228 @Override 229 public Object clone() { 230 if (isFrozen()) { 231 return this; 232 } 233 return cloneAsThawed(); 234 } 235 236 /* (non-Javadoc) 237 * @see com.ibm.icu.util.TimeZone#hashCode() 238 */ 239 @Override 240 public int hashCode() { 241 return super.hashCode() + javatz.hashCode(); 242 } 243 244 private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { 245 s.defaultReadObject(); 246 javacal = new java.util.GregorianCalendar(javatz); 247 } 248 249 // Freezable stuffs 250 private transient volatile boolean isFrozen = false; 251 252 /* (non-Javadoc) 253 * @see com.ibm.icu.util.TimeZone#isFrozen() 254 */ 255 @Override 256 public boolean isFrozen() { 257 return isFrozen; 258 } 259 260 /* (non-Javadoc) 261 * @see com.ibm.icu.util.TimeZone#freeze() 262 */ 263 @Override 264 public TimeZone freeze() { 265 isFrozen = true; 266 return this; 267 } 268 269 /* (non-Javadoc) 270 * @see com.ibm.icu.util.TimeZone#cloneAsThawed() 271 */ 272 @Override 273 public TimeZone cloneAsThawed() { 274 JavaTimeZone tz = (JavaTimeZone)super.cloneAsThawed(); 275 tz.javatz = (java.util.TimeZone)javatz.clone(); 276 tz.javacal = new java.util.GregorianCalendar(javatz); // easier than synchronized javacal.clone() 277 tz.isFrozen = false; 278 return tz; 279 } 280 281 } 282