KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > joda > time > tz > DateTimeZoneBuilder


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  */

16 package org.joda.time.tz;
17
18 import java.io.DataInput JavaDoc;
19 import java.io.DataInputStream JavaDoc;
20 import java.io.DataOutput JavaDoc;
21 import java.io.DataOutputStream JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.io.OutputStream JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.Arrays JavaDoc;
27 import java.util.HashSet JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.Set JavaDoc;
30
31 import org.joda.time.Chronology;
32 import org.joda.time.DateTimeUtils;
33 import org.joda.time.DateTimeZone;
34 import org.joda.time.chrono.ISOChronology;
35
36 /**
37  * DateTimeZoneBuilder allows complex DateTimeZones to be constructed. Since
38  * creating a new DateTimeZone this way is a relatively expensive operation,
39  * built zones can be written to a file. Reading back the encoded data is a
40  * quick operation.
41  * <p>
42  * DateTimeZoneBuilder itself is mutable and not thread-safe, but the
43  * DateTimeZone objects that it builds are thread-safe and immutable.
44  * <p>
45  * It is intended that {@link ZoneInfoCompiler} be used to read time zone data
46  * files, indirectly calling DateTimeZoneBuilder. The following complex
47  * example defines the America/Los_Angeles time zone, with all historical
48  * transitions:
49  *
50  * <pre>
51  * DateTimeZone America_Los_Angeles = new DateTimeZoneBuilder()
52  * .addCutover(-2147483648, 'w', 1, 1, 0, false, 0)
53  * .setStandardOffset(-28378000)
54  * .setFixedSavings("LMT", 0)
55  * .addCutover(1883, 'w', 11, 18, 0, false, 43200000)
56  * .setStandardOffset(-28800000)
57  * .addRecurringSavings("PDT", 3600000, 1918, 1919, 'w', 3, -1, 7, false, 7200000)
58  * .addRecurringSavings("PST", 0, 1918, 1919, 'w', 10, -1, 7, false, 7200000)
59  * .addRecurringSavings("PWT", 3600000, 1942, 1942, 'w', 2, 9, 0, false, 7200000)
60  * .addRecurringSavings("PPT", 3600000, 1945, 1945, 'u', 8, 14, 0, false, 82800000)
61  * .addRecurringSavings("PST", 0, 1945, 1945, 'w', 9, 30, 0, false, 7200000)
62  * .addRecurringSavings("PDT", 3600000, 1948, 1948, 'w', 3, 14, 0, false, 7200000)
63  * .addRecurringSavings("PST", 0, 1949, 1949, 'w', 1, 1, 0, false, 7200000)
64  * .addRecurringSavings("PDT", 3600000, 1950, 1966, 'w', 4, -1, 7, false, 7200000)
65  * .addRecurringSavings("PST", 0, 1950, 1961, 'w', 9, -1, 7, false, 7200000)
66  * .addRecurringSavings("PST", 0, 1962, 1966, 'w', 10, -1, 7, false, 7200000)
67  * .addRecurringSavings("PST", 0, 1967, 2147483647, 'w', 10, -1, 7, false, 7200000)
68  * .addRecurringSavings("PDT", 3600000, 1967, 1973, 'w', 4, -1, 7, false, 7200000)
69  * .addRecurringSavings("PDT", 3600000, 1974, 1974, 'w', 1, 6, 0, false, 7200000)
70  * .addRecurringSavings("PDT", 3600000, 1975, 1975, 'w', 2, 23, 0, false, 7200000)
71  * .addRecurringSavings("PDT", 3600000, 1976, 1986, 'w', 4, -1, 7, false, 7200000)
72  * .addRecurringSavings("PDT", 3600000, 1987, 2147483647, 'w', 4, 1, 7, true, 7200000)
73  * .toDateTimeZone("America/Los_Angeles");
74  * </pre>
75  *
76  * @author Brian S O'Neill
77  * @see ZoneInfoCompiler
78  * @see ZoneInfoProvider
79  * @since 1.0
80  */

81 public class DateTimeZoneBuilder {
82     /**
83      * Decodes a built DateTimeZone from the given stream, as encoded by
84      * writeTo.
85      *
86      * @param in input stream to read encoded DateTimeZone from.
87      * @param id time zone id to assign
88      */

89     public static DateTimeZone readFrom(InputStream JavaDoc in, String JavaDoc id) throws IOException JavaDoc {
90         if (in instanceof DataInput JavaDoc) {
91             return readFrom((DataInput JavaDoc)in, id);
92         } else {
93             return readFrom((DataInput JavaDoc)new DataInputStream JavaDoc(in), id);
94         }
95     }
96
97     /**
98      * Decodes a built DateTimeZone from the given stream, as encoded by
99      * writeTo.
100      *
101      * @param in input stream to read encoded DateTimeZone from.
102      * @param id time zone id to assign
103      */

104     public static DateTimeZone readFrom(DataInput JavaDoc in, String JavaDoc id) throws IOException JavaDoc {
105         switch (in.readUnsignedByte()) {
106         case 'F':
107             DateTimeZone fixed = new FixedDateTimeZone
108                 (id, in.readUTF(), (int)readMillis(in), (int)readMillis(in));
109             if (fixed.equals(DateTimeZone.UTC)) {
110                 fixed = DateTimeZone.UTC;
111             }
112             return fixed;
113         case 'C':
114             return CachedDateTimeZone.forZone(PrecalculatedZone.readFrom(in, id));
115         case 'P':
116             return PrecalculatedZone.readFrom(in, id);
117         default:
118             throw new IOException JavaDoc("Invalid encoding");
119         }
120     }
121
122     /**
123      * Millisecond encoding formats:
124      *
125      * upper two bits units field length approximate range
126      * ---------------------------------------------------------------
127      * 00 30 minutes 1 byte +/- 16 hours
128      * 01 minutes 4 bytes +/- 1020 years
129      * 10 seconds 5 bytes +/- 4355 years
130      * 11 millis 9 bytes +/- 292,000,000 years
131      *
132      * Remaining bits in field form signed offset from 1970-01-01T00:00:00Z.
133      */

134     static void writeMillis(DataOutput JavaDoc out, long millis) throws IOException JavaDoc {
135         if (millis % (30 * 60000L) == 0) {
136             // Try to write in 30 minute units.
137
long units = millis / (30 * 60000L);
138             if (((units << (64 - 6)) >> (64 - 6)) == units) {
139                 // Form 00 (6 bits effective precision)
140
out.writeByte((int)(units & 0x3f));
141                 return;
142             }
143         }
144
145         if (millis % 60000L == 0) {
146             // Try to write minutes.
147
long minutes = millis / 60000L;
148             if (((minutes << (64 - 30)) >> (64 - 30)) == minutes) {
149                 // Form 01 (30 bits effective precision)
150
out.writeInt(0x40000000 | (int)(minutes & 0x3fffffff));
151                 return;
152             }
153         }
154         
155         if (millis % 1000L == 0) {
156             // Try to write seconds.
157
long seconds = millis / 1000L;
158             if (((seconds << (64 - 38)) >> (64 - 38)) == seconds) {
159                 // Form 10 (38 bits effective precision)
160
out.writeByte(0x80 | (int)((seconds >> 32) & 0x3f));
161                 out.writeInt((int)(seconds & 0xffffffff));
162                 return;
163             }
164         }
165
166         // Write milliseconds either because the additional precision is
167
// required or the minutes didn't fit in the field.
168

169         // Form 11 (64 bits effective precision, but write as if 70 bits)
170
out.writeByte(millis < 0 ? 0xff : 0xc0);
171         out.writeLong(millis);
172     }
173
174     /**
175      * Reads encoding generated by writeMillis.
176      */

177     static long readMillis(DataInput JavaDoc in) throws IOException JavaDoc {
178         int v = in.readUnsignedByte();
179         switch (v >> 6) {
180         case 0: default:
181             // Form 00 (6 bits effective precision)
182
v = (v << (32 - 6)) >> (32 - 6);
183             return v * (30 * 60000L);
184
185         case 1:
186             // Form 01 (30 bits effective precision)
187
v = (v << (32 - 6)) >> (32 - 30);
188             v |= (in.readUnsignedByte()) << 16;
189             v |= (in.readUnsignedByte()) << 8;
190             v |= (in.readUnsignedByte());
191             return v * 60000L;
192
193         case 2:
194             // Form 10 (38 bits effective precision)
195
long w = (((long)v) << (64 - 6)) >> (64 - 38);
196             w |= (in.readUnsignedByte()) << 24;
197             w |= (in.readUnsignedByte()) << 16;
198             w |= (in.readUnsignedByte()) << 8;
199             w |= (in.readUnsignedByte());
200             return w * 1000L;
201
202         case 3:
203             // Form 11 (64 bits effective precision)
204
return in.readLong();
205         }
206     }
207
208     private static DateTimeZone buildFixedZone(String JavaDoc id, String JavaDoc nameKey,
209                                                int wallOffset, int standardOffset) {
210         if ("UTC".equals(id) && id.equals(nameKey) &&
211             wallOffset == 0 && standardOffset == 0) {
212             return DateTimeZone.UTC;
213         }
214         return new FixedDateTimeZone(id, nameKey, wallOffset, standardOffset);
215     }
216
217     // List of RuleSets.
218
private final ArrayList JavaDoc iRuleSets;
219
220     public DateTimeZoneBuilder() {
221         iRuleSets = new ArrayList JavaDoc(10);
222     }
223
224     /**
225      * Adds a cutover for added rules. The standard offset at the cutover
226      * defaults to 0. Call setStandardOffset afterwards to change it.
227      *
228      * @param year year of cutover
229      * @param mode 'u' - cutover is measured against UTC, 'w' - against wall
230      * offset, 's' - against standard offset.
231      * @param dayOfMonth if negative, set to ((last day of month) - ~dayOfMonth).
232      * For example, if -1, set to last day of month
233      * @param dayOfWeek if 0, ignore
234      * @param advanceDayOfWeek if dayOfMonth does not fall on dayOfWeek, advance to
235      * dayOfWeek when true, retreat when false.
236      * @param millisOfDay additional precision for specifying time of day of
237      * cutover
238      */

239     public DateTimeZoneBuilder addCutover(int year,
240                                           char mode,
241                                           int monthOfYear,
242                                           int dayOfMonth,
243                                           int dayOfWeek,
244                                           boolean advanceDayOfWeek,
245                                           int millisOfDay)
246     {
247         OfYear ofYear = new OfYear
248             (mode, monthOfYear, dayOfMonth, dayOfWeek, advanceDayOfWeek, millisOfDay);
249         if (iRuleSets.size() > 0) {
250             RuleSet lastRuleSet = (RuleSet)iRuleSets.get(iRuleSets.size() - 1);
251             lastRuleSet.setUpperLimit(year, ofYear);
252         }
253         iRuleSets.add(new RuleSet());
254         return this;
255     }
256
257     /**
258      * Sets the standard offset to use for newly added rules until the next
259      * cutover is added.
260      */

261     public DateTimeZoneBuilder setStandardOffset(int standardOffset) {
262         getLastRuleSet().setStandardOffset(standardOffset);
263         return this;
264     }
265
266     /**
267      * Set a fixed savings rule at the cutover.
268      */

269     public DateTimeZoneBuilder setFixedSavings(String JavaDoc nameKey, int saveMillis) {
270         getLastRuleSet().setFixedSavings(nameKey, saveMillis);
271         return this;
272     }
273
274     /**
275      * Add a recurring daylight saving time rule.
276      *
277      * @param nameKey name key of new rule
278      * @param saveMillis milliseconds to add to standard offset
279      * @param fromYear First year that rule is in effect. MIN_VALUE indicates
280      * beginning of time.
281      * @param toYear Last year (inclusive) that rule is in effect. MAX_VALUE
282      * indicates end of time.
283      * @param mode 'u' - transitions are calculated against UTC, 'w' -
284      * transitions are calculated against wall offset, 's' - transitions are
285      * calculated against standard offset.
286      * @param dayOfMonth if negative, set to ((last day of month) - ~dayOfMonth).
287      * For example, if -1, set to last day of month
288      * @param dayOfWeek if 0, ignore
289      * @param advanceDayOfWeek if dayOfMonth does not fall on dayOfWeek, advance to
290      * dayOfWeek when true, retreat when false.
291      * @param millisOfDay additional precision for specifying time of day of
292      * transitions
293      */

294     public DateTimeZoneBuilder addRecurringSavings(String JavaDoc nameKey, int saveMillis,
295                                                    int fromYear, int toYear,
296                                                    char mode,
297                                                    int monthOfYear,
298                                                    int dayOfMonth,
299                                                    int dayOfWeek,
300                                                    boolean advanceDayOfWeek,
301                                                    int millisOfDay)
302     {
303         if (fromYear <= toYear) {
304             OfYear ofYear = new OfYear
305                 (mode, monthOfYear, dayOfMonth, dayOfWeek, advanceDayOfWeek, millisOfDay);
306             Recurrence recurrence = new Recurrence(ofYear, nameKey, saveMillis);
307             Rule rule = new Rule(recurrence, fromYear, toYear);
308             getLastRuleSet().addRule(rule);
309         }
310         return this;
311     }
312
313     private RuleSet getLastRuleSet() {
314         if (iRuleSets.size() == 0) {
315             addCutover(Integer.MIN_VALUE, 'w', 1, 1, 0, false, 0);
316         }
317         return (RuleSet)iRuleSets.get(iRuleSets.size() - 1);
318     }
319     
320     /**
321      * Processes all the rules and builds a DateTimeZone.
322      *
323      * @param id time zone id to assign
324      */

325     public DateTimeZone toDateTimeZone(String JavaDoc id) {
326         if (id == null) {
327             throw new IllegalArgumentException JavaDoc();
328         }
329
330         // Discover where all the transitions occur and store the results in
331
// these lists.
332
ArrayList JavaDoc transitions = new ArrayList JavaDoc();
333
334         // Tail zone picks up remaining transitions in the form of an endless
335
// DST cycle.
336
DSTZone tailZone = null;
337
338         long millis = Long.MIN_VALUE;
339         int saveMillis = 0;
340             
341         int ruleSetCount = iRuleSets.size();
342         for (int i=0; i<ruleSetCount; i++) {
343             RuleSet rs = (RuleSet)iRuleSets.get(i);
344             Transition next = rs.firstTransition(millis);
345             if (next == null) {
346                 continue;
347             }
348             addTransition(transitions, next);
349             millis = next.getMillis();
350             saveMillis = next.getSaveMillis();
351
352             // Copy it since we're going to destroy it.
353
rs = new RuleSet(rs);
354
355             while ((next = rs.nextTransition(millis, saveMillis)) != null) {
356                 if (addTransition(transitions, next)) {
357                     if (tailZone != null) {
358                         // Got the extra transition before DSTZone.
359
break;
360                     }
361                 }
362                 millis = next.getMillis();
363                 saveMillis = next.getSaveMillis();
364                 if (tailZone == null && i == ruleSetCount - 1) {
365                     tailZone = rs.buildTailZone(id);
366                     // If tailZone is not null, don't break out of main loop until
367
// at least one more transition is calculated. This ensures a
368
// correct 'seam' to the DSTZone.
369
}
370             }
371
372             millis = rs.getUpperLimit(saveMillis);
373         }
374
375         // Check if a simpler zone implementation can be returned.
376
if (transitions.size() == 0) {
377             if (tailZone != null) {
378                 // This shouldn't happen, but handle just in case.
379
return tailZone;
380             }
381             return buildFixedZone(id, "UTC", 0, 0);
382         }
383         if (transitions.size() == 1 && tailZone == null) {
384             Transition tr = (Transition)transitions.get(0);
385             return buildFixedZone(id, tr.getNameKey(),
386                                   tr.getWallOffset(), tr.getStandardOffset());
387         }
388
389         PrecalculatedZone zone = new PrecalculatedZone(id, transitions, tailZone);
390         if (zone.isCachable()) {
391             return CachedDateTimeZone.forZone(zone);
392         }
393         return zone;
394     }
395
396     private boolean addTransition(ArrayList JavaDoc transitions, Transition tr) {
397         int size = transitions.size();
398         if (size == 0) {
399             transitions.add(tr);
400             return true;
401         }
402
403         Transition last = (Transition)transitions.get(size - 1);
404         if (!tr.isTransitionFrom(last)) {
405             return false;
406         }
407
408         // If local time of new transition is same as last local time, just
409
// replace last transition with new one.
410
int offsetForLast = 0;
411         if (size >= 2) {
412             offsetForLast = ((Transition)transitions.get(size - 2)).getWallOffset();
413         }
414         int offsetForNew = last.getWallOffset();
415
416         long lastLocal = last.getMillis() + offsetForLast;
417         long newLocal = tr.getMillis() + offsetForNew;
418
419         if (newLocal != lastLocal) {
420             transitions.add(tr);
421             return true;
422         }
423
424         transitions.remove(size - 1);
425         return addTransition(transitions, tr);
426     }
427
428     /**
429      * Encodes a built DateTimeZone to the given stream. Call readFrom to
430      * decode the data into a DateTimeZone object.
431      *
432      * @param out output stream to receive encoded DateTimeZone.
433      */

434     public void writeTo(OutputStream JavaDoc out) throws IOException JavaDoc {
435         if (out instanceof DataOutput JavaDoc) {
436             writeTo((DataOutput JavaDoc)out);
437         } else {
438             writeTo((DataOutput JavaDoc)new DataOutputStream JavaDoc(out));
439         }
440     }
441
442     /**
443      * Encodes a built DateTimeZone to the given stream. Call readFrom to
444      * decode the data into a DateTimeZone object.
445      *
446      * @param out output stream to receive encoded DateTimeZone.
447      */

448     public void writeTo(DataOutput JavaDoc out) throws IOException JavaDoc {
449         // The zone id is not written out, so the empty string is okay.
450
DateTimeZone zone = toDateTimeZone("");
451
452         if (zone instanceof FixedDateTimeZone) {
453             out.writeByte('F'); // 'F' for fixed
454
out.writeUTF(zone.getNameKey(0));
455             writeMillis(out, zone.getOffset(0));
456             writeMillis(out, zone.getStandardOffset(0));
457         } else {
458             if (zone instanceof CachedDateTimeZone) {
459                 out.writeByte('C'); // 'C' for cached, precalculated
460
zone = ((CachedDateTimeZone)zone).getUncachedZone();
461             } else {
462                 out.writeByte('P'); // 'P' for precalculated, uncached
463
}
464             ((PrecalculatedZone)zone).writeTo(out);
465         }
466     }
467
468     /**
469      * Supports setting fields of year and moving between transitions.
470      */

471     private static final class OfYear {
472         static OfYear readFrom(DataInput JavaDoc in) throws IOException JavaDoc {
473             return new OfYear((char)in.readUnsignedByte(),
474                               (int)in.readUnsignedByte(),
475                               (int)in.readByte(),
476                               (int)in.readUnsignedByte(),
477                               in.readBoolean(),
478                               (int)readMillis(in));
479         }
480
481         // Is 'u', 'w', or 's'.
482
final char iMode;
483
484         final int iMonthOfYear;
485         final int iDayOfMonth;
486         final int iDayOfWeek;
487         final boolean iAdvance;
488         final int iMillisOfDay;
489
490         OfYear(char mode,
491                int monthOfYear,
492                int dayOfMonth,
493                int dayOfWeek, boolean advanceDayOfWeek,
494                int millisOfDay)
495         {
496             if (mode != 'u' && mode != 'w' && mode != 's') {
497                 throw new IllegalArgumentException JavaDoc("Unknown mode: " + mode);
498             }
499
500             iMode = mode;
501             iMonthOfYear = monthOfYear;
502             iDayOfMonth = dayOfMonth;
503             iDayOfWeek = dayOfWeek;
504             iAdvance = advanceDayOfWeek;
505             iMillisOfDay = millisOfDay;
506         }
507
508         /**
509          * @param standardOffset standard offset just before instant
510          */

511         public long setInstant(int year, int standardOffset, int saveMillis) {
512             int offset;
513             if (iMode == 'w') {
514                 offset = standardOffset + saveMillis;
515             } else if (iMode == 's') {
516                 offset = standardOffset;
517             } else {
518                 offset = 0;
519             }
520
521             Chronology chrono = ISOChronology.getInstanceUTC();
522             long millis = chrono.year().set(0, year);
523             millis = chrono.monthOfYear().set(millis, iMonthOfYear);
524             millis = chrono.millisOfDay().set(millis, iMillisOfDay);
525             millis = setDayOfMonth(chrono, millis);
526
527             if (iDayOfWeek != 0) {
528                 millis = setDayOfWeek(chrono, millis);
529             }
530
531             // Convert from local time to UTC.
532
return millis - offset;
533         }
534
535         /**
536          * @param standardOffset standard offset just before next recurrence
537          */

538         public long next(long instant, int standardOffset, int saveMillis) {
539             int offset;
540             if (iMode == 'w') {
541                 offset = standardOffset + saveMillis;
542             } else if (iMode == 's') {
543                 offset = standardOffset;
544             } else {
545                 offset = 0;
546             }
547
548             // Convert from UTC to local time.
549
instant += offset;
550
551             Chronology chrono = ISOChronology.getInstanceUTC();
552             long next = chrono.monthOfYear().set(instant, iMonthOfYear);
553             // Be lenient with millisOfDay.
554
next = chrono.millisOfDay().set(next, 0);
555             next = chrono.millisOfDay().add(next, iMillisOfDay);
556             next = setDayOfMonthNext(chrono, next);
557
558             if (iDayOfWeek == 0) {
559                 if (next <= instant) {
560                     next = chrono.year().add(next, 1);
561                     next = setDayOfMonthNext(chrono, next);
562                 }
563             } else {
564                 next = setDayOfWeek(chrono, next);
565                 if (next <= instant) {
566                     next = chrono.year().add(next, 1);
567                     next = chrono.monthOfYear().set(next, iMonthOfYear);
568                     next = setDayOfMonthNext(chrono, next);
569                     next = setDayOfWeek(chrono, next);
570                 }
571             }
572
573             // Convert from local time to UTC.
574
return next - offset;
575         }
576
577         /**
578          * @param standardOffset standard offset just before previous recurrence
579          */

580         public long previous(long instant, int standardOffset, int saveMillis) {
581             int offset;
582             if (iMode == 'w') {
583                 offset = standardOffset + saveMillis;
584             } else if (iMode == 's') {
585                 offset = standardOffset;
586             } else {
587                 offset = 0;
588             }
589
590             // Convert from UTC to local time.
591
instant += offset;
592
593             Chronology chrono = ISOChronology.getInstanceUTC();
594             long prev = chrono.monthOfYear().set(instant, iMonthOfYear);
595             // Be lenient with millisOfDay.
596
prev = chrono.millisOfDay().set(prev, 0);
597             prev = chrono.millisOfDay().add(prev, iMillisOfDay);
598             prev = setDayOfMonthPrevious(chrono, prev);
599
600             if (iDayOfWeek == 0) {
601                 if (prev >= instant) {
602                     prev = chrono.year().add(prev, -1);
603                     prev = setDayOfMonthPrevious(chrono, prev);
604                 }
605             } else {
606                 prev = setDayOfWeek(chrono, prev);
607                 if (prev >= instant) {
608                     prev = chrono.year().add(prev, -1);
609                     prev = chrono.monthOfYear().set(prev, iMonthOfYear);
610                     prev = setDayOfMonthPrevious(chrono, prev);
611                     prev = setDayOfWeek(chrono, prev);
612                 }
613             }
614
615             // Convert from local time to UTC.
616
return prev - offset;
617         }
618
619         public boolean equals(Object JavaDoc obj) {
620             if (this == obj) {
621                 return true;
622             }
623             if (obj instanceof OfYear) {
624                 OfYear other = (OfYear)obj;
625                 return
626                     iMode == other.iMode &&
627                     iMonthOfYear == other.iMonthOfYear &&
628                     iDayOfMonth == other.iDayOfMonth &&
629                     iDayOfWeek == other.iDayOfWeek &&
630                     iAdvance == other.iAdvance &&
631                     iMillisOfDay == other.iMillisOfDay;
632             }
633             return false;
634         }
635
636         /*
637         public String toString() {
638             return
639                 "[OfYear]\n" +
640                 "Mode: " + iMode + '\n' +
641                 "MonthOfYear: " + iMonthOfYear + '\n' +
642                 "DayOfMonth: " + iDayOfMonth + '\n' +
643                 "DayOfWeek: " + iDayOfWeek + '\n' +
644                 "AdvanceDayOfWeek: " + iAdvance + '\n' +
645                 "MillisOfDay: " + iMillisOfDay + '\n';
646         }
647         */

648
649         public void writeTo(DataOutput JavaDoc out) throws IOException JavaDoc {
650             out.writeByte(iMode);
651             out.writeByte(iMonthOfYear);
652             out.writeByte(iDayOfMonth);
653             out.writeByte(iDayOfWeek);
654             out.writeBoolean(iAdvance);
655             writeMillis(out, iMillisOfDay);
656         }
657
658         /**
659          * If month-day is 02-29 and year isn't leap, advances to next leap year.
660          */

661         private long setDayOfMonthNext(Chronology chrono, long next) {
662             try {
663                 next = setDayOfMonth(chrono, next);
664             } catch (IllegalArgumentException JavaDoc e) {
665                 if (iMonthOfYear == 2 && iDayOfMonth == 29) {
666                     while (chrono.year().isLeap(next) == false) {
667                         next = chrono.year().add(next, 1);
668                     }
669                     next = setDayOfMonth(chrono, next);
670                 } else {
671                     throw e;
672                 }
673             }
674             return next;
675         }
676
677         /**
678          * If month-day is 02-29 and year isn't leap, retreats to previous leap year.
679          */

680         private long setDayOfMonthPrevious(Chronology chrono, long prev) {
681             try {
682                 prev = setDayOfMonth(chrono, prev);
683             } catch (IllegalArgumentException JavaDoc e) {
684                 if (iMonthOfYear == 2 && iDayOfMonth == 29) {
685                     while (chrono.year().isLeap(prev) == false) {
686                         prev = chrono.year().add(prev, -1);
687                     }
688                     prev = setDayOfMonth(chrono, prev);
689                 } else {
690                     throw e;
691                 }
692             }
693             return prev;
694         }
695
696         private long setDayOfMonth(Chronology chrono, long instant) {
697             if (iDayOfMonth >= 0) {
698                 instant = chrono.dayOfMonth().set(instant, iDayOfMonth);
699             } else {
700                 instant = chrono.dayOfMonth().set(instant, 1);
701                 instant = chrono.monthOfYear().add(instant, 1);
702                 instant = chrono.dayOfMonth().add(instant, iDayOfMonth);
703             }
704             return instant;
705         }
706
707         private long setDayOfWeek(Chronology chrono, long instant) {
708             int dayOfWeek = chrono.dayOfWeek().get(instant);
709             int daysToAdd = iDayOfWeek - dayOfWeek;
710             if (daysToAdd != 0) {
711                 if (iAdvance) {
712                     if (daysToAdd < 0) {
713                         daysToAdd += 7;
714                     }
715                 } else {
716                     if (daysToAdd > 0) {
717                         daysToAdd -= 7;
718                     }
719                 }
720                 instant = chrono.dayOfWeek().add(instant, daysToAdd);
721             }
722             return instant;
723         }
724     }
725
726     /**
727      * Extends OfYear with a nameKey and savings.
728      */

729     private static final class Recurrence {
730         static Recurrence readFrom(DataInput JavaDoc in) throws IOException JavaDoc {
731             return new Recurrence(OfYear.readFrom(in), in.readUTF(), (int)readMillis(in));
732         }
733
734         final OfYear iOfYear;
735         final String JavaDoc iNameKey;
736         final int iSaveMillis;
737
738         Recurrence(OfYear ofYear, String JavaDoc nameKey, int saveMillis) {
739             iOfYear = ofYear;
740             iNameKey = nameKey;
741             iSaveMillis = saveMillis;
742         }
743
744         public OfYear getOfYear() {
745             return iOfYear;
746         }
747
748         /**
749          * @param standardOffset standard offset just before next recurrence
750          */

751         public long next(long instant, int standardOffset, int saveMillis) {
752             return iOfYear.next(instant, standardOffset, saveMillis);
753         }
754
755         /**
756          * @param standardOffset standard offset just before previous recurrence
757          */

758         public long previous(long instant, int standardOffset, int saveMillis) {
759             return iOfYear.previous(instant, standardOffset, saveMillis);
760         }
761
762         public String JavaDoc getNameKey() {
763             return iNameKey;
764         }
765
766         public int getSaveMillis() {
767             return iSaveMillis;
768         }
769
770         public boolean equals(Object JavaDoc obj) {
771             if (this == obj) {
772                 return true;
773             }
774             if (obj instanceof Recurrence) {
775                 Recurrence other = (Recurrence)obj;
776                 return
777                     iSaveMillis == other.iSaveMillis &&
778                     iNameKey.equals(other.iNameKey) &&
779                     iOfYear.equals(other.iOfYear);
780             }
781             return false;
782         }
783
784         public void writeTo(DataOutput JavaDoc out) throws IOException JavaDoc {
785             iOfYear.writeTo(out);
786             out.writeUTF(iNameKey);
787             writeMillis(out, iSaveMillis);
788         }
789     }
790
791     /**
792      * Extends Recurrence with inclusive year limits.
793      */

794     private static final class Rule {
795         final Recurrence iRecurrence;
796         final int iFromYear; // inclusive
797
final int iToYear; // inclusive
798

799         Rule(Recurrence recurrence, int fromYear, int toYear) {
800             iRecurrence = recurrence;
801             iFromYear = fromYear;
802             iToYear = toYear;
803         }
804
805         public int getFromYear() {
806             return iFromYear;
807         }
808
809         public int getToYear() {
810             return iToYear;
811         }
812
813         public OfYear getOfYear() {
814             return iRecurrence.getOfYear();
815         }
816
817         public String JavaDoc getNameKey() {
818             return iRecurrence.getNameKey();
819         }
820
821         public int getSaveMillis() {
822             return iRecurrence.getSaveMillis();
823         }
824
825         public long next(final long instant, int standardOffset, int saveMillis) {
826             Chronology chrono = ISOChronology.getInstanceUTC();
827
828             final int wallOffset = standardOffset + saveMillis;
829             long testInstant = instant;
830
831             int year;
832             if (instant == Long.MIN_VALUE) {
833                 year = Integer.MIN_VALUE;
834             } else {
835                 year = chrono.year().get(instant + wallOffset);
836             }
837
838             if (year < iFromYear) {
839                 // First advance instant to start of from year.
840
testInstant = chrono.year().set(0, iFromYear) - wallOffset;
841                 // Back off one millisecond to account for next recurrence
842
// being exactly at the beginning of the year.
843
testInstant -= 1;
844             }
845
846             long next = iRecurrence.next(testInstant, standardOffset, saveMillis);
847
848             if (next > instant) {
849                 year = chrono.year().get(next + wallOffset);
850                 if (year > iToYear) {
851                     // Out of range, return original value.
852
next = instant;
853                 }
854             }
855
856             return next;
857         }
858     }
859
860     private static final class Transition {
861         private final long iMillis;
862         private final String JavaDoc iNameKey;
863         private final int iWallOffset;
864         private final int iStandardOffset;
865
866         Transition(long millis, Transition tr) {
867             iMillis = millis;
868             iNameKey = tr.iNameKey;
869             iWallOffset = tr.iWallOffset;
870             iStandardOffset = tr.iStandardOffset;
871         }
872
873         Transition(long millis, Rule rule, int standardOffset) {
874             iMillis = millis;
875             iNameKey = rule.getNameKey();
876             iWallOffset = standardOffset + rule.getSaveMillis();
877             iStandardOffset = standardOffset;
878         }
879
880         Transition(long millis, String JavaDoc nameKey,
881                    int wallOffset, int standardOffset) {
882             iMillis = millis;
883             iNameKey = nameKey;
884             iWallOffset = wallOffset;
885             iStandardOffset = standardOffset;
886         }
887
888         public long getMillis() {
889             return iMillis;
890         }
891
892         public String JavaDoc getNameKey() {
893             return iNameKey;
894         }
895
896         public int getWallOffset() {
897             return iWallOffset;
898         }
899
900         public int getStandardOffset() {
901             return iStandardOffset;
902         }
903
904         public int getSaveMillis() {
905             return iWallOffset - iStandardOffset;
906         }
907
908         /**
909          * There must be a change in the millis, wall offsets or name keys.
910          */

911         public boolean isTransitionFrom(Transition other) {
912             if (other == null) {
913                 return true;
914             }
915             return iMillis > other.iMillis &&
916                 (iWallOffset != other.iWallOffset ||
917                  //iStandardOffset != other.iStandardOffset ||
918
!(iNameKey.equals(other.iNameKey)));
919         }
920     }
921
922     private static final class RuleSet {
923         private static final int YEAR_LIMIT;
924
925         static {
926             // Don't pre-calculate more than 100 years into the future. Almost
927
// all zones will stop pre-calculating far sooner anyhow. Either a
928
// simple DST cycle is detected or the last rule is a fixed
929
// offset. If a zone has a fixed offset set more than 100 years
930
// into the future, then it won't be observed.
931
long now = DateTimeUtils.currentTimeMillis();
932             YEAR_LIMIT = ISOChronology.getInstanceUTC().year().get(now) + 100;
933         }
934
935         private int iStandardOffset;
936         private ArrayList JavaDoc iRules;
937
938         // Optional.
939
private String JavaDoc iInitialNameKey;
940         private int iInitialSaveMillis;
941
942         // Upper limit is exclusive.
943
private int iUpperYear;
944         private OfYear iUpperOfYear;
945
946         RuleSet() {
947             iRules = new ArrayList JavaDoc(10);
948             iUpperYear = Integer.MAX_VALUE;
949         }
950
951         /**
952          * Copy constructor.
953          */

954         RuleSet(RuleSet rs) {
955             iStandardOffset = rs.iStandardOffset;
956             iRules = new ArrayList JavaDoc(rs.iRules);
957             iInitialNameKey = rs.iInitialNameKey;
958             iInitialSaveMillis = rs.iInitialSaveMillis;
959             iUpperYear = rs.iUpperYear;
960             iUpperOfYear = rs.iUpperOfYear;
961         }
962
963         public int getStandardOffset() {
964             return iStandardOffset;
965         }
966
967         public void setStandardOffset(int standardOffset) {
968             iStandardOffset = standardOffset;
969         }
970
971         public void setFixedSavings(String JavaDoc nameKey, int saveMillis) {
972             iInitialNameKey = nameKey;
973             iInitialSaveMillis = saveMillis;
974         }
975
976         public void addRule(Rule rule) {
977             if (!iRules.contains(rule)) {
978                 iRules.add(rule);
979             }
980         }
981
982         public void setUpperLimit(int year, OfYear ofYear) {
983             iUpperYear = year;
984             iUpperOfYear = ofYear;
985         }
986
987         /**
988          * Returns a transition at firstMillis with the first name key and
989          * offsets for this rule set. This method may return null.
990          *
991          * @param firstMillis millis of first transition
992          */

993         public Transition firstTransition(final long firstMillis) {
994             if (iInitialNameKey != null) {
995                 // Initial zone info explicitly set, so don't search the rules.
996
return new Transition(firstMillis, iInitialNameKey,
997                                       iStandardOffset + iInitialSaveMillis, iStandardOffset);
998             }
999
1000            // Make a copy before we destroy the rules.
1001
ArrayList JavaDoc copy = new ArrayList JavaDoc(iRules);
1002
1003            // Iterate through all the transitions until firstMillis is
1004
// reached. Use the name key and savings for whatever rule reaches
1005
// the limit.
1006

1007            long millis = Long.MIN_VALUE;
1008            int saveMillis = 0;
1009            Transition first = null;
1010
1011            Transition next;
1012            while ((next = nextTransition(millis, saveMillis)) != null) {
1013                millis = next.getMillis();
1014
1015                if (millis == firstMillis) {
1016                    first = new Transition(firstMillis, next);
1017                    break;
1018                }
1019
1020                if (millis > firstMillis) {
1021                    if (first == null) {
1022                        // Find first rule without savings. This way a more
1023
// accurate nameKey is found even though no rule
1024
// extends to the RuleSet's lower limit.
1025
Iterator JavaDoc it = copy.iterator();
1026                        while (it.hasNext()) {
1027                            Rule rule = (Rule)it.next();
1028                            if (rule.getSaveMillis() == 0) {
1029                                first = new Transition(firstMillis, rule, iStandardOffset);
1030                                break;
1031                            }
1032                        }
1033                    }
1034                    if (first == null) {
1035                        // Found no rule without savings. Create a transition
1036
// with no savings anyhow, and use the best available
1037
// name key.
1038
first = new Transition(firstMillis, next.getNameKey(),
1039                                               iStandardOffset, iStandardOffset);
1040                    }
1041                    break;
1042                }
1043                
1044                // Set first to the best transition found so far, but next
1045
// iteration may find something closer to lower limit.
1046
first = new Transition(firstMillis, next);
1047
1048                saveMillis = next.getSaveMillis();
1049            }
1050
1051            iRules = copy;
1052            return first;
1053        }
1054
1055        /**
1056         * Returns null if RuleSet is exhausted or upper limit reached. Calling
1057         * this method will throw away rules as they each become
1058         * exhausted. Copy the RuleSet before using it to compute transitions.
1059         *
1060         * Returned transition may be a duplicate from previous
1061         * transition. Caller must call isTransitionFrom to filter out
1062         * duplicates.
1063         *
1064         * @param saveMillis savings before next transition
1065         */

1066        public Transition nextTransition(final long instant, final int saveMillis) {
1067            Chronology chrono = ISOChronology.getInstanceUTC();
1068
1069            // Find next matching rule.
1070
Rule nextRule = null;
1071            long nextMillis = Long.MAX_VALUE;
1072            
1073            Iterator JavaDoc it = iRules.iterator();
1074            while (it.hasNext()) {
1075                Rule rule = (Rule)it.next();
1076                long next = rule.next(instant, iStandardOffset, saveMillis);
1077                if (next <= instant) {
1078                    it.remove();
1079                    continue;
1080                }
1081                // Even if next is same as previous next, choose the rule
1082
// in order for more recently added rules to override.
1083
if (next <= nextMillis) {
1084                    // Found a better match.
1085
nextRule = rule;
1086                    nextMillis = next;
1087                }
1088            }
1089            
1090            if (nextRule == null) {
1091                return null;
1092            }
1093            
1094            // Stop precalculating if year reaches some arbitrary limit.
1095
if (chrono.year().get(nextMillis) >= YEAR_LIMIT) {
1096                return null;
1097            }
1098            
1099            // Check if upper limit reached or passed.
1100
if (iUpperYear < Integer.MAX_VALUE) {
1101                long upperMillis =
1102                    iUpperOfYear.setInstant(iUpperYear, iStandardOffset, saveMillis);
1103                if (nextMillis >= upperMillis) {
1104                    // At or after upper limit.
1105
return null;
1106                }
1107            }
1108            
1109            return new Transition(nextMillis, nextRule, iStandardOffset);
1110        }
1111
1112        /**
1113         * @param saveMillis savings before upper limit
1114         */

1115        public long getUpperLimit(int saveMillis) {
1116            if (iUpperYear == Integer.MAX_VALUE) {
1117                return Long.MAX_VALUE;
1118            }
1119            return iUpperOfYear.setInstant(iUpperYear, iStandardOffset, saveMillis);
1120        }
1121
1122        /**
1123         * Returns null if none can be built.
1124         */

1125        public DSTZone buildTailZone(String JavaDoc id) {
1126            if (iRules.size() == 2) {
1127                Rule startRule = (Rule)iRules.get(0);
1128                Rule endRule = (Rule)iRules.get(1);
1129                if (startRule.getToYear() == Integer.MAX_VALUE &&
1130                    endRule.getToYear() == Integer.MAX_VALUE) {
1131
1132                    // With exactly two infinitely recurring rules left, a
1133
// simple DSTZone can be formed.
1134

1135                    // The order of rules can come in any order, and it doesn't
1136
// really matter which rule was chosen the 'start' and
1137
// which is chosen the 'end'. DSTZone works properly either
1138
// way.
1139
return new DSTZone(id, iStandardOffset,
1140                                       startRule.iRecurrence, endRule.iRecurrence);
1141                }
1142            }
1143            return null;
1144        }
1145    }
1146
1147    private static final class DSTZone extends DateTimeZone {
1148        private static final long serialVersionUID = 6941492635554961361L;
1149
1150        static DSTZone readFrom(DataInput JavaDoc in, String JavaDoc id) throws IOException JavaDoc {
1151            return new DSTZone(id, (int)readMillis(in),
1152                               Recurrence.readFrom(in), Recurrence.readFrom(in));
1153        }
1154
1155        private final int iStandardOffset;
1156        private final Recurrence iStartRecurrence;
1157        private final Recurrence iEndRecurrence;
1158
1159        DSTZone(String JavaDoc id, int standardOffset,
1160                Recurrence startRecurrence, Recurrence endRecurrence) {
1161            super(id);
1162            iStandardOffset = standardOffset;
1163            iStartRecurrence = startRecurrence;
1164            iEndRecurrence = endRecurrence;
1165        }
1166
1167        public String JavaDoc getNameKey(long instant) {
1168            return findMatchingRecurrence(instant).getNameKey();
1169        }
1170
1171        public int getOffset(long instant) {
1172            return iStandardOffset + findMatchingRecurrence(instant).getSaveMillis();
1173        }
1174
1175        public int getStandardOffset(long instant) {
1176            return iStandardOffset;
1177        }
1178
1179        public boolean isFixed() {
1180            return false;
1181        }
1182
1183        public long nextTransition(long instant) {
1184            int standardOffset = iStandardOffset;
1185            Recurrence startRecurrence = iStartRecurrence;
1186            Recurrence endRecurrence = iEndRecurrence;
1187
1188            long start, end;
1189
1190            try {
1191                start = startRecurrence.next
1192                    (instant, standardOffset, endRecurrence.getSaveMillis());
1193                if (instant > 0 && start < 0) {
1194                    // Overflowed.
1195
start = instant;
1196                }
1197            } catch (IllegalArgumentException JavaDoc e) {
1198                // Overflowed.
1199
start = instant;
1200            } catch (ArithmeticException JavaDoc e) {
1201                // Overflowed.
1202
start = instant;
1203            }
1204
1205            try {
1206                end = endRecurrence.next
1207                    (instant, standardOffset, startRecurrence.getSaveMillis());
1208                if (instant > 0 && end < 0) {
1209                    // Overflowed.
1210
end = instant;
1211                }
1212            } catch (IllegalArgumentException JavaDoc e) {
1213                // Overflowed.
1214
end = instant;
1215            } catch (ArithmeticException JavaDoc e) {
1216                // Overflowed.
1217
end = instant;
1218            }
1219
1220            return (start > end) ? end : start;
1221        }
1222
1223        public long previousTransition(long instant) {
1224            // Increment in order to handle the case where instant is exactly at
1225
// a transition.
1226
instant++;
1227
1228            int standardOffset = iStandardOffset;
1229            Recurrence startRecurrence = iStartRecurrence;
1230            Recurrence endRecurrence = iEndRecurrence;
1231
1232            long start, end;
1233
1234            try {
1235                start = startRecurrence.previous
1236                    (instant, standardOffset, endRecurrence.getSaveMillis());
1237                if (instant < 0 && start > 0) {
1238                    // Overflowed.
1239
start = instant;
1240                }
1241            } catch (IllegalArgumentException JavaDoc e) {
1242                // Overflowed.
1243
start = instant;
1244            } catch (ArithmeticException JavaDoc e) {
1245                // Overflowed.
1246
start = instant;
1247            }
1248
1249            try {
1250                end = endRecurrence.previous
1251                    (instant, standardOffset, startRecurrence.getSaveMillis());
1252                if (instant < 0 && end > 0) {
1253                    // Overflowed.
1254
end = instant;
1255                }
1256            } catch (IllegalArgumentException JavaDoc e) {
1257                // Overflowed.
1258
end = instant;
1259            } catch (ArithmeticException JavaDoc e) {
1260                // Overflowed.
1261
end = instant;
1262            }
1263
1264            return ((start > end) ? start : end) - 1;
1265        }
1266
1267        public boolean equals(Object JavaDoc obj) {
1268            if (this == obj) {
1269                return true;
1270            }
1271            if (obj instanceof DSTZone) {
1272                DSTZone other = (DSTZone)obj;
1273                return
1274                    getID().equals(other.getID()) &&
1275                    iStandardOffset == other.iStandardOffset &&
1276                    iStartRecurrence.equals(other.iStartRecurrence) &&
1277                    iEndRecurrence.equals(other.iEndRecurrence);
1278            }
1279            return false;
1280        }
1281
1282        public void writeTo(DataOutput JavaDoc out) throws IOException JavaDoc {
1283            writeMillis(out, iStandardOffset);
1284            iStartRecurrence.writeTo(out);
1285            iEndRecurrence.writeTo(out);
1286        }
1287
1288        private Recurrence findMatchingRecurrence(long instant) {
1289            int standardOffset = iStandardOffset;
1290            Recurrence startRecurrence = iStartRecurrence;
1291            Recurrence endRecurrence = iEndRecurrence;
1292
1293            long start, end;
1294
1295            try {
1296                start = startRecurrence.next
1297                    (instant, standardOffset, endRecurrence.getSaveMillis());
1298            } catch (IllegalArgumentException JavaDoc e) {
1299                // Overflowed.
1300
start = instant;
1301            } catch (ArithmeticException JavaDoc e) {
1302                // Overflowed.
1303
start = instant;
1304            }
1305
1306            try {
1307                end = endRecurrence.next
1308                    (instant, standardOffset, startRecurrence.getSaveMillis());
1309            } catch (IllegalArgumentException JavaDoc e) {
1310                // Overflowed.
1311
end = instant;
1312            } catch (ArithmeticException JavaDoc e) {
1313                // Overflowed.
1314
end = instant;
1315            }
1316
1317            return (start > end) ? startRecurrence : endRecurrence;
1318        }
1319    }
1320
1321    private static final class PrecalculatedZone extends DateTimeZone {
1322        private static final long serialVersionUID = 7811976468055766265L;
1323
1324        static PrecalculatedZone readFrom(DataInput JavaDoc in, String JavaDoc id) throws IOException JavaDoc {
1325            // Read string pool.
1326
int poolSize = in.readUnsignedShort();
1327            String JavaDoc[] pool = new String JavaDoc[poolSize];
1328            for (int i=0; i<poolSize; i++) {
1329                pool[i] = in.readUTF();
1330            }
1331
1332            int size = in.readInt();
1333            long[] transitions = new long[size];
1334            int[] wallOffsets = new int[size];
1335            int[] standardOffsets = new int[size];
1336            String JavaDoc[] nameKeys = new String JavaDoc[size];
1337            
1338            for (int i=0; i<size; i++) {
1339                transitions[i] = readMillis(in);
1340                wallOffsets[i] = (int)readMillis(in);
1341                standardOffsets[i] = (int)readMillis(in);
1342                try {
1343                    int index;
1344                    if (poolSize < 256) {
1345                        index = in.readUnsignedByte();
1346                    } else {
1347                        index = in.readUnsignedShort();
1348                    }
1349                    nameKeys[i] = pool[index];
1350                } catch (ArrayIndexOutOfBoundsException JavaDoc e) {
1351                    throw new IOException JavaDoc("Invalid encoding");
1352                }
1353            }
1354
1355            DSTZone tailZone = null;
1356            if (in.readBoolean()) {
1357                tailZone = DSTZone.readFrom(in, id);
1358            }
1359
1360            return new PrecalculatedZone
1361                (id, transitions, wallOffsets, standardOffsets, nameKeys, tailZone);
1362        }
1363
1364        // All array fields have the same length.
1365

1366        private final long[] iTransitions;
1367
1368        private final int[] iWallOffsets;
1369        private final int[] iStandardOffsets;
1370        private final String JavaDoc[] iNameKeys;
1371
1372        private final DSTZone iTailZone;
1373
1374        PrecalculatedZone(String JavaDoc id, long[] transitions, int[] wallOffsets,
1375                          int[] standardOffsets, String JavaDoc[] nameKeys, DSTZone tailZone)
1376        {
1377            super(id);
1378            iTransitions = transitions;
1379            iWallOffsets = wallOffsets;
1380            iStandardOffsets = standardOffsets;
1381            iNameKeys = nameKeys;
1382            iTailZone = tailZone;
1383        }
1384
1385        /**
1386         * @param tailZone optional zone for getting info beyond precalculated
1387         * tables.
1388         */

1389        PrecalculatedZone(String JavaDoc id, ArrayList JavaDoc transitions, DSTZone tailZone) {
1390            super(id);
1391
1392            int size = transitions.size();
1393            if (size == 0) {
1394                throw new IllegalArgumentException JavaDoc();
1395            }
1396
1397            iTransitions = new long[size];
1398            iWallOffsets = new int[size];
1399            iStandardOffsets = new int[size];
1400            iNameKeys = new String JavaDoc[size];
1401
1402            Transition last = null;
1403            for (int i=0; i<size; i++) {
1404                Transition tr = (Transition)transitions.get(i);
1405
1406                if (!tr.isTransitionFrom(last)) {
1407                    throw new IllegalArgumentException JavaDoc(id);
1408                }
1409
1410                iTransitions[i] = tr.getMillis();
1411                iWallOffsets[i] = tr.getWallOffset();
1412                iStandardOffsets[i] = tr.getStandardOffset();
1413                iNameKeys[i] = tr.getNameKey();
1414
1415                last = tr;
1416            }
1417
1418            iTailZone = tailZone;
1419        }
1420
1421        public String JavaDoc getNameKey(long instant) {
1422            long[] transitions = iTransitions;
1423            int i = Arrays.binarySearch(transitions, instant);
1424            if (i >= 0) {
1425                return iNameKeys[i];
1426            }
1427            i = ~i;
1428            if (i < transitions.length) {
1429                if (i > 0) {
1430                    return iNameKeys[i - 1];
1431                }
1432                return "UTC";
1433            }
1434            if (iTailZone == null) {
1435                return iNameKeys[i - 1];
1436            }
1437            return iTailZone.getNameKey(instant);
1438        }
1439
1440        public int getOffset(long instant) {
1441            long[] transitions = iTransitions;
1442            int i = Arrays.binarySearch(transitions, instant);
1443            if (i >= 0) {
1444                return iWallOffsets[i];
1445            }
1446            i = ~i;
1447            if (i < transitions.length) {
1448                if (i > 0) {
1449                    return iWallOffsets[i - 1];
1450                }
1451                return 0;
1452            }
1453            if (iTailZone == null) {
1454                return iWallOffsets[i - 1];
1455            }
1456            return iTailZone.getOffset(instant);
1457        }
1458
1459        public int getStandardOffset(long instant) {
1460            long[] transitions = iTransitions;
1461            int i = Arrays.binarySearch(transitions, instant);
1462            if (i >= 0) {
1463                return iStandardOffsets[i];
1464            }
1465            i = ~i;
1466            if (i < transitions.length) {
1467                if (i > 0) {
1468                    return iStandardOffsets[i - 1];
1469                }
1470                return 0;
1471            }
1472            if (iTailZone == null) {
1473                return iStandardOffsets[i - 1];
1474            }
1475            return iTailZone.getStandardOffset(instant);
1476        }
1477
1478        public boolean isFixed() {
1479            return false;
1480        }
1481
1482        public long nextTransition(long instant) {
1483            long[] transitions = iTransitions;
1484            int i = Arrays.binarySearch(transitions, instant);
1485            i = (i >= 0) ? (i + 1) : ~i;
1486            if (i < transitions.length) {
1487                return transitions[i];
1488            }
1489            if (iTailZone == null) {
1490                return instant;
1491            }
1492            long end = transitions[transitions.length - 1];
1493            if (instant < end) {
1494                instant = end;
1495            }
1496            return iTailZone.nextTransition(instant);
1497        }
1498
1499        public long previousTransition(long instant) {
1500            long[] transitions = iTransitions;
1501            int i = Arrays.binarySearch(transitions, instant);
1502            if (i >= 0) {
1503                if (instant > Long.MIN_VALUE) {
1504                    return instant - 1;
1505                }
1506                return instant;
1507            }
1508            i = ~i;
1509            if (i < transitions.length) {
1510                if (i > 0) {
1511                    long prev = transitions[i - 1];
1512                    if (prev > Long.MIN_VALUE) {
1513                        return prev - 1;
1514                    }
1515                }
1516                return instant;
1517            }
1518            if (iTailZone != null) {
1519                long prev = iTailZone.previousTransition(instant);
1520                if (prev < instant) {
1521                    return prev;
1522                }
1523            }
1524            long prev = transitions[i - 1];
1525            if (prev > Long.MIN_VALUE) {
1526                return prev - 1;
1527            }
1528            return instant;
1529        }
1530
1531        public boolean equals(Object JavaDoc obj) {
1532            if (this == obj) {
1533                return true;
1534            }
1535            if (obj instanceof PrecalculatedZone) {
1536                PrecalculatedZone other = (PrecalculatedZone)obj;
1537                return
1538                    getID().equals(other.getID()) &&
1539                    Arrays.equals(iTransitions, other.iTransitions) &&
1540                    Arrays.equals(iNameKeys, other.iNameKeys) &&
1541                    Arrays.equals(iWallOffsets, other.iWallOffsets) &&
1542                    Arrays.equals(iStandardOffsets, other.iStandardOffsets) &&
1543                    ((iTailZone == null)
1544                     ? (null == other.iTailZone)
1545                     : (iTailZone.equals(other.iTailZone)));
1546            }
1547            return false;
1548        }
1549
1550        public void writeTo(DataOutput JavaDoc out) throws IOException JavaDoc {
1551            int size = iTransitions.length;
1552
1553            // Create unique string pool.
1554
Set JavaDoc poolSet = new HashSet JavaDoc();
1555            for (int i=0; i<size; i++) {
1556                poolSet.add(iNameKeys[i]);
1557            }
1558
1559            int poolSize = poolSet.size();
1560            if (poolSize > 65535) {
1561                throw new UnsupportedOperationException JavaDoc("String pool is too large");
1562            }
1563            String JavaDoc[] pool = new String JavaDoc[poolSize];
1564            Iterator JavaDoc it = poolSet.iterator();
1565            for (int i=0; it.hasNext(); i++) {
1566                pool[i] = (String JavaDoc)it.next();
1567            }
1568
1569            // Write out the pool.
1570
out.writeShort(poolSize);
1571            for (int i=0; i<poolSize; i++) {
1572                out.writeUTF(pool[i]);
1573            }
1574
1575            out.writeInt(size);
1576
1577            for (int i=0; i<size; i++) {
1578                writeMillis(out, iTransitions[i]);
1579                writeMillis(out, iWallOffsets[i]);
1580                writeMillis(out, iStandardOffsets[i]);
1581                
1582                // Find pool index and write it out.
1583
String JavaDoc nameKey = iNameKeys[i];
1584                for (int j=0; j<poolSize; j++) {
1585                    if (pool[j].equals(nameKey)) {
1586                        if (poolSize < 256) {
1587                            out.writeByte(j);
1588                        } else {
1589                            out.writeShort(j);
1590                        }
1591                        break;
1592                    }
1593                }
1594            }
1595
1596            out.writeBoolean(iTailZone != null);
1597            if (iTailZone != null) {
1598                iTailZone.writeTo(out);
1599            }
1600        }
1601
1602        public boolean isCachable() {
1603            if (iTailZone != null) {
1604                return true;
1605            }
1606            long[] transitions = iTransitions;
1607            if (transitions.length <= 1) {
1608                return false;
1609            }
1610
1611            // Add up all the distances between transitions that are less than
1612
// about two years.
1613
double distances = 0;
1614            int count = 0;
1615
1616            for (int i=1; i<transitions.length; i++) {
1617                long diff = transitions[i] - transitions[i - 1];
1618                if (diff < ((366L + 365) * 24 * 60 * 60 * 1000)) {
1619                    distances += (double)diff;
1620                    count++;
1621                }
1622            }
1623
1624            if (count > 0) {
1625                double avg = distances / count;
1626                avg /= 24 * 60 * 60 * 1000;
1627                if (avg >= 25) {
1628                    // Only bother caching if average distance between
1629
// transitions is at least 25 days. Why 25?
1630
// CachedDateTimeZone is more efficient if the distance
1631
// between transitions is large. With an average of 25, it
1632
// will on average perform about 2 tests per cache
1633
// hit. (49.7 / 25) is approximately 2.
1634
return true;
1635                }
1636            }
1637
1638            return false;
1639        }
1640    }
1641}
1642
Popular Tags