001 /* 002 * Copyright 2001-2012 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 final Info[] iInfoCache = new Info[cInfoCacheMask + 1]; 085 086 private CachedDateTimeZone(DateTimeZone zone) { 087 super(zone.getID()); 088 iZone = zone; 089 } 090 091 /** 092 * Returns the DateTimeZone being wrapped. 093 */ 094 public DateTimeZone getUncachedZone() { 095 return iZone; 096 } 097 098 public String getNameKey(long instant) { 099 return getInfo(instant).getNameKey(instant); 100 } 101 102 public int getOffset(long instant) { 103 return getInfo(instant).getOffset(instant); 104 } 105 106 public int getStandardOffset(long instant) { 107 return getInfo(instant).getStandardOffset(instant); 108 } 109 110 public boolean isFixed() { 111 return iZone.isFixed(); 112 } 113 114 public long nextTransition(long instant) { 115 return iZone.nextTransition(instant); 116 } 117 118 public long previousTransition(long instant) { 119 return iZone.previousTransition(instant); 120 } 121 122 public int hashCode() { 123 return iZone.hashCode(); 124 } 125 126 public boolean equals(Object obj) { 127 if (this == obj) { 128 return true; 129 } 130 if (obj instanceof CachedDateTimeZone) { 131 return iZone.equals(((CachedDateTimeZone)obj).iZone); 132 } 133 return false; 134 } 135 136 // Although accessed by multiple threads, this method doesn't need to be 137 // synchronized. 138 139 private Info getInfo(long millis) { 140 int period = (int)(millis >> 32); 141 Info[] cache = iInfoCache; 142 int index = period & cInfoCacheMask; 143 Info info = cache[index]; 144 if (info == null || (int)((info.iPeriodStart >> 32)) != period) { 145 info = createInfo(millis); 146 cache[index] = info; 147 } 148 return info; 149 } 150 151 private Info createInfo(long millis) { 152 long periodStart = millis & (0xffffffffL << 32); 153 Info info = new Info(iZone, periodStart); 154 155 long end = periodStart | 0xffffffffL; 156 Info chain = info; 157 while (true) { 158 long next = iZone.nextTransition(periodStart); 159 if (next == periodStart || next > end) { 160 break; 161 } 162 periodStart = next; 163 chain = (chain.iNextInfo = new Info(iZone, periodStart)); 164 } 165 166 return info; 167 } 168 169 private final static class Info { 170 // For first Info in chain, iPeriodStart's lower 32 bits are clear. 171 public final long iPeriodStart; 172 public final DateTimeZone iZoneRef; 173 174 Info iNextInfo; 175 176 private String iNameKey; 177 private int iOffset = Integer.MIN_VALUE; 178 private int iStandardOffset = Integer.MIN_VALUE; 179 180 Info(DateTimeZone zone, long periodStart) { 181 iPeriodStart = periodStart; 182 iZoneRef = zone; 183 } 184 185 public String getNameKey(long millis) { 186 if (iNextInfo == null || millis < iNextInfo.iPeriodStart) { 187 if (iNameKey == null) { 188 iNameKey = iZoneRef.getNameKey(iPeriodStart); 189 } 190 return iNameKey; 191 } 192 return iNextInfo.getNameKey(millis); 193 } 194 195 public int getOffset(long millis) { 196 if (iNextInfo == null || millis < iNextInfo.iPeriodStart) { 197 if (iOffset == Integer.MIN_VALUE) { 198 iOffset = iZoneRef.getOffset(iPeriodStart); 199 } 200 return iOffset; 201 } 202 return iNextInfo.getOffset(millis); 203 } 204 205 public int getStandardOffset(long millis) { 206 if (iNextInfo == null || millis < iNextInfo.iPeriodStart) { 207 if (iStandardOffset == Integer.MIN_VALUE) { 208 iStandardOffset = iZoneRef.getStandardOffset(iPeriodStart); 209 } 210 return iStandardOffset; 211 } 212 return iNextInfo.getStandardOffset(millis); 213 } 214 } 215 }