EMMA Coverage Report (generated Tue Oct 28 00:01:11 GMT 2008)
[all classes][org.joda.time.tz]

COVERAGE SUMMARY FOR SOURCE FILE [DateTimeZoneBuilder.java]

nameclass, %method, %block, %line, %
DateTimeZoneBuilder.java100% (8/8)92%  (81/88)82%  (2533/3106)81%  (555.6/690)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class DateTimeZoneBuilder$PrecalculatedZone100% (1/1)100% (12/12)72%  (714/989)75%  (149.6/200)
isCachable (): boolean 100% (1/1)8%   (5/65)11%  (2/18)
create (String, boolean, ArrayList, DateTimeZoneBuilder$DSTZone): DateTimeZon... 100% (1/1)55%  (185/334)71%  (32.7/46)
previousTransition (long): long 100% (1/1)63%  (47/75)62%  (13/21)
getStandardOffset (long): int 100% (1/1)81%  (39/48)83%  (10/12)
readFrom (DataInput, String): DateTimeZoneBuilder$PrecalculatedZone 100% (1/1)91%  (90/99)88%  (21/24)
writeTo (DataOutput): void 100% (1/1)93%  (132/142)90%  (26.9/30)
equals (Object): boolean 100% (1/1)93%  (57/61)67%  (4/6)
getNameKey (long): String 100% (1/1)96%  (46/48)92%  (11/12)
getOffset (long): int 100% (1/1)96%  (46/48)92%  (11/12)
nextTransition (long): long 100% (1/1)96%  (46/48)91%  (10/11)
DateTimeZoneBuilder$PrecalculatedZone (String, long [], int [], int [], Strin... 100% (1/1)100% (19/19)100% (7/7)
isFixed (): boolean 100% (1/1)100% (2/2)100% (1/1)
     
class DateTimeZoneBuilder$Recurrence100% (1/1)73%  (8/11)74%  (86/116)78%  (18/23)
getOfYear (): DateTimeZoneBuilder$OfYear 0%   (0/1)0%   (0/3)0%   (0/1)
rename (String): DateTimeZoneBuilder$Recurrence 0%   (0/1)0%   (0/9)0%   (0/1)
renameAppend (String): DateTimeZoneBuilder$Recurrence 0%   (0/1)0%   (0/13)0%   (0/1)
equals (Object): boolean 100% (1/1)85%  (29/34)66%  (4/6)
DateTimeZoneBuilder$Recurrence (DateTimeZoneBuilder$OfYear, String, int): void 100% (1/1)100% (12/12)100% (5/5)
getNameKey (): String 100% (1/1)100% (3/3)100% (1/1)
getSaveMillis (): int 100% (1/1)100% (3/3)100% (1/1)
next (long, int, int): long 100% (1/1)100% (7/7)100% (1/1)
previous (long, int, int): long 100% (1/1)100% (7/7)100% (1/1)
readFrom (DataInput): DateTimeZoneBuilder$Recurrence 100% (1/1)100% (11/11)100% (1/1)
writeTo (DataOutput): void 100% (1/1)100% (14/14)100% (4/4)
     
class DateTimeZoneBuilder$OfYear100% (1/1)100% (11/11)78%  (423/539)77%  (89/115)
setDayOfMonthNext (Chronology, long): long 100% (1/1)22%  (8/37)33%  (3/9)
setDayOfMonthPrevious (Chronology, long): long 100% (1/1)22%  (8/37)33%  (3/9)
DateTimeZoneBuilder$OfYear (char, int, int, int, boolean, int): void 100% (1/1)71%  (30/42)90%  (9/10)
setInstant (int, int, int): long 100% (1/1)76%  (44/58)69%  (9/13)
previous (long, int, int): long 100% (1/1)76%  (81/106)73%  (16/22)
equals (Object): boolean 100% (1/1)89%  (42/47)66%  (4/6)
setDayOfWeek (Chronology, long): long 100% (1/1)93%  (28/30)90%  (9/10)
next (long, int, int): long 100% (1/1)100% (106/106)100% (22/22)
readFrom (DataInput): DateTimeZoneBuilder$OfYear 100% (1/1)100% (18/18)100% (1/1)
setDayOfMonth (Chronology, long): long 100% (1/1)100% (32/32)100% (6/6)
writeTo (DataOutput): void 100% (1/1)100% (26/26)100% (7/7)
     
class DateTimeZoneBuilder$DSTZone100% (1/1)91%  (10/11)80%  (227/284)61%  (46.8/77)
isFixed (): boolean 0%   (0/1)0%   (0/2)0%   (0/1)
previousTransition (long): long 100% (1/1)64%  (47/73)43%  (9/21)
findMatchingRecurrence (long): DateTimeZoneBuilder$Recurrence 100% (1/1)79%  (37/47)60%  (9.7/16)
nextTransition (long): long 100% (1/1)79%  (53/67)56%  (11.2/20)
equals (Object): boolean 100% (1/1)88%  (35/40)66%  (4/6)
DateTimeZoneBuilder$DSTZone (String, int, DateTimeZoneBuilder$Recurrence, Dat... 100% (1/1)100% (13/13)100% (5/5)
getNameKey (long): String 100% (1/1)100% (5/5)100% (1/1)
getOffset (long): int 100% (1/1)100% (8/8)100% (1/1)
getStandardOffset (long): int 100% (1/1)100% (3/3)100% (1/1)
readFrom (DataInput, String): DateTimeZoneBuilder$DSTZone 100% (1/1)100% (12/12)100% (1/1)
writeTo (DataOutput): void 100% (1/1)100% (14/14)100% (4/4)
     
class DateTimeZoneBuilder100% (1/1)100% (15/15)90%  (597/661)89%  (125.2/141)
buildFixedZone (String, String, int, int): DateTimeZone 100% (1/1)55%  (12/22)44%  (1.3/3)
writeTo (String, OutputStream): void 100% (1/1)65%  (11/17)75%  (3/4)
readFrom (InputStream, String): DateTimeZone 100% (1/1)67%  (10/15)67%  (2/3)
readFrom (DataInput, String): DateTimeZone 100% (1/1)71%  (27/38)62%  (5/8)
addTransition (ArrayList, DateTimeZoneBuilder$Transition): boolean 100% (1/1)85%  (62/73)89%  (16/18)
toDateTimeZone (String, boolean): DateTimeZone 100% (1/1)88%  (123/140)83%  (30/36)
writeTo (String, DataOutput): void 100% (1/1)94%  (45/48)92%  (11/12)
writeMillis (DataOutput, long): void 100% (1/1)99%  (99/100)100% (18.9/19)
DateTimeZoneBuilder (): void 100% (1/1)100% (9/9)100% (3/3)
addCutover (int, char, int, int, int, boolean, int): DateTimeZoneBuilder 100% (1/1)100% (37/37)100% (6/6)
addRecurringSavings (String, int, int, int, char, int, int, int, boolean, int... 100% (1/1)100% (33/33)100% (6/6)
getLastRuleSet (): DateTimeZoneBuilder$RuleSet 100% (1/1)100% (24/24)100% (3/3)
readMillis (DataInput): long 100% (1/1)100% (92/92)100% (16/16)
setFixedSavings (String, int): DateTimeZoneBuilder 100% (1/1)100% (7/7)100% (2/2)
setStandardOffset (int): DateTimeZoneBuilder 100% (1/1)100% (6/6)100% (2/2)
     
class DateTimeZoneBuilder$RuleSet100% (1/1)92%  (11/12)93%  (300/324)94%  (78/83)
getStandardOffset (): int 0%   (0/1)0%   (0/3)0%   (0/1)
firstTransition (long): DateTimeZoneBuilder$Transition 100% (1/1)82%  (89/108)88%  (22/25)
nextTransition (long, int): DateTimeZoneBuilder$Transition 100% (1/1)97%  (77/79)95%  (21/22)
<static initializer> 100% (1/1)100% (10/10)100% (3/3)
DateTimeZoneBuilder$RuleSet (): void 100% (1/1)100% (12/12)100% (4/4)
DateTimeZoneBuilder$RuleSet (DateTimeZoneBuilder$RuleSet): void 100% (1/1)100% (30/30)100% (8/8)
addRule (DateTimeZoneBuilder$Rule): void 100% (1/1)100% (11/11)100% (3/3)
buildTailZone (String): DateTimeZoneBuilder$DSTZone 100% (1/1)100% (38/38)100% (6/6)
getUpperLimit (int): long 100% (1/1)100% (15/15)100% (3/3)
setFixedSavings (String, int): void 100% (1/1)100% (7/7)100% (3/3)
setStandardOffset (int): void 100% (1/1)100% (4/4)100% (2/2)
setUpperLimit (int, DateTimeZoneBuilder$OfYear): void 100% (1/1)100% (7/7)100% (3/3)
     
class DateTimeZoneBuilder$Rule100% (1/1)71%  (5/7)93%  (91/98)92%  (23/25)
getFromYear (): int 0%   (0/1)0%   (0/3)0%   (0/1)
getOfYear (): DateTimeZoneBuilder$OfYear 0%   (0/1)0%   (0/4)0%   (0/1)
DateTimeZoneBuilder$Rule (DateTimeZoneBuilder$Recurrence, int, int): void 100% (1/1)100% (12/12)100% (5/5)
getNameKey (): String 100% (1/1)100% (4/4)100% (1/1)
getSaveMillis (): int 100% (1/1)100% (4/4)100% (1/1)
getToYear (): int 100% (1/1)100% (3/3)100% (1/1)
next (long, int, int): long 100% (1/1)100% (68/68)100% (15/15)
     
class DateTimeZoneBuilder$Transition100% (1/1)100% (9/9)100% (95/95)100% (26/26)
DateTimeZoneBuilder$Transition (long, DateTimeZoneBuilder$Rule, int): void 100% (1/1)100% (19/19)100% (6/6)
DateTimeZoneBuilder$Transition (long, DateTimeZoneBuilder$Transition): void 100% (1/1)100% (18/18)100% (6/6)
DateTimeZoneBuilder$Transition (long, String, int, int): void 100% (1/1)100% (15/15)100% (6/6)
getMillis (): long 100% (1/1)100% (3/3)100% (1/1)
getNameKey (): String 100% (1/1)100% (3/3)100% (1/1)
getSaveMillis (): int 100% (1/1)100% (6/6)100% (1/1)
getStandardOffset (): int 100% (1/1)100% (3/3)100% (1/1)
getWallOffset (): int 100% (1/1)100% (3/3)100% (1/1)
isTransitionFrom (DateTimeZoneBuilder$Transition): boolean 100% (1/1)100% (25/25)100% (3/3)

1/*
2 *  Copyright 2001-2005 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 */
16package org.joda.time.tz;
17 
18import java.io.DataInput;
19import java.io.DataInputStream;
20import java.io.DataOutput;
21import java.io.DataOutputStream;
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.OutputStream;
25import java.text.DateFormatSymbols;
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.HashSet;
29import java.util.Iterator;
30import java.util.Locale;
31import java.util.Set;
32 
33import org.joda.time.Chronology;
34import org.joda.time.DateTime;
35import org.joda.time.DateTimeUtils;
36import org.joda.time.DateTimeZone;
37import org.joda.time.Period;
38import org.joda.time.PeriodType;
39import org.joda.time.chrono.ISOChronology;
40 
41/**
42 * DateTimeZoneBuilder allows complex DateTimeZones to be constructed. Since
43 * creating a new DateTimeZone this way is a relatively expensive operation,
44 * built zones can be written to a file. Reading back the encoded data is a
45 * quick operation.
46 * <p>
47 * DateTimeZoneBuilder itself is mutable and not thread-safe, but the
48 * DateTimeZone objects that it builds are thread-safe and immutable.
49 * <p>
50 * It is intended that {@link ZoneInfoCompiler} be used to read time zone data
51 * files, indirectly calling DateTimeZoneBuilder. The following complex
52 * example defines the America/Los_Angeles time zone, with all historical
53 * transitions:
54 * 
55 * <pre>
56 * DateTimeZone America_Los_Angeles = new DateTimeZoneBuilder()
57 *     .addCutover(-2147483648, 'w', 1, 1, 0, false, 0)
58 *     .setStandardOffset(-28378000)
59 *     .setFixedSavings("LMT", 0)
60 *     .addCutover(1883, 'w', 11, 18, 0, false, 43200000)
61 *     .setStandardOffset(-28800000)
62 *     .addRecurringSavings("PDT", 3600000, 1918, 1919, 'w',  3, -1, 7, false, 7200000)
63 *     .addRecurringSavings("PST",       0, 1918, 1919, 'w', 10, -1, 7, false, 7200000)
64 *     .addRecurringSavings("PWT", 3600000, 1942, 1942, 'w',  2,  9, 0, false, 7200000)
65 *     .addRecurringSavings("PPT", 3600000, 1945, 1945, 'u',  8, 14, 0, false, 82800000)
66 *     .addRecurringSavings("PST",       0, 1945, 1945, 'w',  9, 30, 0, false, 7200000)
67 *     .addRecurringSavings("PDT", 3600000, 1948, 1948, 'w',  3, 14, 0, false, 7200000)
68 *     .addRecurringSavings("PST",       0, 1949, 1949, 'w',  1,  1, 0, false, 7200000)
69 *     .addRecurringSavings("PDT", 3600000, 1950, 1966, 'w',  4, -1, 7, false, 7200000)
70 *     .addRecurringSavings("PST",       0, 1950, 1961, 'w',  9, -1, 7, false, 7200000)
71 *     .addRecurringSavings("PST",       0, 1962, 1966, 'w', 10, -1, 7, false, 7200000)
72 *     .addRecurringSavings("PST",       0, 1967, 2147483647, 'w', 10, -1, 7, false, 7200000)
73 *     .addRecurringSavings("PDT", 3600000, 1967, 1973, 'w', 4, -1,  7, false, 7200000)
74 *     .addRecurringSavings("PDT", 3600000, 1974, 1974, 'w', 1,  6,  0, false, 7200000)
75 *     .addRecurringSavings("PDT", 3600000, 1975, 1975, 'w', 2, 23,  0, false, 7200000)
76 *     .addRecurringSavings("PDT", 3600000, 1976, 1986, 'w', 4, -1,  7, false, 7200000)
77 *     .addRecurringSavings("PDT", 3600000, 1987, 2147483647, 'w', 4, 1, 7, true, 7200000)
78 *     .toDateTimeZone("America/Los_Angeles");
79 * </pre>
80 *
81 * @author Brian S O'Neill
82 * @see ZoneInfoCompiler
83 * @see ZoneInfoProvider
84 * @since 1.0
85 */
86public class DateTimeZoneBuilder {
87    /**
88     * Decodes a built DateTimeZone from the given stream, as encoded by
89     * writeTo.
90     *
91     * @param in input stream to read encoded DateTimeZone from.
92     * @param id time zone id to assign
93     */
94    public static DateTimeZone readFrom(InputStream in, String id) throws IOException {
95        if (in instanceof DataInput) {
96            return readFrom((DataInput)in, id);
97        } else {
98            return readFrom((DataInput)new DataInputStream(in), id);
99        }
100    }
101 
102    /**
103     * Decodes a built DateTimeZone from the given stream, as encoded by
104     * writeTo.
105     *
106     * @param in input stream to read encoded DateTimeZone from.
107     * @param id time zone id to assign
108     */
109    public static DateTimeZone readFrom(DataInput in, String id) throws IOException {
110        switch (in.readUnsignedByte()) {
111        case 'F':
112            DateTimeZone fixed = new FixedDateTimeZone
113                (id, in.readUTF(), (int)readMillis(in), (int)readMillis(in));
114            if (fixed.equals(DateTimeZone.UTC)) {
115                fixed = DateTimeZone.UTC;
116            }
117            return fixed;
118        case 'C':
119            return CachedDateTimeZone.forZone(PrecalculatedZone.readFrom(in, id));
120        case 'P':
121            return PrecalculatedZone.readFrom(in, id);
122        default:
123            throw new IOException("Invalid encoding");
124        }
125    }
126 
127    /**
128     * Millisecond encoding formats:
129     *
130     * upper two bits  units       field length  approximate range
131     * ---------------------------------------------------------------
132     * 00              30 minutes  1 byte        +/- 16 hours
133     * 01              minutes     4 bytes       +/- 1020 years
134     * 10              seconds     5 bytes       +/- 4355 years
135     * 11              millis      9 bytes       +/- 292,000,000 years
136     *
137     * Remaining bits in field form signed offset from 1970-01-01T00:00:00Z.
138     */
139    static void writeMillis(DataOutput out, long millis) throws IOException {
140        if (millis % (30 * 60000L) == 0) {
141            // Try to write in 30 minute units.
142            long units = millis / (30 * 60000L);
143            if (((units << (64 - 6)) >> (64 - 6)) == units) {
144                // Form 00 (6 bits effective precision)
145                out.writeByte((int)(units & 0x3f));
146                return;
147            }
148        }
149 
150        if (millis % 60000L == 0) {
151            // Try to write minutes.
152            long minutes = millis / 60000L;
153            if (((minutes << (64 - 30)) >> (64 - 30)) == minutes) {
154                // Form 01 (30 bits effective precision)
155                out.writeInt(0x40000000 | (int)(minutes & 0x3fffffff));
156                return;
157            }
158        }
159        
160        if (millis % 1000L == 0) {
161            // Try to write seconds.
162            long seconds = millis / 1000L;
163            if (((seconds << (64 - 38)) >> (64 - 38)) == seconds) {
164                // Form 10 (38 bits effective precision)
165                out.writeByte(0x80 | (int)((seconds >> 32) & 0x3f));
166                out.writeInt((int)(seconds & 0xffffffff));
167                return;
168            }
169        }
170 
171        // Write milliseconds either because the additional precision is
172        // required or the minutes didn't fit in the field.
173        
174        // Form 11 (64 bits effective precision, but write as if 70 bits)
175        out.writeByte(millis < 0 ? 0xff : 0xc0);
176        out.writeLong(millis);
177    }
178 
179    /**
180     * Reads encoding generated by writeMillis.
181     */
182    static long readMillis(DataInput in) throws IOException {
183        int v = in.readUnsignedByte();
184        switch (v >> 6) {
185        case 0: default:
186            // Form 00 (6 bits effective precision)
187            v = (v << (32 - 6)) >> (32 - 6);
188            return v * (30 * 60000L);
189 
190        case 1:
191            // Form 01 (30 bits effective precision)
192            v = (v << (32 - 6)) >> (32 - 30);
193            v |= (in.readUnsignedByte()) << 16;
194            v |= (in.readUnsignedByte()) << 8;
195            v |= (in.readUnsignedByte());
196            return v * 60000L;
197 
198        case 2:
199            // Form 10 (38 bits effective precision)
200            long w = (((long)v) << (64 - 6)) >> (64 - 38);
201            w |= (in.readUnsignedByte()) << 24;
202            w |= (in.readUnsignedByte()) << 16;
203            w |= (in.readUnsignedByte()) << 8;
204            w |= (in.readUnsignedByte());
205            return w * 1000L;
206 
207        case 3:
208            // Form 11 (64 bits effective precision)
209            return in.readLong();
210        }
211    }
212 
213    private static DateTimeZone buildFixedZone(String id, String nameKey,
214                                               int wallOffset, int standardOffset) {
215        if ("UTC".equals(id) && id.equals(nameKey) &&
216            wallOffset == 0 && standardOffset == 0) {
217            return DateTimeZone.UTC;
218        }
219        return new FixedDateTimeZone(id, nameKey, wallOffset, standardOffset);
220    }
221 
222    // List of RuleSets.
223    private final ArrayList iRuleSets;
224 
225    public DateTimeZoneBuilder() {
226        iRuleSets = new ArrayList(10);
227    }
228 
229    /**
230     * Adds a cutover for added rules. The standard offset at the cutover
231     * defaults to 0. Call setStandardOffset afterwards to change it.
232     *
233     * @param year year of cutover
234     * @param mode 'u' - cutover is measured against UTC, 'w' - against wall
235     * offset, 's' - against standard offset.
236     * @param dayOfMonth if negative, set to ((last day of month) - ~dayOfMonth).
237     * For example, if -1, set to last day of month
238     * @param dayOfWeek if 0, ignore
239     * @param advanceDayOfWeek if dayOfMonth does not fall on dayOfWeek, advance to
240     * dayOfWeek when true, retreat when false.
241     * @param millisOfDay additional precision for specifying time of day of
242     * cutover
243     */
244    public DateTimeZoneBuilder addCutover(int year,
245                                          char mode,
246                                          int monthOfYear,
247                                          int dayOfMonth,
248                                          int dayOfWeek,
249                                          boolean advanceDayOfWeek,
250                                          int millisOfDay)
251    {
252        OfYear ofYear = new OfYear
253            (mode, monthOfYear, dayOfMonth, dayOfWeek, advanceDayOfWeek, millisOfDay);
254        if (iRuleSets.size() > 0) {
255            RuleSet lastRuleSet = (RuleSet)iRuleSets.get(iRuleSets.size() - 1);
256            lastRuleSet.setUpperLimit(year, ofYear);
257        }
258        iRuleSets.add(new RuleSet());
259        return this;
260    }
261 
262    /**
263     * Sets the standard offset to use for newly added rules until the next
264     * cutover is added.
265     */
266    public DateTimeZoneBuilder setStandardOffset(int standardOffset) {
267        getLastRuleSet().setStandardOffset(standardOffset);
268        return this;
269    }
270 
271    /**
272     * Set a fixed savings rule at the cutover.
273     */
274    public DateTimeZoneBuilder setFixedSavings(String nameKey, int saveMillis) {
275        getLastRuleSet().setFixedSavings(nameKey, saveMillis);
276        return this;
277    }
278 
279    /**
280     * Add a recurring daylight saving time rule.
281     *
282     * @param nameKey name key of new rule
283     * @param saveMillis milliseconds to add to standard offset
284     * @param fromYear First year that rule is in effect. MIN_VALUE indicates
285     * beginning of time.
286     * @param toYear Last year (inclusive) that rule is in effect. MAX_VALUE
287     * indicates end of time.
288     * @param mode 'u' - transitions are calculated against UTC, 'w' -
289     * transitions are calculated against wall offset, 's' - transitions are
290     * calculated against standard offset.
291     * @param dayOfMonth if negative, set to ((last day of month) - ~dayOfMonth).
292     * For example, if -1, set to last day of month
293     * @param dayOfWeek if 0, ignore
294     * @param advanceDayOfWeek if dayOfMonth does not fall on dayOfWeek, advance to
295     * dayOfWeek when true, retreat when false.
296     * @param millisOfDay additional precision for specifying time of day of
297     * transitions
298     */
299    public DateTimeZoneBuilder addRecurringSavings(String nameKey, int saveMillis,
300                                                   int fromYear, int toYear,
301                                                   char mode,
302                                                   int monthOfYear,
303                                                   int dayOfMonth,
304                                                   int dayOfWeek,
305                                                   boolean advanceDayOfWeek,
306                                                   int millisOfDay)
307    {
308        if (fromYear <= toYear) {
309            OfYear ofYear = new OfYear
310                (mode, monthOfYear, dayOfMonth, dayOfWeek, advanceDayOfWeek, millisOfDay);
311            Recurrence recurrence = new Recurrence(ofYear, nameKey, saveMillis);
312            Rule rule = new Rule(recurrence, fromYear, toYear);
313            getLastRuleSet().addRule(rule);
314        }
315        return this;
316    }
317 
318    private RuleSet getLastRuleSet() {
319        if (iRuleSets.size() == 0) {
320            addCutover(Integer.MIN_VALUE, 'w', 1, 1, 0, false, 0);
321        }
322        return (RuleSet)iRuleSets.get(iRuleSets.size() - 1);
323    }
324    
325    /**
326     * Processes all the rules and builds a DateTimeZone.
327     *
328     * @param id  time zone id to assign
329     * @param outputID  true if the zone id should be output
330     */
331    public DateTimeZone toDateTimeZone(String id, boolean outputID) {
332        if (id == null) {
333            throw new IllegalArgumentException();
334        }
335 
336        // Discover where all the transitions occur and store the results in
337        // these lists.
338        ArrayList transitions = new ArrayList();
339 
340        // Tail zone picks up remaining transitions in the form of an endless
341        // DST cycle.
342        DSTZone tailZone = null;
343 
344        long millis = Long.MIN_VALUE;
345        int saveMillis = 0;
346            
347        int ruleSetCount = iRuleSets.size();
348        for (int i=0; i<ruleSetCount; i++) {
349            RuleSet rs = (RuleSet)iRuleSets.get(i);
350            Transition next = rs.firstTransition(millis);
351            if (next == null) {
352                continue;
353            }
354            addTransition(transitions, next);
355            millis = next.getMillis();
356            saveMillis = next.getSaveMillis();
357 
358            // Copy it since we're going to destroy it.
359            rs = new RuleSet(rs);
360 
361            while ((next = rs.nextTransition(millis, saveMillis)) != null) {
362                if (addTransition(transitions, next)) {
363                    if (tailZone != null) {
364                        // Got the extra transition before DSTZone.
365                        break;
366                    }
367                }
368                millis = next.getMillis();
369                saveMillis = next.getSaveMillis();
370                if (tailZone == null && i == ruleSetCount - 1) {
371                    tailZone = rs.buildTailZone(id);
372                    // If tailZone is not null, don't break out of main loop until
373                    // at least one more transition is calculated. This ensures a
374                    // correct 'seam' to the DSTZone.
375                }
376            }
377 
378            millis = rs.getUpperLimit(saveMillis);
379        }
380 
381        // Check if a simpler zone implementation can be returned.
382        if (transitions.size() == 0) {
383            if (tailZone != null) {
384                // This shouldn't happen, but handle just in case.
385                return tailZone;
386            }
387            return buildFixedZone(id, "UTC", 0, 0);
388        }
389        if (transitions.size() == 1 && tailZone == null) {
390            Transition tr = (Transition)transitions.get(0);
391            return buildFixedZone(id, tr.getNameKey(),
392                                  tr.getWallOffset(), tr.getStandardOffset());
393        }
394 
395        PrecalculatedZone zone = PrecalculatedZone.create(id, outputID, transitions, tailZone);
396        if (zone.isCachable()) {
397            return CachedDateTimeZone.forZone(zone);
398        }
399        return zone;
400    }
401 
402    private boolean addTransition(ArrayList transitions, Transition tr) {
403        int size = transitions.size();
404        if (size == 0) {
405            transitions.add(tr);
406            return true;
407        }
408 
409        Transition last = (Transition)transitions.get(size - 1);
410        if (!tr.isTransitionFrom(last)) {
411            return false;
412        }
413 
414        // If local time of new transition is same as last local time, just
415        // replace last transition with new one.
416        int offsetForLast = 0;
417        if (size >= 2) {
418            offsetForLast = ((Transition)transitions.get(size - 2)).getWallOffset();
419        }
420        int offsetForNew = last.getWallOffset();
421 
422        long lastLocal = last.getMillis() + offsetForLast;
423        long newLocal = tr.getMillis() + offsetForNew;
424 
425        if (newLocal != lastLocal) {
426            transitions.add(tr);
427            return true;
428        }
429 
430        transitions.remove(size - 1);
431        return addTransition(transitions, tr);
432    }
433 
434    /**
435     * Encodes a built DateTimeZone to the given stream. Call readFrom to
436     * decode the data into a DateTimeZone object.
437     *
438     * @param out output stream to receive encoded DateTimeZone.
439     * @since 1.5 (parameter added)
440     */
441    public void writeTo(String zoneID, OutputStream out) throws IOException {
442        if (out instanceof DataOutput) {
443            writeTo(zoneID, (DataOutput)out);
444        } else {
445            writeTo(zoneID, (DataOutput)new DataOutputStream(out));
446        }
447    }
448 
449    /**
450     * Encodes a built DateTimeZone to the given stream. Call readFrom to
451     * decode the data into a DateTimeZone object.
452     *
453     * @param out output stream to receive encoded DateTimeZone.
454     * @since 1.5 (parameter added)
455     */
456    public void writeTo(String zoneID, DataOutput out) throws IOException {
457        // pass false so zone id is not written out
458        DateTimeZone zone = toDateTimeZone(zoneID, false);
459 
460        if (zone instanceof FixedDateTimeZone) {
461            out.writeByte('F'); // 'F' for fixed
462            out.writeUTF(zone.getNameKey(0));
463            writeMillis(out, zone.getOffset(0));
464            writeMillis(out, zone.getStandardOffset(0));
465        } else {
466            if (zone instanceof CachedDateTimeZone) {
467                out.writeByte('C'); // 'C' for cached, precalculated
468                zone = ((CachedDateTimeZone)zone).getUncachedZone();
469            } else {
470                out.writeByte('P'); // 'P' for precalculated, uncached
471            }
472            ((PrecalculatedZone)zone).writeTo(out);
473        }
474    }
475 
476    /**
477     * Supports setting fields of year and moving between transitions.
478     */
479    private static final class OfYear {
480        static OfYear readFrom(DataInput in) throws IOException {
481            return new OfYear((char)in.readUnsignedByte(),
482                              (int)in.readUnsignedByte(),
483                              (int)in.readByte(),
484                              (int)in.readUnsignedByte(),
485                              in.readBoolean(),
486                              (int)readMillis(in));
487        }
488 
489        // Is 'u', 'w', or 's'.
490        final char iMode;
491 
492        final int iMonthOfYear;
493        final int iDayOfMonth;
494        final int iDayOfWeek;
495        final boolean iAdvance;
496        final int iMillisOfDay;
497 
498        OfYear(char mode,
499               int monthOfYear,
500               int dayOfMonth,
501               int dayOfWeek, boolean advanceDayOfWeek,
502               int millisOfDay)
503        {
504            if (mode != 'u' && mode != 'w' && mode != 's') {
505                throw new IllegalArgumentException("Unknown mode: " + mode);
506            }
507 
508            iMode = mode;
509            iMonthOfYear = monthOfYear;
510            iDayOfMonth = dayOfMonth;
511            iDayOfWeek = dayOfWeek;
512            iAdvance = advanceDayOfWeek;
513            iMillisOfDay = millisOfDay;
514        }
515 
516        /**
517         * @param standardOffset standard offset just before instant
518         */
519        public long setInstant(int year, int standardOffset, int saveMillis) {
520            int offset;
521            if (iMode == 'w') {
522                offset = standardOffset + saveMillis;
523            } else if (iMode == 's') {
524                offset = standardOffset;
525            } else {
526                offset = 0;
527            }
528 
529            Chronology chrono = ISOChronology.getInstanceUTC();
530            long millis = chrono.year().set(0, year);
531            millis = chrono.monthOfYear().set(millis, iMonthOfYear);
532            millis = chrono.millisOfDay().set(millis, iMillisOfDay);
533            millis = setDayOfMonth(chrono, millis);
534 
535            if (iDayOfWeek != 0) {
536                millis = setDayOfWeek(chrono, millis);
537            }
538 
539            // Convert from local time to UTC.
540            return millis - offset;
541        }
542 
543        /**
544         * @param standardOffset standard offset just before next recurrence
545         */
546        public long next(long instant, int standardOffset, int saveMillis) {
547            int offset;
548            if (iMode == 'w') {
549                offset = standardOffset + saveMillis;
550            } else if (iMode == 's') {
551                offset = standardOffset;
552            } else {
553                offset = 0;
554            }
555 
556            // Convert from UTC to local time.
557            instant += offset;
558 
559            Chronology chrono = ISOChronology.getInstanceUTC();
560            long next = chrono.monthOfYear().set(instant, iMonthOfYear);
561            // Be lenient with millisOfDay.
562            next = chrono.millisOfDay().set(next, 0);
563            next = chrono.millisOfDay().add(next, iMillisOfDay);
564            next = setDayOfMonthNext(chrono, next);
565 
566            if (iDayOfWeek == 0) {
567                if (next <= instant) {
568                    next = chrono.year().add(next, 1);
569                    next = setDayOfMonthNext(chrono, next);
570                }
571            } else {
572                next = setDayOfWeek(chrono, next);
573                if (next <= instant) {
574                    next = chrono.year().add(next, 1);
575                    next = chrono.monthOfYear().set(next, iMonthOfYear);
576                    next = setDayOfMonthNext(chrono, next);
577                    next = setDayOfWeek(chrono, next);
578                }
579            }
580 
581            // Convert from local time to UTC.
582            return next - offset;
583        }
584 
585        /**
586         * @param standardOffset standard offset just before previous recurrence
587         */
588        public long previous(long instant, int standardOffset, int saveMillis) {
589            int offset;
590            if (iMode == 'w') {
591                offset = standardOffset + saveMillis;
592            } else if (iMode == 's') {
593                offset = standardOffset;
594            } else {
595                offset = 0;
596            }
597 
598            // Convert from UTC to local time.
599            instant += offset;
600 
601            Chronology chrono = ISOChronology.getInstanceUTC();
602            long prev = chrono.monthOfYear().set(instant, iMonthOfYear);
603            // Be lenient with millisOfDay.
604            prev = chrono.millisOfDay().set(prev, 0);
605            prev = chrono.millisOfDay().add(prev, iMillisOfDay);
606            prev = setDayOfMonthPrevious(chrono, prev);
607 
608            if (iDayOfWeek == 0) {
609                if (prev >= instant) {
610                    prev = chrono.year().add(prev, -1);
611                    prev = setDayOfMonthPrevious(chrono, prev);
612                }
613            } else {
614                prev = setDayOfWeek(chrono, prev);
615                if (prev >= instant) {
616                    prev = chrono.year().add(prev, -1);
617                    prev = chrono.monthOfYear().set(prev, iMonthOfYear);
618                    prev = setDayOfMonthPrevious(chrono, prev);
619                    prev = setDayOfWeek(chrono, prev);
620                }
621            }
622 
623            // Convert from local time to UTC.
624            return prev - offset;
625        }
626 
627        public boolean equals(Object obj) {
628            if (this == obj) {
629                return true;
630            }
631            if (obj instanceof OfYear) {
632                OfYear other = (OfYear)obj;
633                return
634                    iMode == other.iMode &&
635                    iMonthOfYear == other.iMonthOfYear &&
636                    iDayOfMonth == other.iDayOfMonth &&
637                    iDayOfWeek == other.iDayOfWeek &&
638                    iAdvance == other.iAdvance &&
639                    iMillisOfDay == other.iMillisOfDay;
640            }
641            return false;
642        }
643 
644        /*
645        public String toString() {
646            return
647                "[OfYear]\n" + 
648                "Mode: " + iMode + '\n' +
649                "MonthOfYear: " + iMonthOfYear + '\n' +
650                "DayOfMonth: " + iDayOfMonth + '\n' +
651                "DayOfWeek: " + iDayOfWeek + '\n' +
652                "AdvanceDayOfWeek: " + iAdvance + '\n' +
653                "MillisOfDay: " + iMillisOfDay + '\n';
654        }
655        */
656 
657        public void writeTo(DataOutput out) throws IOException {
658            out.writeByte(iMode);
659            out.writeByte(iMonthOfYear);
660            out.writeByte(iDayOfMonth);
661            out.writeByte(iDayOfWeek);
662            out.writeBoolean(iAdvance);
663            writeMillis(out, iMillisOfDay);
664        }
665 
666        /**
667         * If month-day is 02-29 and year isn't leap, advances to next leap year.
668         */
669        private long setDayOfMonthNext(Chronology chrono, long next) {
670            try {
671                next = setDayOfMonth(chrono, next);
672            } catch (IllegalArgumentException e) {
673                if (iMonthOfYear == 2 && iDayOfMonth == 29) {
674                    while (chrono.year().isLeap(next) == false) {
675                        next = chrono.year().add(next, 1);
676                    }
677                    next = setDayOfMonth(chrono, next);
678                } else {
679                    throw e;
680                }
681            }
682            return next;
683        }
684 
685        /**
686         * If month-day is 02-29 and year isn't leap, retreats to previous leap year.
687         */
688        private long setDayOfMonthPrevious(Chronology chrono, long prev) {
689            try {
690                prev = setDayOfMonth(chrono, prev);
691            } catch (IllegalArgumentException e) {
692                if (iMonthOfYear == 2 && iDayOfMonth == 29) {
693                    while (chrono.year().isLeap(prev) == false) {
694                        prev = chrono.year().add(prev, -1);
695                    }
696                    prev = setDayOfMonth(chrono, prev);
697                } else {
698                    throw e;
699                }
700            }
701            return prev;
702        }
703 
704        private long setDayOfMonth(Chronology chrono, long instant) {
705            if (iDayOfMonth >= 0) {
706                instant = chrono.dayOfMonth().set(instant, iDayOfMonth);
707            } else {
708                instant = chrono.dayOfMonth().set(instant, 1);
709                instant = chrono.monthOfYear().add(instant, 1);
710                instant = chrono.dayOfMonth().add(instant, iDayOfMonth);
711            }
712            return instant;
713        }
714 
715        private long setDayOfWeek(Chronology chrono, long instant) {
716            int dayOfWeek = chrono.dayOfWeek().get(instant);
717            int daysToAdd = iDayOfWeek - dayOfWeek;
718            if (daysToAdd != 0) {
719                if (iAdvance) {
720                    if (daysToAdd < 0) {
721                        daysToAdd += 7;
722                    }
723                } else {
724                    if (daysToAdd > 0) {
725                        daysToAdd -= 7;
726                    }
727                }
728                instant = chrono.dayOfWeek().add(instant, daysToAdd);
729            }
730            return instant;
731        }
732    }
733 
734    /**
735     * Extends OfYear with a nameKey and savings.
736     */
737    private static final class Recurrence {
738        static Recurrence readFrom(DataInput in) throws IOException {
739            return new Recurrence(OfYear.readFrom(in), in.readUTF(), (int)readMillis(in));
740        }
741 
742        final OfYear iOfYear;
743        final String iNameKey;
744        final int iSaveMillis;
745 
746        Recurrence(OfYear ofYear, String nameKey, int saveMillis) {
747            iOfYear = ofYear;
748            iNameKey = nameKey;
749            iSaveMillis = saveMillis;
750        }
751 
752        public OfYear getOfYear() {
753            return iOfYear;
754        }
755 
756        /**
757         * @param standardOffset standard offset just before next recurrence
758         */
759        public long next(long instant, int standardOffset, int saveMillis) {
760            return iOfYear.next(instant, standardOffset, saveMillis);
761        }
762 
763        /**
764         * @param standardOffset standard offset just before previous recurrence
765         */
766        public long previous(long instant, int standardOffset, int saveMillis) {
767            return iOfYear.previous(instant, standardOffset, saveMillis);
768        }
769 
770        public String getNameKey() {
771            return iNameKey;
772        }
773 
774        public int getSaveMillis() {
775            return iSaveMillis;
776        }
777 
778        public boolean equals(Object obj) {
779            if (this == obj) {
780                return true;
781            }
782            if (obj instanceof Recurrence) {
783                Recurrence other = (Recurrence)obj;
784                return
785                    iSaveMillis == other.iSaveMillis &&
786                    iNameKey.equals(other.iNameKey) &&
787                    iOfYear.equals(other.iOfYear);
788            }
789            return false;
790        }
791 
792        public void writeTo(DataOutput out) throws IOException {
793            iOfYear.writeTo(out);
794            out.writeUTF(iNameKey);
795            writeMillis(out, iSaveMillis);
796        }
797 
798        Recurrence rename(String nameKey) {
799            return new Recurrence(iOfYear, nameKey, iSaveMillis);
800        }
801 
802        Recurrence renameAppend(String appendNameKey) {
803            return rename((iNameKey + appendNameKey).intern());
804        }
805    }
806 
807    /**
808     * Extends Recurrence with inclusive year limits.
809     */
810    private static final class Rule {
811        final Recurrence iRecurrence;
812        final int iFromYear; // inclusive
813        final int iToYear;   // inclusive
814 
815        Rule(Recurrence recurrence, int fromYear, int toYear) {
816            iRecurrence = recurrence;
817            iFromYear = fromYear;
818            iToYear = toYear;
819        }
820 
821        public int getFromYear() {
822            return iFromYear;
823        }
824 
825        public int getToYear() {
826            return iToYear;
827        }
828 
829        public OfYear getOfYear() {
830            return iRecurrence.getOfYear();
831        }
832 
833        public String getNameKey() {
834            return iRecurrence.getNameKey();
835        }
836 
837        public int getSaveMillis() {
838            return iRecurrence.getSaveMillis();
839        }
840 
841        public long next(final long instant, int standardOffset, int saveMillis) {
842            Chronology chrono = ISOChronology.getInstanceUTC();
843 
844            final int wallOffset = standardOffset + saveMillis;
845            long testInstant = instant;
846 
847            int year;
848            if (instant == Long.MIN_VALUE) {
849                year = Integer.MIN_VALUE;
850            } else {
851                year = chrono.year().get(instant + wallOffset);
852            }
853 
854            if (year < iFromYear) {
855                // First advance instant to start of from year.
856                testInstant = chrono.year().set(0, iFromYear) - wallOffset;
857                // Back off one millisecond to account for next recurrence
858                // being exactly at the beginning of the year.
859                testInstant -= 1;
860            }
861 
862            long next = iRecurrence.next(testInstant, standardOffset, saveMillis);
863 
864            if (next > instant) {
865                year = chrono.year().get(next + wallOffset);
866                if (year > iToYear) {
867                    // Out of range, return original value.
868                    next = instant;
869                }
870            }
871 
872            return next;
873        }
874    }
875 
876    private static final class Transition {
877        private final long iMillis;
878        private final String iNameKey;
879        private final int iWallOffset;
880        private final int iStandardOffset;
881 
882        Transition(long millis, Transition tr) {
883            iMillis = millis;
884            iNameKey = tr.iNameKey;
885            iWallOffset = tr.iWallOffset;
886            iStandardOffset = tr.iStandardOffset;
887        }
888 
889        Transition(long millis, Rule rule, int standardOffset) {
890            iMillis = millis;
891            iNameKey = rule.getNameKey();
892            iWallOffset = standardOffset + rule.getSaveMillis();
893            iStandardOffset = standardOffset;
894        }
895 
896        Transition(long millis, String nameKey,
897                   int wallOffset, int standardOffset) {
898            iMillis = millis;
899            iNameKey = nameKey;
900            iWallOffset = wallOffset;
901            iStandardOffset = standardOffset;
902        }
903 
904        public long getMillis() {
905            return iMillis;
906        }
907 
908        public String getNameKey() {
909            return iNameKey;
910        }
911 
912        public int getWallOffset() {
913            return iWallOffset;
914        }
915 
916        public int getStandardOffset() {
917            return iStandardOffset;
918        }
919 
920        public int getSaveMillis() {
921            return iWallOffset - iStandardOffset;
922        }
923 
924        /**
925         * There must be a change in the millis, wall offsets or name keys.
926         */
927        public boolean isTransitionFrom(Transition other) {
928            if (other == null) {
929                return true;
930            }
931            return iMillis > other.iMillis &&
932                (iWallOffset != other.iWallOffset ||
933                 //iStandardOffset != other.iStandardOffset ||
934                 !(iNameKey.equals(other.iNameKey)));
935        }
936    }
937 
938    private static final class RuleSet {
939        private static final int YEAR_LIMIT;
940 
941        static {
942            // Don't pre-calculate more than 100 years into the future. Almost
943            // all zones will stop pre-calculating far sooner anyhow. Either a
944            // simple DST cycle is detected or the last rule is a fixed
945            // offset. If a zone has a fixed offset set more than 100 years
946            // into the future, then it won't be observed.
947            long now = DateTimeUtils.currentTimeMillis();
948            YEAR_LIMIT = ISOChronology.getInstanceUTC().year().get(now) + 100;
949        }
950 
951        private int iStandardOffset;
952        private ArrayList iRules;
953 
954        // Optional.
955        private String iInitialNameKey;
956        private int iInitialSaveMillis;
957 
958        // Upper limit is exclusive.
959        private int iUpperYear;
960        private OfYear iUpperOfYear;
961 
962        RuleSet() {
963            iRules = new ArrayList(10);
964            iUpperYear = Integer.MAX_VALUE;
965        }
966 
967        /**
968         * Copy constructor.
969         */
970        RuleSet(RuleSet rs) {
971            iStandardOffset = rs.iStandardOffset;
972            iRules = new ArrayList(rs.iRules);
973            iInitialNameKey = rs.iInitialNameKey;
974            iInitialSaveMillis = rs.iInitialSaveMillis;
975            iUpperYear = rs.iUpperYear;
976            iUpperOfYear = rs.iUpperOfYear;
977        }
978 
979        public int getStandardOffset() {
980            return iStandardOffset;
981        }
982 
983        public void setStandardOffset(int standardOffset) {
984            iStandardOffset = standardOffset;
985        }
986 
987        public void setFixedSavings(String nameKey, int saveMillis) {
988            iInitialNameKey = nameKey;
989            iInitialSaveMillis = saveMillis;
990        }
991 
992        public void addRule(Rule rule) {
993            if (!iRules.contains(rule)) {
994                iRules.add(rule);
995            }
996        }
997 
998        public void setUpperLimit(int year, OfYear ofYear) {
999            iUpperYear = year;
1000            iUpperOfYear = ofYear;
1001        }
1002 
1003        /**
1004         * Returns a transition at firstMillis with the first name key and
1005         * offsets for this rule set. This method may return null.
1006         *
1007         * @param firstMillis millis of first transition
1008         */
1009        public Transition firstTransition(final long firstMillis) {
1010            if (iInitialNameKey != null) {
1011                // Initial zone info explicitly set, so don't search the rules.
1012                return new Transition(firstMillis, iInitialNameKey,
1013                                      iStandardOffset + iInitialSaveMillis, iStandardOffset);
1014            }
1015 
1016            // Make a copy before we destroy the rules.
1017            ArrayList copy = new ArrayList(iRules);
1018 
1019            // Iterate through all the transitions until firstMillis is
1020            // reached. Use the name key and savings for whatever rule reaches
1021            // the limit.
1022 
1023            long millis = Long.MIN_VALUE;
1024            int saveMillis = 0;
1025            Transition first = null;
1026 
1027            Transition next;
1028            while ((next = nextTransition(millis, saveMillis)) != null) {
1029                millis = next.getMillis();
1030 
1031                if (millis == firstMillis) {
1032                    first = new Transition(firstMillis, next);
1033                    break;
1034                }
1035 
1036                if (millis > firstMillis) {
1037                    if (first == null) {
1038                        // Find first rule without savings. This way a more
1039                        // accurate nameKey is found even though no rule
1040                        // extends to the RuleSet's lower limit.
1041                        Iterator it = copy.iterator();
1042                        while (it.hasNext()) {
1043                            Rule rule = (Rule)it.next();
1044                            if (rule.getSaveMillis() == 0) {
1045                                first = new Transition(firstMillis, rule, iStandardOffset);
1046                                break;
1047                            }
1048                        }
1049                    }
1050                    if (first == null) {
1051                        // Found no rule without savings. Create a transition
1052                        // with no savings anyhow, and use the best available
1053                        // name key.
1054                        first = new Transition(firstMillis, next.getNameKey(),
1055                                               iStandardOffset, iStandardOffset);
1056                    }
1057                    break;
1058                }
1059                
1060                // Set first to the best transition found so far, but next
1061                // iteration may find something closer to lower limit.
1062                first = new Transition(firstMillis, next);
1063 
1064                saveMillis = next.getSaveMillis();
1065            }
1066 
1067            iRules = copy;
1068            return first;
1069        }
1070 
1071        /**
1072         * Returns null if RuleSet is exhausted or upper limit reached. Calling
1073         * this method will throw away rules as they each become
1074         * exhausted. Copy the RuleSet before using it to compute transitions.
1075         *
1076         * Returned transition may be a duplicate from previous
1077         * transition. Caller must call isTransitionFrom to filter out
1078         * duplicates.
1079         *
1080         * @param saveMillis savings before next transition
1081         */
1082        public Transition nextTransition(final long instant, final int saveMillis) {
1083            Chronology chrono = ISOChronology.getInstanceUTC();
1084 
1085            // Find next matching rule.
1086            Rule nextRule = null;
1087            long nextMillis = Long.MAX_VALUE;
1088            
1089            Iterator it = iRules.iterator();
1090            while (it.hasNext()) {
1091                Rule rule = (Rule)it.next();
1092                long next = rule.next(instant, iStandardOffset, saveMillis);
1093                if (next <= instant) {
1094                    it.remove();
1095                    continue;
1096                }
1097                // Even if next is same as previous next, choose the rule
1098                // in order for more recently added rules to override.
1099                if (next <= nextMillis) {
1100                    // Found a better match.
1101                    nextRule = rule;
1102                    nextMillis = next;
1103                }
1104            }
1105            
1106            if (nextRule == null) {
1107                return null;
1108            }
1109            
1110            // Stop precalculating if year reaches some arbitrary limit.
1111            if (chrono.year().get(nextMillis) >= YEAR_LIMIT) {
1112                return null;
1113            }
1114            
1115            // Check if upper limit reached or passed.
1116            if (iUpperYear < Integer.MAX_VALUE) {
1117                long upperMillis =
1118                    iUpperOfYear.setInstant(iUpperYear, iStandardOffset, saveMillis);
1119                if (nextMillis >= upperMillis) {
1120                    // At or after upper limit.
1121                    return null;
1122                }
1123            }
1124            
1125            return new Transition(nextMillis, nextRule, iStandardOffset);
1126        }
1127 
1128        /**
1129         * @param saveMillis savings before upper limit
1130         */
1131        public long getUpperLimit(int saveMillis) {
1132            if (iUpperYear == Integer.MAX_VALUE) {
1133                return Long.MAX_VALUE;
1134            }
1135            return iUpperOfYear.setInstant(iUpperYear, iStandardOffset, saveMillis);
1136        }
1137 
1138        /**
1139         * Returns null if none can be built.
1140         */
1141        public DSTZone buildTailZone(String id) {
1142            if (iRules.size() == 2) {
1143                Rule startRule = (Rule)iRules.get(0);
1144                Rule endRule = (Rule)iRules.get(1);
1145                if (startRule.getToYear() == Integer.MAX_VALUE &&
1146                    endRule.getToYear() == Integer.MAX_VALUE) {
1147 
1148                    // With exactly two infinitely recurring rules left, a
1149                    // simple DSTZone can be formed.
1150 
1151                    // The order of rules can come in any order, and it doesn't
1152                    // really matter which rule was chosen the 'start' and
1153                    // which is chosen the 'end'. DSTZone works properly either
1154                    // way.
1155                    return new DSTZone(id, iStandardOffset,
1156                                       startRule.iRecurrence, endRule.iRecurrence);
1157                }
1158            }
1159            return null;
1160        }
1161    }
1162 
1163    private static final class DSTZone extends DateTimeZone {
1164        private static final long serialVersionUID = 6941492635554961361L;
1165 
1166        static DSTZone readFrom(DataInput in, String id) throws IOException {
1167            return new DSTZone(id, (int)readMillis(in), 
1168                               Recurrence.readFrom(in), Recurrence.readFrom(in));
1169        }
1170 
1171        final int iStandardOffset;
1172        final Recurrence iStartRecurrence;
1173        final Recurrence iEndRecurrence;
1174 
1175        DSTZone(String id, int standardOffset,
1176                Recurrence startRecurrence, Recurrence endRecurrence) {
1177            super(id);
1178            iStandardOffset = standardOffset;
1179            iStartRecurrence = startRecurrence;
1180            iEndRecurrence = endRecurrence;
1181        }
1182 
1183        public String getNameKey(long instant) {
1184            return findMatchingRecurrence(instant).getNameKey();
1185        }
1186 
1187        public int getOffset(long instant) {
1188            return iStandardOffset + findMatchingRecurrence(instant).getSaveMillis();
1189        }
1190 
1191        public int getStandardOffset(long instant) {
1192            return iStandardOffset;
1193        }
1194 
1195        public boolean isFixed() {
1196            return false;
1197        }
1198 
1199        public long nextTransition(long instant) {
1200            int standardOffset = iStandardOffset;
1201            Recurrence startRecurrence = iStartRecurrence;
1202            Recurrence endRecurrence = iEndRecurrence;
1203 
1204            long start, end;
1205 
1206            try {
1207                start = startRecurrence.next
1208                    (instant, standardOffset, endRecurrence.getSaveMillis());
1209                if (instant > 0 && start < 0) {
1210                    // Overflowed.
1211                    start = instant;
1212                }
1213            } catch (IllegalArgumentException e) {
1214                // Overflowed.
1215                start = instant;
1216            } catch (ArithmeticException e) {
1217                // Overflowed.
1218                start = instant;
1219            }
1220 
1221            try {
1222                end = endRecurrence.next
1223                    (instant, standardOffset, startRecurrence.getSaveMillis());
1224                if (instant > 0 && end < 0) {
1225                    // Overflowed.
1226                    end = instant;
1227                }
1228            } catch (IllegalArgumentException e) {
1229                // Overflowed.
1230                end = instant;
1231            } catch (ArithmeticException e) {
1232                // Overflowed.
1233                end = instant;
1234            }
1235 
1236            return (start > end) ? end : start;
1237        }
1238 
1239        public long previousTransition(long instant) {
1240            // Increment in order to handle the case where instant is exactly at
1241            // a transition.
1242            instant++;
1243 
1244            int standardOffset = iStandardOffset;
1245            Recurrence startRecurrence = iStartRecurrence;
1246            Recurrence endRecurrence = iEndRecurrence;
1247 
1248            long start, end;
1249 
1250            try {
1251                start = startRecurrence.previous
1252                    (instant, standardOffset, endRecurrence.getSaveMillis());
1253                if (instant < 0 && start > 0) {
1254                    // Overflowed.
1255                    start = instant;
1256                }
1257            } catch (IllegalArgumentException e) {
1258                // Overflowed.
1259                start = instant;
1260            } catch (ArithmeticException e) {
1261                // Overflowed.
1262                start = instant;
1263            }
1264 
1265            try {
1266                end = endRecurrence.previous
1267                    (instant, standardOffset, startRecurrence.getSaveMillis());
1268                if (instant < 0 && end > 0) {
1269                    // Overflowed.
1270                    end = instant;
1271                }
1272            } catch (IllegalArgumentException e) {
1273                // Overflowed.
1274                end = instant;
1275            } catch (ArithmeticException e) {
1276                // Overflowed.
1277                end = instant;
1278            }
1279 
1280            return ((start > end) ? start : end) - 1;
1281        }
1282 
1283        public boolean equals(Object obj) {
1284            if (this == obj) {
1285                return true;
1286            }
1287            if (obj instanceof DSTZone) {
1288                DSTZone other = (DSTZone)obj;
1289                return
1290                    getID().equals(other.getID()) &&
1291                    iStandardOffset == other.iStandardOffset &&
1292                    iStartRecurrence.equals(other.iStartRecurrence) &&
1293                    iEndRecurrence.equals(other.iEndRecurrence);
1294            }
1295            return false;
1296        }
1297 
1298        public void writeTo(DataOutput out) throws IOException {
1299            writeMillis(out, iStandardOffset);
1300            iStartRecurrence.writeTo(out);
1301            iEndRecurrence.writeTo(out);
1302        }
1303 
1304        private Recurrence findMatchingRecurrence(long instant) {
1305            int standardOffset = iStandardOffset;
1306            Recurrence startRecurrence = iStartRecurrence;
1307            Recurrence endRecurrence = iEndRecurrence;
1308 
1309            long start, end;
1310 
1311            try {
1312                start = startRecurrence.next
1313                    (instant, standardOffset, endRecurrence.getSaveMillis());
1314            } catch (IllegalArgumentException e) {
1315                // Overflowed.
1316                start = instant;
1317            } catch (ArithmeticException e) {
1318                // Overflowed.
1319                start = instant;
1320            }
1321 
1322            try {
1323                end = endRecurrence.next
1324                    (instant, standardOffset, startRecurrence.getSaveMillis());
1325            } catch (IllegalArgumentException e) {
1326                // Overflowed.
1327                end = instant;
1328            } catch (ArithmeticException e) {
1329                // Overflowed.
1330                end = instant;
1331            }
1332 
1333            return (start > end) ? startRecurrence : endRecurrence;
1334        }
1335    }
1336 
1337    private static final class PrecalculatedZone extends DateTimeZone {
1338        private static final long serialVersionUID = 7811976468055766265L;
1339 
1340        static PrecalculatedZone readFrom(DataInput in, String id) throws IOException {
1341            // Read string pool.
1342            int poolSize = in.readUnsignedShort();
1343            String[] pool = new String[poolSize];
1344            for (int i=0; i<poolSize; i++) {
1345                pool[i] = in.readUTF();
1346            }
1347 
1348            int size = in.readInt();
1349            long[] transitions = new long[size];
1350            int[] wallOffsets = new int[size];
1351            int[] standardOffsets = new int[size];
1352            String[] nameKeys = new String[size];
1353            
1354            for (int i=0; i<size; i++) {
1355                transitions[i] = readMillis(in);
1356                wallOffsets[i] = (int)readMillis(in);
1357                standardOffsets[i] = (int)readMillis(in);
1358                try {
1359                    int index;
1360                    if (poolSize < 256) {
1361                        index = in.readUnsignedByte();
1362                    } else {
1363                        index = in.readUnsignedShort();
1364                    }
1365                    nameKeys[i] = pool[index];
1366                } catch (ArrayIndexOutOfBoundsException e) {
1367                    throw new IOException("Invalid encoding");
1368                }
1369            }
1370 
1371            DSTZone tailZone = null;
1372            if (in.readBoolean()) {
1373                tailZone = DSTZone.readFrom(in, id);
1374            }
1375 
1376            return new PrecalculatedZone
1377                (id, transitions, wallOffsets, standardOffsets, nameKeys, tailZone);
1378        }
1379 
1380        /**
1381         * Factory to create instance from builder.
1382         * 
1383         * @param id  the zone id
1384         * @param outputID  true if the zone id should be output
1385         * @param transitions  the list of Transition objects
1386         * @param tailZone  optional zone for getting info beyond precalculated tables
1387         */
1388        static PrecalculatedZone create(String id, boolean outputID, ArrayList transitions, DSTZone tailZone) {
1389            int size = transitions.size();
1390            if (size == 0) {
1391                throw new IllegalArgumentException();
1392            }
1393 
1394            long[] trans = new long[size];
1395            int[] wallOffsets = new int[size];
1396            int[] standardOffsets = new int[size];
1397            String[] nameKeys = new String[size];
1398 
1399            Transition last = null;
1400            for (int i=0; i<size; i++) {
1401                Transition tr = (Transition)transitions.get(i);
1402 
1403                if (!tr.isTransitionFrom(last)) {
1404                    throw new IllegalArgumentException(id);
1405                }
1406 
1407                trans[i] = tr.getMillis();
1408                wallOffsets[i] = tr.getWallOffset();
1409                standardOffsets[i] = tr.getStandardOffset();
1410                nameKeys[i] = tr.getNameKey();
1411 
1412                last = tr;
1413            }
1414 
1415            // Some timezones (Australia) have the same name key for
1416            // summer and winter which messes everything up. Fix it here.
1417            String[] zoneNameData = new String[5];
1418            String[][] zoneStrings = new DateFormatSymbols(Locale.ENGLISH).getZoneStrings();
1419            for (int j = 0; j < zoneStrings.length; j++) {
1420                String[] set = zoneStrings[j];
1421                if (set != null && set.length == 5 && id.equals(set[0])) {
1422                    zoneNameData = set;
1423                }
1424            }
1425            for (int i = 0; i < nameKeys.length - 1; i++) {
1426                String curNameKey = nameKeys[i];
1427                String nextNameKey = nameKeys[i + 1];
1428                long curOffset = wallOffsets[i];
1429                long nextOffset = wallOffsets[i + 1];
1430                long curStdOffset = standardOffsets[i];
1431                long nextStdOffset = standardOffsets[i + 1];
1432                Period p = new Period(trans[i], trans[i + 1], PeriodType.yearMonthDay());
1433                if (curOffset != nextOffset &&
1434                        curStdOffset == nextStdOffset &&
1435                        curNameKey.equals(nextNameKey) &&
1436                        p.getYears() == 0 && p.getMonths() > 4 && p.getMonths() < 8 &&
1437                        curNameKey.equals(zoneNameData[2]) &&
1438                        curNameKey.equals(zoneNameData[4])) {
1439                    
1440                    System.out.println("Fixing duplicate name key - " + nextNameKey);
1441                    System.out.println("     - " + new DateTime(trans[i]) + " - " + new DateTime(trans[i + 1]));
1442                    if (curOffset > nextOffset) {
1443                        nameKeys[i] = (curNameKey + "-Summer").intern();
1444                    } else if (curOffset < nextOffset) {
1445                        nameKeys[i + 1] = (nextNameKey + "-Summer").intern();
1446                        i++;
1447                    }
1448                }
1449            }
1450            if (tailZone != null) {
1451                if (tailZone.iStartRecurrence.getNameKey().equals(tailZone.iEndRecurrence.getNameKey())) {
1452                    System.out.println("Fixing duplicate recurrent name key - " + tailZone.iStartRecurrence.getNameKey());
1453                    if (tailZone.iStartRecurrence.getSaveMillis() > 0) {
1454                        tailZone = new DSTZone(
1455                            tailZone.getID(),
1456                            tailZone.iStandardOffset,
1457                            tailZone.iStartRecurrence.renameAppend("-Summer"),
1458                            tailZone.iEndRecurrence);
1459                    } else {
1460                        tailZone = new DSTZone(
1461                            tailZone.getID(),
1462                            tailZone.iStandardOffset,
1463                            tailZone.iStartRecurrence,
1464                            tailZone.iEndRecurrence.renameAppend("-Summer"));
1465                    }
1466                }
1467            }
1468            
1469            return new PrecalculatedZone((outputID ? id : ""), trans, wallOffsets, standardOffsets, nameKeys, tailZone);
1470        }
1471 
1472        // All array fields have the same length.
1473 
1474        private final long[] iTransitions;
1475 
1476        private final int[] iWallOffsets;
1477        private final int[] iStandardOffsets;
1478        private final String[] iNameKeys;
1479 
1480        private final DSTZone iTailZone;
1481 
1482        /**
1483         * Constructor used ONLY for valid input, loaded via static methods.
1484         */
1485        private PrecalculatedZone(String id, long[] transitions, int[] wallOffsets,
1486                          int[] standardOffsets, String[] nameKeys, DSTZone tailZone)
1487        {
1488            super(id);
1489            iTransitions = transitions;
1490            iWallOffsets = wallOffsets;
1491            iStandardOffsets = standardOffsets;
1492            iNameKeys = nameKeys;
1493            iTailZone = tailZone;
1494        }
1495 
1496        public String getNameKey(long instant) {
1497            long[] transitions = iTransitions;
1498            int i = Arrays.binarySearch(transitions, instant);
1499            if (i >= 0) {
1500                return iNameKeys[i];
1501            }
1502            i = ~i;
1503            if (i < transitions.length) {
1504                if (i > 0) {
1505                    return iNameKeys[i - 1];
1506                }
1507                return "UTC";
1508            }
1509            if (iTailZone == null) {
1510                return iNameKeys[i - 1];
1511            }
1512            return iTailZone.getNameKey(instant);
1513        }
1514 
1515        public int getOffset(long instant) {
1516            long[] transitions = iTransitions;
1517            int i = Arrays.binarySearch(transitions, instant);
1518            if (i >= 0) {
1519                return iWallOffsets[i];
1520            }
1521            i = ~i;
1522            if (i < transitions.length) {
1523                if (i > 0) {
1524                    return iWallOffsets[i - 1];
1525                }
1526                return 0;
1527            }
1528            if (iTailZone == null) {
1529                return iWallOffsets[i - 1];
1530            }
1531            return iTailZone.getOffset(instant);
1532        }
1533 
1534        public int getStandardOffset(long instant) {
1535            long[] transitions = iTransitions;
1536            int i = Arrays.binarySearch(transitions, instant);
1537            if (i >= 0) {
1538                return iStandardOffsets[i];
1539            }
1540            i = ~i;
1541            if (i < transitions.length) {
1542                if (i > 0) {
1543                    return iStandardOffsets[i - 1];
1544                }
1545                return 0;
1546            }
1547            if (iTailZone == null) {
1548                return iStandardOffsets[i - 1];
1549            }
1550            return iTailZone.getStandardOffset(instant);
1551        }
1552 
1553        public boolean isFixed() {
1554            return false;
1555        }
1556 
1557        public long nextTransition(long instant) {
1558            long[] transitions = iTransitions;
1559            int i = Arrays.binarySearch(transitions, instant);
1560            i = (i >= 0) ? (i + 1) : ~i;
1561            if (i < transitions.length) {
1562                return transitions[i];
1563            }
1564            if (iTailZone == null) {
1565                return instant;
1566            }
1567            long end = transitions[transitions.length - 1];
1568            if (instant < end) {
1569                instant = end;
1570            }
1571            return iTailZone.nextTransition(instant);
1572        }
1573 
1574        public long previousTransition(long instant) {
1575            long[] transitions = iTransitions;
1576            int i = Arrays.binarySearch(transitions, instant);
1577            if (i >= 0) {
1578                if (instant > Long.MIN_VALUE) {
1579                    return instant - 1;
1580                }
1581                return instant;
1582            }
1583            i = ~i;
1584            if (i < transitions.length) {
1585                if (i > 0) {
1586                    long prev = transitions[i - 1];
1587                    if (prev > Long.MIN_VALUE) {
1588                        return prev - 1;
1589                    }
1590                }
1591                return instant;
1592            }
1593            if (iTailZone != null) {
1594                long prev = iTailZone.previousTransition(instant);
1595                if (prev < instant) {
1596                    return prev;
1597                }
1598            }
1599            long prev = transitions[i - 1];
1600            if (prev > Long.MIN_VALUE) {
1601                return prev - 1;
1602            }
1603            return instant;
1604        }
1605 
1606        public boolean equals(Object obj) {
1607            if (this == obj) {
1608                return true;
1609            }
1610            if (obj instanceof PrecalculatedZone) {
1611                PrecalculatedZone other = (PrecalculatedZone)obj;
1612                return
1613                    getID().equals(other.getID()) &&
1614                    Arrays.equals(iTransitions, other.iTransitions) &&
1615                    Arrays.equals(iNameKeys, other.iNameKeys) &&
1616                    Arrays.equals(iWallOffsets, other.iWallOffsets) &&
1617                    Arrays.equals(iStandardOffsets, other.iStandardOffsets) &&
1618                    ((iTailZone == null)
1619                     ? (null == other.iTailZone)
1620                     : (iTailZone.equals(other.iTailZone)));
1621            }
1622            return false;
1623        }
1624 
1625        public void writeTo(DataOutput out) throws IOException {
1626            int size = iTransitions.length;
1627 
1628            // Create unique string pool.
1629            Set poolSet = new HashSet();
1630            for (int i=0; i<size; i++) {
1631                poolSet.add(iNameKeys[i]);
1632            }
1633 
1634            int poolSize = poolSet.size();
1635            if (poolSize > 65535) {
1636                throw new UnsupportedOperationException("String pool is too large");
1637            }
1638            String[] pool = new String[poolSize];
1639            Iterator it = poolSet.iterator();
1640            for (int i=0; it.hasNext(); i++) {
1641                pool[i] = (String)it.next();
1642            }
1643 
1644            // Write out the pool.
1645            out.writeShort(poolSize);
1646            for (int i=0; i<poolSize; i++) {
1647                out.writeUTF(pool[i]);
1648            }
1649 
1650            out.writeInt(size);
1651 
1652            for (int i=0; i<size; i++) {
1653                writeMillis(out, iTransitions[i]);
1654                writeMillis(out, iWallOffsets[i]);
1655                writeMillis(out, iStandardOffsets[i]);
1656                
1657                // Find pool index and write it out.
1658                String nameKey = iNameKeys[i];
1659                for (int j=0; j<poolSize; j++) {
1660                    if (pool[j].equals(nameKey)) {
1661                        if (poolSize < 256) {
1662                            out.writeByte(j);
1663                        } else {
1664                            out.writeShort(j);
1665                        }
1666                        break;
1667                    }
1668                }
1669            }
1670 
1671            out.writeBoolean(iTailZone != null);
1672            if (iTailZone != null) {
1673                iTailZone.writeTo(out);
1674            }
1675        }
1676 
1677        public boolean isCachable() {
1678            if (iTailZone != null) {
1679                return true;
1680            }
1681            long[] transitions = iTransitions;
1682            if (transitions.length <= 1) {
1683                return false;
1684            }
1685 
1686            // Add up all the distances between transitions that are less than
1687            // about two years.
1688            double distances = 0;
1689            int count = 0;
1690 
1691            for (int i=1; i<transitions.length; i++) {
1692                long diff = transitions[i] - transitions[i - 1];
1693                if (diff < ((366L + 365) * 24 * 60 * 60 * 1000)) {
1694                    distances += (double)diff;
1695                    count++;
1696                }
1697            }
1698 
1699            if (count > 0) {
1700                double avg = distances / count;
1701                avg /= 24 * 60 * 60 * 1000;
1702                if (avg >= 25) {
1703                    // Only bother caching if average distance between
1704                    // transitions is at least 25 days. Why 25?
1705                    // CachedDateTimeZone is more efficient if the distance
1706                    // between transitions is large. With an average of 25, it
1707                    // will on average perform about 2 tests per cache
1708                    // hit. (49.7 / 25) is approximately 2.
1709                    return true;
1710                }
1711            }
1712 
1713            return false;
1714        }
1715    }
1716}

[all classes][org.joda.time.tz]
EMMA 2.0.5312 (C) Vladimir Roubtsov