View Javadoc

1   /*
2    *  Copyright 2001-2012 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.time.tz;
17  
18  import org.joda.time.DateTimeZone;
19  
20  /**
21   * Improves the performance of requesting time zone offsets and name keys by
22   * caching the results. Time zones that have simple rules or are fixed should
23   * not be cached, as it is unlikely to improve performance.
24   * <p>
25   * CachedDateTimeZone is thread-safe and immutable.
26   * 
27   * @author Brian S O'Neill
28   * @since 1.0
29   */
30  public class CachedDateTimeZone extends DateTimeZone {
31  
32      private static final long serialVersionUID = 5472298452022250685L;
33  
34      private static final int cInfoCacheMask;
35  
36      static {
37          Integer i;
38          try {
39              i = Integer.getInteger("org.joda.time.tz.CachedDateTimeZone.size");
40          } catch (SecurityException e) {
41              i = null;
42          }
43  
44          int cacheSize;
45          if (i == null) {
46              // With a cache size of 512, dates that lie within any 69.7 year
47              // period have no cache collisions.
48              cacheSize = 512; // (1 << 9)
49          } else {
50              cacheSize = i.intValue();
51              // Ensure cache size is even power of 2.
52              cacheSize--;
53              int shift = 0;
54              while (cacheSize > 0) {
55                  shift++;
56                  cacheSize >>= 1;
57              }
58              cacheSize = 1 << shift;
59          }
60  
61          cInfoCacheMask = cacheSize - 1;
62      }
63  
64      /**
65       * Returns a new CachedDateTimeZone unless given zone is already cached.
66       */
67      public static CachedDateTimeZone forZone(DateTimeZone zone) {
68          if (zone instanceof CachedDateTimeZone) {
69              return (CachedDateTimeZone)zone;
70          }
71          return new CachedDateTimeZone(zone);
72      }
73  
74      /*
75       * Caching is performed by breaking timeline down into periods of 2^32
76       * milliseconds, or about 49.7 days. A year has about 7.3 periods, usually
77       * with only 2 time zone offset periods. Most of the 49.7 day periods will
78       * have no transition, about one quarter have one transition, and very rare
79       * cases have multiple transitions.
80       */
81  
82      private final DateTimeZone iZone;
83  
84      private final Info[] iInfoCache = new Info[cInfoCacheMask + 1];
85  
86      private CachedDateTimeZone(DateTimeZone zone) {
87          super(zone.getID());
88          iZone = zone;
89      }
90  
91      /**
92       * Returns the DateTimeZone being wrapped.
93       */
94      public DateTimeZone getUncachedZone() {
95          return iZone;
96      }
97  
98      public String getNameKey(long instant) {
99          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 }