001 /* 002 * Copyright 2001-2005 Stephen Colebourne 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.joda.time.tz; 017 018 import org.joda.time.DateTimeZone; 019 020 /** 021 * Improves the performance of requesting time zone offsets and name keys by 022 * caching the results. Time zones that have simple rules or are fixed should 023 * not be cached, as it is unlikely to improve performance. 024 * <p> 025 * CachedDateTimeZone is thread-safe and immutable. 026 * 027 * @author Brian S O'Neill 028 * @since 1.0 029 */ 030 public class CachedDateTimeZone extends DateTimeZone { 031 032 private static final long serialVersionUID = 5472298452022250685L; 033 034 private static final int cInfoCacheMask; 035 036 static { 037 Integer i; 038 try { 039 i = Integer.getInteger("org.joda.time.tz.CachedDateTimeZone.size"); 040 } catch (SecurityException e) { 041 i = null; 042 } 043 044 int cacheSize; 045 if (i == null) { 046 // With a cache size of 512, dates that lie within any 69.7 year 047 // period have no cache collisions. 048 cacheSize = 512; // (1 << 9) 049 } else { 050 cacheSize = i.intValue(); 051 // Ensure cache size is even power of 2. 052 cacheSize--; 053 int shift = 0; 054 while (cacheSize > 0) { 055 shift++; 056 cacheSize >>= 1; 057 } 058 cacheSize = 1 << shift; 059 } 060 061 cInfoCacheMask = cacheSize - 1; 062 } 063 064 /** 065 * Returns a new CachedDateTimeZone unless given zone is already cached. 066 */ 067 public static CachedDateTimeZone forZone(DateTimeZone zone) { 068 if (zone instanceof CachedDateTimeZone) { 069 return (CachedDateTimeZone)zone; 070 } 071 return new CachedDateTimeZone(zone); 072 } 073 074 /* 075 * Caching is performed by breaking timeline down into periods of 2^32 076 * milliseconds, or about 49.7 days. A year has about 7.3 periods, usually 077 * with only 2 time zone offset periods. Most of the 49.7 day periods will 078 * have no transition, about one quarter have one transition, and very rare 079 * cases have multiple transitions. 080 */ 081 082 private final DateTimeZone iZone; 083 084 private transient Info[] iInfoCache; 085 086 private CachedDateTimeZone(DateTimeZone zone) { 087 super(zone.getID()); 088 iZone = zone; 089 iInfoCache = new Info[cInfoCacheMask + 1]; 090 } 091 092 private void readObject(java.io.ObjectInputStream in) 093 throws java.io.IOException, ClassNotFoundException 094 { 095 in.defaultReadObject(); 096 iInfoCache = new Info[cInfoCacheMask + 1]; 097 } 098 099 /** 100 * Returns the DateTimeZone being wrapped. 101 */ 102 public DateTimeZone getUncachedZone() { 103 return iZone; 104 } 105 106 public String getNameKey(long instant) { 107 return getInfo(instant).getNameKey(instant); 108 } 109 110 public int getOffset(long instant) { 111 return getInfo(instant).getOffset(instant); 112 } 113 114 public int getStandardOffset(long instant) { 115 return getInfo(instant).getStandardOffset(instant); 116 } 117 118 public boolean isFixed() { 119 return iZone.isFixed(); 120 } 121 122 public long nextTransition(long instant) { 123 return iZone.nextTransition(instant); 124 } 125 126 public long previousTransition(long instant) { 127 return iZone.previousTransition(instant); 128 } 129 130 public int hashCode() { 131 return iZone.hashCode(); 132 } 133 134 public boolean equals(Object obj) { 135 if (this == obj) { 136 return true; 137 } 138 if (obj instanceof CachedDateTimeZone) { 139 return iZone.equals(((CachedDateTimeZone)obj).iZone); 140 } 141 return false; 142 } 143 144 // Although accessed by multiple threads, this method doesn't need to be 145 // synchronized. 146 147 private Info getInfo(long millis) { 148 int period = (int)(millis >> 32); 149 Info[] cache = iInfoCache; 150 int index = period & cInfoCacheMask; 151 Info info = cache[index]; 152 if (info == null || (int)((info.iPeriodStart >> 32)) != period) { 153 info = createInfo(millis); 154 cache[index] = info; 155 } 156 return info; 157 } 158 159 private Info createInfo(long millis) { 160 long periodStart = millis & (0xffffffffL << 32); 161 Info info = new Info(iZone, periodStart); 162 163 long end = periodStart | 0xffffffffL; 164 Info chain = info; 165 while (true) { 166 long next = iZone.nextTransition(periodStart); 167 if (next == periodStart || next > end) { 168 break; 169 } 170 periodStart = next; 171 chain = (chain.iNextInfo = new Info(iZone, periodStart)); 172 } 173 174 return info; 175 } 176 177 private final static class Info { 178 // For first Info in chain, iPeriodStart's lower 32 bits are clear. 179 public final long iPeriodStart; 180 public final DateTimeZone iZoneRef; 181 182 Info iNextInfo; 183 184 private String iNameKey; 185 private int iOffset = Integer.MIN_VALUE; 186 private int iStandardOffset = Integer.MIN_VALUE; 187 188 Info(DateTimeZone zone, long periodStart) { 189 iPeriodStart = periodStart; 190 iZoneRef = zone; 191 } 192 193 public String getNameKey(long millis) { 194 if (iNextInfo == null || millis < iNextInfo.iPeriodStart) { 195 if (iNameKey == null) { 196 iNameKey = iZoneRef.getNameKey(iPeriodStart); 197 } 198 return iNameKey; 199 } 200 return iNextInfo.getNameKey(millis); 201 } 202 203 public int getOffset(long millis) { 204 if (iNextInfo == null || millis < iNextInfo.iPeriodStart) { 205 if (iOffset == Integer.MIN_VALUE) { 206 iOffset = iZoneRef.getOffset(iPeriodStart); 207 } 208 return iOffset; 209 } 210 return iNextInfo.getOffset(millis); 211 } 212 213 public int getStandardOffset(long millis) { 214 if (iNextInfo == null || millis < iNextInfo.iPeriodStart) { 215 if (iStandardOffset == Integer.MIN_VALUE) { 216 iStandardOffset = iZoneRef.getStandardOffset(iPeriodStart); 217 } 218 return iStandardOffset; 219 } 220 return iNextInfo.getStandardOffset(millis); 221 } 222 } 223 }