1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  package org.joda.time.tz;
17  
18  import java.io.DataInput;
19  import java.io.DataInputStream;
20  import java.io.DataOutput;
21  import java.io.DataOutputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.text.DateFormatSymbols;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.Locale;
31  import java.util.Set;
32  
33  import org.joda.time.Chronology;
34  import org.joda.time.DateTime;
35  import org.joda.time.DateTimeUtils;
36  import org.joda.time.DateTimeZone;
37  import org.joda.time.Period;
38  import org.joda.time.PeriodType;
39  import org.joda.time.chrono.ISOChronology;
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  public class DateTimeZoneBuilder {
87      
88  
89  
90  
91  
92  
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 
104 
105 
106 
107 
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 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139     static void writeMillis(DataOutput out, long millis) throws IOException {
140         if (millis % (30 * 60000L) == 0) {
141             
142             long units = millis / (30 * 60000L);
143             if (((units << (64 - 6)) >> (64 - 6)) == units) {
144                 
145                 out.writeByte((int)(units & 0x3f));
146                 return;
147             }
148         }
149 
150         if (millis % 60000L == 0) {
151             
152             long minutes = millis / 60000L;
153             if (((minutes << (64 - 30)) >> (64 - 30)) == minutes) {
154                 
155                 out.writeInt(0x40000000 | (int)(minutes & 0x3fffffff));
156                 return;
157             }
158         }
159         
160         if (millis % 1000L == 0) {
161             
162             long seconds = millis / 1000L;
163             if (((seconds << (64 - 38)) >> (64 - 38)) == seconds) {
164                 
165                 out.writeByte(0x80 | (int)((seconds >> 32) & 0x3f));
166                 out.writeInt((int)(seconds & 0xffffffff));
167                 return;
168             }
169         }
170 
171         
172         
173         
174         
175         out.writeByte(millis < 0 ? 0xff : 0xc0);
176         out.writeLong(millis);
177     }
178 
179     
180 
181 
182     static long readMillis(DataInput in) throws IOException {
183         int v = in.readUnsignedByte();
184         switch (v >> 6) {
185         case 0: default:
186             
187             v = (v << (32 - 6)) >> (32 - 6);
188             return v * (30 * 60000L);
189 
190         case 1:
191             
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             
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             
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     
223     private final ArrayList<RuleSet> iRuleSets;
224 
225     public DateTimeZoneBuilder() {
226         iRuleSets = new ArrayList<RuleSet>(10);
227     }
228 
229     
230 
231 
232 
233 
234 
235 
236 
237 
238 
239 
240 
241 
242 
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         if (iRuleSets.size() > 0) {
253             OfYear ofYear = new OfYear
254                 (mode, monthOfYear, dayOfMonth, dayOfWeek, advanceDayOfWeek, millisOfDay);
255             RuleSet lastRuleSet = iRuleSets.get(iRuleSets.size() - 1);
256             lastRuleSet.setUpperLimit(year, ofYear);
257         }
258         iRuleSets.add(new RuleSet());
259         return this;
260     }
261 
262     
263 
264 
265 
266 
267     public DateTimeZoneBuilder setStandardOffset(int standardOffset) {
268         getLastRuleSet().setStandardOffset(standardOffset);
269         return this;
270     }
271 
272     
273 
274 
275     public DateTimeZoneBuilder setFixedSavings(String nameKey, int saveMillis) {
276         getLastRuleSet().setFixedSavings(nameKey, saveMillis);
277         return this;
278     }
279 
280     
281 
282 
283 
284 
285 
286 
287 
288 
289 
290 
291 
292 
293 
294 
295 
296 
297 
298 
299 
300     public DateTimeZoneBuilder addRecurringSavings(String nameKey, int saveMillis,
301                                                    int fromYear, int toYear,
302                                                    char mode,
303                                                    int monthOfYear,
304                                                    int dayOfMonth,
305                                                    int dayOfWeek,
306                                                    boolean advanceDayOfWeek,
307                                                    int millisOfDay)
308     {
309         if (fromYear <= toYear) {
310             OfYear ofYear = new OfYear
311                 (mode, monthOfYear, dayOfMonth, dayOfWeek, advanceDayOfWeek, millisOfDay);
312             Recurrence recurrence = new Recurrence(ofYear, nameKey, saveMillis);
313             Rule rule = new Rule(recurrence, fromYear, toYear);
314             getLastRuleSet().addRule(rule);
315         }
316         return this;
317     }
318 
319     private RuleSet getLastRuleSet() {
320         if (iRuleSets.size() == 0) {
321             addCutover(Integer.MIN_VALUE, 'w', 1, 1, 0, false, 0);
322         }
323         return iRuleSets.get(iRuleSets.size() - 1);
324     }
325     
326     
327 
328 
329 
330 
331 
332     public DateTimeZone toDateTimeZone(String id, boolean outputID) {
333         if (id == null) {
334             throw new IllegalArgumentException();
335         }
336 
337         
338         
339         ArrayList<Transition> transitions = new ArrayList<Transition>();
340 
341         
342         
343         DSTZone tailZone = null;
344 
345         long millis = Long.MIN_VALUE;
346         int saveMillis = 0;
347             
348         int ruleSetCount = iRuleSets.size();
349         for (int i=0; i<ruleSetCount; i++) {
350             RuleSet rs = iRuleSets.get(i);
351             Transition next = rs.firstTransition(millis);
352             if (next == null) {
353                 continue;
354             }
355             addTransition(transitions, next);
356             millis = next.getMillis();
357             saveMillis = next.getSaveMillis();
358 
359             
360             rs = new RuleSet(rs);
361 
362             while ((next = rs.nextTransition(millis, saveMillis)) != null) {
363                 if (addTransition(transitions, next)) {
364                     if (tailZone != null) {
365                         
366                         break;
367                     }
368                 }
369                 millis = next.getMillis();
370                 saveMillis = next.getSaveMillis();
371                 if (tailZone == null && i == ruleSetCount - 1) {
372                     tailZone = rs.buildTailZone(id);
373                     
374                     
375                     
376                 }
377             }
378 
379             millis = rs.getUpperLimit(saveMillis);
380         }
381 
382         
383         if (transitions.size() == 0) {
384             if (tailZone != null) {
385                 
386                 return tailZone;
387             }
388             return buildFixedZone(id, "UTC", 0, 0);
389         }
390         if (transitions.size() == 1 && tailZone == null) {
391             Transition tr = transitions.get(0);
392             return buildFixedZone(id, tr.getNameKey(),
393                                   tr.getWallOffset(), tr.getStandardOffset());
394         }
395 
396         PrecalculatedZone zone = PrecalculatedZone.create(id, outputID, transitions, tailZone);
397         if (zone.isCachable()) {
398             return CachedDateTimeZone.forZone(zone);
399         }
400         return zone;
401     }
402 
403     private boolean addTransition(ArrayList<Transition> transitions, Transition tr) {
404         int size = transitions.size();
405         if (size == 0) {
406             transitions.add(tr);
407             return true;
408         }
409 
410         Transition last = transitions.get(size - 1);
411         if (!tr.isTransitionFrom(last)) {
412             return false;
413         }
414 
415         
416         
417         int offsetForLast = 0;
418         if (size >= 2) {
419             offsetForLast = transitions.get(size - 2).getWallOffset();
420         }
421         int offsetForNew = last.getWallOffset();
422 
423         long lastLocal = last.getMillis() + offsetForLast;
424         long newLocal = tr.getMillis() + offsetForNew;
425 
426         if (newLocal != lastLocal) {
427             transitions.add(tr);
428             return true;
429         }
430 
431         transitions.remove(size - 1);
432         return addTransition(transitions, tr);
433     }
434 
435     
436 
437 
438 
439 
440 
441 
442     public void writeTo(String zoneID, OutputStream out) throws IOException {
443         if (out instanceof DataOutput) {
444             writeTo(zoneID, (DataOutput)out);
445         } else {
446             writeTo(zoneID, (DataOutput)new DataOutputStream(out));
447         }
448     }
449 
450     
451 
452 
453 
454 
455 
456 
457     public void writeTo(String zoneID, DataOutput out) throws IOException {
458         
459         DateTimeZone zone = toDateTimeZone(zoneID, false);
460 
461         if (zone instanceof FixedDateTimeZone) {
462             out.writeByte('F'); 
463             out.writeUTF(zone.getNameKey(0));
464             writeMillis(out, zone.getOffset(0));
465             writeMillis(out, zone.getStandardOffset(0));
466         } else {
467             if (zone instanceof CachedDateTimeZone) {
468                 out.writeByte('C'); 
469                 zone = ((CachedDateTimeZone)zone).getUncachedZone();
470             } else {
471                 out.writeByte('P'); 
472             }
473             ((PrecalculatedZone)zone).writeTo(out);
474         }
475     }
476 
477     
478 
479 
480     private static final class OfYear {
481         static OfYear readFrom(DataInput in) throws IOException {
482             return new OfYear((char)in.readUnsignedByte(),
483                               (int)in.readUnsignedByte(),
484                               (int)in.readByte(),
485                               (int)in.readUnsignedByte(),
486                               in.readBoolean(),
487                               (int)readMillis(in));
488         }
489 
490         
491         final char iMode;
492 
493         final int iMonthOfYear;
494         final int iDayOfMonth;
495         final int iDayOfWeek;
496         final boolean iAdvance;
497         final int iMillisOfDay;
498 
499         OfYear(char mode,
500                int monthOfYear,
501                int dayOfMonth,
502                int dayOfWeek, boolean advanceDayOfWeek,
503                int millisOfDay)
504         {
505             if (mode != 'u' && mode != 'w' && mode != 's') {
506                 throw new IllegalArgumentException("Unknown mode: " + mode);
507             }
508 
509             iMode = mode;
510             iMonthOfYear = monthOfYear;
511             iDayOfMonth = dayOfMonth;
512             iDayOfWeek = dayOfWeek;
513             iAdvance = advanceDayOfWeek;
514             iMillisOfDay = millisOfDay;
515         }
516 
517         
518 
519 
520         public long setInstant(int year, int standardOffset, int saveMillis) {
521             int offset;
522             if (iMode == 'w') {
523                 offset = standardOffset + saveMillis;
524             } else if (iMode == 's') {
525                 offset = standardOffset;
526             } else {
527                 offset = 0;
528             }
529 
530             Chronology chrono = ISOChronology.getInstanceUTC();
531             long millis = chrono.year().set(0, year);
532             millis = chrono.monthOfYear().set(millis, iMonthOfYear);
533             millis = chrono.millisOfDay().set(millis, iMillisOfDay);
534             millis = setDayOfMonth(chrono, millis);
535 
536             if (iDayOfWeek != 0) {
537                 millis = setDayOfWeek(chrono, millis);
538             }
539 
540             
541             return millis - offset;
542         }
543 
544         
545 
546 
547         public long next(long instant, int standardOffset, int saveMillis) {
548             int offset;
549             if (iMode == 'w') {
550                 offset = standardOffset + saveMillis;
551             } else if (iMode == 's') {
552                 offset = standardOffset;
553             } else {
554                 offset = 0;
555             }
556 
557             
558             instant += offset;
559 
560             Chronology chrono = ISOChronology.getInstanceUTC();
561             long next = chrono.monthOfYear().set(instant, iMonthOfYear);
562             
563             next = chrono.millisOfDay().set(next, 0);
564             next = chrono.millisOfDay().add(next, iMillisOfDay);
565             next = setDayOfMonthNext(chrono, next);
566 
567             if (iDayOfWeek == 0) {
568                 if (next <= instant) {
569                     next = chrono.year().add(next, 1);
570                     next = setDayOfMonthNext(chrono, next);
571                 }
572             } else {
573                 next = setDayOfWeek(chrono, next);
574                 if (next <= instant) {
575                     next = chrono.year().add(next, 1);
576                     next = chrono.monthOfYear().set(next, iMonthOfYear);
577                     next = setDayOfMonthNext(chrono, next);
578                     next = setDayOfWeek(chrono, next);
579                 }
580             }
581 
582             
583             return next - offset;
584         }
585 
586         
587 
588 
589         public long previous(long instant, int standardOffset, int saveMillis) {
590             int offset;
591             if (iMode == 'w') {
592                 offset = standardOffset + saveMillis;
593             } else if (iMode == 's') {
594                 offset = standardOffset;
595             } else {
596                 offset = 0;
597             }
598 
599             
600             instant += offset;
601 
602             Chronology chrono = ISOChronology.getInstanceUTC();
603             long prev = chrono.monthOfYear().set(instant, iMonthOfYear);
604             
605             prev = chrono.millisOfDay().set(prev, 0);
606             prev = chrono.millisOfDay().add(prev, iMillisOfDay);
607             prev = setDayOfMonthPrevious(chrono, prev);
608 
609             if (iDayOfWeek == 0) {
610                 if (prev >= instant) {
611                     prev = chrono.year().add(prev, -1);
612                     prev = setDayOfMonthPrevious(chrono, prev);
613                 }
614             } else {
615                 prev = setDayOfWeek(chrono, prev);
616                 if (prev >= instant) {
617                     prev = chrono.year().add(prev, -1);
618                     prev = chrono.monthOfYear().set(prev, iMonthOfYear);
619                     prev = setDayOfMonthPrevious(chrono, prev);
620                     prev = setDayOfWeek(chrono, prev);
621                 }
622             }
623 
624             
625             return prev - offset;
626         }
627 
628         public boolean equals(Object obj) {
629             if (this == obj) {
630                 return true;
631             }
632             if (obj instanceof OfYear) {
633                 OfYear other = (OfYear)obj;
634                 return
635                     iMode == other.iMode &&
636                     iMonthOfYear == other.iMonthOfYear &&
637                     iDayOfMonth == other.iDayOfMonth &&
638                     iDayOfWeek == other.iDayOfWeek &&
639                     iAdvance == other.iAdvance &&
640                     iMillisOfDay == other.iMillisOfDay;
641             }
642             return false;
643         }
644 
645         
646 
647 
648 
649 
650 
651 
652 
653 
654 
655 
656 
657 
658         public void writeTo(DataOutput out) throws IOException {
659             out.writeByte(iMode);
660             out.writeByte(iMonthOfYear);
661             out.writeByte(iDayOfMonth);
662             out.writeByte(iDayOfWeek);
663             out.writeBoolean(iAdvance);
664             writeMillis(out, iMillisOfDay);
665         }
666 
667         
668 
669 
670         private long setDayOfMonthNext(Chronology chrono, long next) {
671             try {
672                 next = setDayOfMonth(chrono, next);
673             } catch (IllegalArgumentException e) {
674                 if (iMonthOfYear == 2 && iDayOfMonth == 29) {
675                     while (chrono.year().isLeap(next) == false) {
676                         next = chrono.year().add(next, 1);
677                     }
678                     next = setDayOfMonth(chrono, next);
679                 } else {
680                     throw e;
681                 }
682             }
683             return next;
684         }
685 
686         
687 
688 
689         private long setDayOfMonthPrevious(Chronology chrono, long prev) {
690             try {
691                 prev = setDayOfMonth(chrono, prev);
692             } catch (IllegalArgumentException e) {
693                 if (iMonthOfYear == 2 && iDayOfMonth == 29) {
694                     while (chrono.year().isLeap(prev) == false) {
695                         prev = chrono.year().add(prev, -1);
696                     }
697                     prev = setDayOfMonth(chrono, prev);
698                 } else {
699                     throw e;
700                 }
701             }
702             return prev;
703         }
704 
705         private long setDayOfMonth(Chronology chrono, long instant) {
706             if (iDayOfMonth >= 0) {
707                 instant = chrono.dayOfMonth().set(instant, iDayOfMonth);
708             } else {
709                 instant = chrono.dayOfMonth().set(instant, 1);
710                 instant = chrono.monthOfYear().add(instant, 1);
711                 instant = chrono.dayOfMonth().add(instant, iDayOfMonth);
712             }
713             return instant;
714         }
715 
716         private long setDayOfWeek(Chronology chrono, long instant) {
717             int dayOfWeek = chrono.dayOfWeek().get(instant);
718             int daysToAdd = iDayOfWeek - dayOfWeek;
719             if (daysToAdd != 0) {
720                 if (iAdvance) {
721                     if (daysToAdd < 0) {
722                         daysToAdd += 7;
723                     }
724                 } else {
725                     if (daysToAdd > 0) {
726                         daysToAdd -= 7;
727                     }
728                 }
729                 instant = chrono.dayOfWeek().add(instant, daysToAdd);
730             }
731             return instant;
732         }
733     }
734 
735     
736 
737 
738     private static final class Recurrence {
739         static Recurrence readFrom(DataInput in) throws IOException {
740             return new Recurrence(OfYear.readFrom(in), in.readUTF(), (int)readMillis(in));
741         }
742 
743         final OfYear iOfYear;
744         final String iNameKey;
745         final int iSaveMillis;
746 
747         Recurrence(OfYear ofYear, String nameKey, int saveMillis) {
748             iOfYear = ofYear;
749             iNameKey = nameKey;
750             iSaveMillis = saveMillis;
751         }
752 
753         public OfYear getOfYear() {
754             return iOfYear;
755         }
756 
757         
758 
759 
760         public long next(long instant, int standardOffset, int saveMillis) {
761             return iOfYear.next(instant, standardOffset, saveMillis);
762         }
763 
764         
765 
766 
767         public long previous(long instant, int standardOffset, int saveMillis) {
768             return iOfYear.previous(instant, standardOffset, saveMillis);
769         }
770 
771         public String getNameKey() {
772             return iNameKey;
773         }
774 
775         public int getSaveMillis() {
776             return iSaveMillis;
777         }
778 
779         public boolean equals(Object obj) {
780             if (this == obj) {
781                 return true;
782             }
783             if (obj instanceof Recurrence) {
784                 Recurrence other = (Recurrence)obj;
785                 return
786                     iSaveMillis == other.iSaveMillis &&
787                     iNameKey.equals(other.iNameKey) &&
788                     iOfYear.equals(other.iOfYear);
789             }
790             return false;
791         }
792 
793         public void writeTo(DataOutput out) throws IOException {
794             iOfYear.writeTo(out);
795             out.writeUTF(iNameKey);
796             writeMillis(out, iSaveMillis);
797         }
798 
799         Recurrence rename(String nameKey) {
800             return new Recurrence(iOfYear, nameKey, iSaveMillis);
801         }
802 
803         Recurrence renameAppend(String appendNameKey) {
804             return rename((iNameKey + appendNameKey).intern());
805         }
806     }
807 
808     
809 
810 
811     private static final class Rule {
812         final Recurrence iRecurrence;
813         final int iFromYear; 
814         final int iToYear;   
815 
816         Rule(Recurrence recurrence, int fromYear, int toYear) {
817             iRecurrence = recurrence;
818             iFromYear = fromYear;
819             iToYear = toYear;
820         }
821 
822         public int getFromYear() {
823             return iFromYear;
824         }
825 
826         public int getToYear() {
827             return iToYear;
828         }
829 
830         public OfYear getOfYear() {
831             return iRecurrence.getOfYear();
832         }
833 
834         public String getNameKey() {
835             return iRecurrence.getNameKey();
836         }
837 
838         public int getSaveMillis() {
839             return iRecurrence.getSaveMillis();
840         }
841 
842         public long next(final long instant, int standardOffset, int saveMillis) {
843             Chronology chrono = ISOChronology.getInstanceUTC();
844 
845             final int wallOffset = standardOffset + saveMillis;
846             long testInstant = instant;
847 
848             int year;
849             if (instant == Long.MIN_VALUE) {
850                 year = Integer.MIN_VALUE;
851             } else {
852                 year = chrono.year().get(instant + wallOffset);
853             }
854 
855             if (year < iFromYear) {
856                 
857                 testInstant = chrono.year().set(0, iFromYear) - wallOffset;
858                 
859                 
860                 testInstant -= 1;
861             }
862 
863             long next = iRecurrence.next(testInstant, standardOffset, saveMillis);
864 
865             if (next > instant) {
866                 year = chrono.year().get(next + wallOffset);
867                 if (year > iToYear) {
868                     
869                     next = instant;
870                 }
871             }
872 
873             return next;
874         }
875     }
876 
877     private static final class Transition {
878         private final long iMillis;
879         private final String iNameKey;
880         private final int iWallOffset;
881         private final int iStandardOffset;
882 
883         Transition(long millis, Transition tr) {
884             iMillis = millis;
885             iNameKey = tr.iNameKey;
886             iWallOffset = tr.iWallOffset;
887             iStandardOffset = tr.iStandardOffset;
888         }
889 
890         Transition(long millis, Rule rule, int standardOffset) {
891             iMillis = millis;
892             iNameKey = rule.getNameKey();
893             iWallOffset = standardOffset + rule.getSaveMillis();
894             iStandardOffset = standardOffset;
895         }
896 
897         Transition(long millis, String nameKey,
898                    int wallOffset, int standardOffset) {
899             iMillis = millis;
900             iNameKey = nameKey;
901             iWallOffset = wallOffset;
902             iStandardOffset = standardOffset;
903         }
904 
905         public long getMillis() {
906             return iMillis;
907         }
908 
909         public String getNameKey() {
910             return iNameKey;
911         }
912 
913         public int getWallOffset() {
914             return iWallOffset;
915         }
916 
917         public int getStandardOffset() {
918             return iStandardOffset;
919         }
920 
921         public int getSaveMillis() {
922             return iWallOffset - iStandardOffset;
923         }
924 
925         
926 
927 
928         public boolean isTransitionFrom(Transition other) {
929             if (other == null) {
930                 return true;
931             }
932             return iMillis > other.iMillis &&
933                 (iWallOffset != other.iWallOffset ||
934                  
935                  !(iNameKey.equals(other.iNameKey)));
936         }
937     }
938 
939     private static final class RuleSet {
940         private static final int YEAR_LIMIT;
941 
942         static {
943             
944             
945             
946             
947             
948             long now = DateTimeUtils.currentTimeMillis();
949             YEAR_LIMIT = ISOChronology.getInstanceUTC().year().get(now) + 100;
950         }
951 
952         private int iStandardOffset;
953         private ArrayList<Rule> iRules;
954 
955         
956         private String iInitialNameKey;
957         private int iInitialSaveMillis;
958 
959         
960         private int iUpperYear;
961         private OfYear iUpperOfYear;
962 
963         RuleSet() {
964             iRules = new ArrayList<Rule>(10);
965             iUpperYear = Integer.MAX_VALUE;
966         }
967 
968         
969 
970 
971         RuleSet(RuleSet rs) {
972             iStandardOffset = rs.iStandardOffset;
973             iRules = new ArrayList<Rule>(rs.iRules);
974             iInitialNameKey = rs.iInitialNameKey;
975             iInitialSaveMillis = rs.iInitialSaveMillis;
976             iUpperYear = rs.iUpperYear;
977             iUpperOfYear = rs.iUpperOfYear;
978         }
979 
980         public int getStandardOffset() {
981             return iStandardOffset;
982         }
983 
984         public void setStandardOffset(int standardOffset) {
985             iStandardOffset = standardOffset;
986         }
987 
988         public void setFixedSavings(String nameKey, int saveMillis) {
989             iInitialNameKey = nameKey;
990             iInitialSaveMillis = saveMillis;
991         }
992 
993         public void addRule(Rule rule) {
994             if (!iRules.contains(rule)) {
995                 iRules.add(rule);
996             }
997         }
998 
999         public void setUpperLimit(int year, OfYear ofYear) {
1000             iUpperYear = year;
1001             iUpperOfYear = ofYear;
1002         }
1003 
1004         
1005 
1006 
1007 
1008 
1009 
1010         public Transition firstTransition(final long firstMillis) {
1011             if (iInitialNameKey != null) {
1012                 
1013                 return new Transition(firstMillis, iInitialNameKey,
1014                                       iStandardOffset + iInitialSaveMillis, iStandardOffset);
1015             }
1016 
1017             
1018             ArrayList<Rule> copy = new ArrayList<Rule>(iRules);
1019 
1020             
1021             
1022             
1023 
1024             long millis = Long.MIN_VALUE;
1025             int saveMillis = 0;
1026             Transition first = null;
1027 
1028             Transition next;
1029             while ((next = nextTransition(millis, saveMillis)) != null) {
1030                 millis = next.getMillis();
1031 
1032                 if (millis == firstMillis) {
1033                     first = new Transition(firstMillis, next);
1034                     break;
1035                 }
1036 
1037                 if (millis > firstMillis) {
1038                     if (first == null) {
1039                         
1040                         
1041                         
1042                         for (Rule rule : copy) {
1043                             if (rule.getSaveMillis() == 0) {
1044                                 first = new Transition(firstMillis, rule, iStandardOffset);
1045                                 break;
1046                             }
1047                         }
1048                     }
1049                     if (first == null) {
1050                         
1051                         
1052                         
1053                         first = new Transition(firstMillis, next.getNameKey(),
1054                                                iStandardOffset, iStandardOffset);
1055                     }
1056                     break;
1057                 }
1058                 
1059                 
1060                 
1061                 first = new Transition(firstMillis, next);
1062 
1063                 saveMillis = next.getSaveMillis();
1064             }
1065 
1066             iRules = copy;
1067             return first;
1068         }
1069 
1070         
1071 
1072 
1073 
1074 
1075 
1076 
1077 
1078 
1079 
1080 
1081         public Transition nextTransition(final long instant, final int saveMillis) {
1082             Chronology chrono = ISOChronology.getInstanceUTC();
1083 
1084             
1085             Rule nextRule = null;
1086             long nextMillis = Long.MAX_VALUE;
1087             
1088             Iterator<Rule> it = iRules.iterator();
1089             while (it.hasNext()) {
1090                 Rule rule = it.next();
1091                 long next = rule.next(instant, iStandardOffset, saveMillis);
1092                 if (next <= instant) {
1093                     it.remove();
1094                     continue;
1095                 }
1096                 
1097                 
1098                 if (next <= nextMillis) {
1099                     
1100                     nextRule = rule;
1101                     nextMillis = next;
1102                 }
1103             }
1104             
1105             if (nextRule == null) {
1106                 return null;
1107             }
1108             
1109             
1110             if (chrono.year().get(nextMillis) >= YEAR_LIMIT) {
1111                 return null;
1112             }
1113             
1114             
1115             if (iUpperYear < Integer.MAX_VALUE) {
1116                 long upperMillis =
1117                     iUpperOfYear.setInstant(iUpperYear, iStandardOffset, saveMillis);
1118                 if (nextMillis >= upperMillis) {
1119                     
1120                     return null;
1121                 }
1122             }
1123             
1124             return new Transition(nextMillis, nextRule, iStandardOffset);
1125         }
1126 
1127         
1128 
1129 
1130         public long getUpperLimit(int saveMillis) {
1131             if (iUpperYear == Integer.MAX_VALUE) {
1132                 return Long.MAX_VALUE;
1133             }
1134             return iUpperOfYear.setInstant(iUpperYear, iStandardOffset, saveMillis);
1135         }
1136 
1137         
1138 
1139 
1140         public DSTZone buildTailZone(String id) {
1141             if (iRules.size() == 2) {
1142                 Rule startRule = iRules.get(0);
1143                 Rule endRule = iRules.get(1);
1144                 if (startRule.getToYear() == Integer.MAX_VALUE &&
1145                     endRule.getToYear() == Integer.MAX_VALUE) {
1146 
1147                     
1148                     
1149 
1150                     
1151                     
1152                     
1153                     
1154                     return new DSTZone(id, iStandardOffset,
1155                                        startRule.iRecurrence, endRule.iRecurrence);
1156                 }
1157             }
1158             return null;
1159         }
1160     }
1161 
1162     private static final class DSTZone extends DateTimeZone {
1163         private static final long serialVersionUID = 6941492635554961361L;
1164 
1165         static DSTZone readFrom(DataInput in, String id) throws IOException {
1166             return new DSTZone(id, (int)readMillis(in), 
1167                                Recurrence.readFrom(in), Recurrence.readFrom(in));
1168         }
1169 
1170         final int iStandardOffset;
1171         final Recurrence iStartRecurrence;
1172         final Recurrence iEndRecurrence;
1173 
1174         DSTZone(String id, int standardOffset,
1175                 Recurrence startRecurrence, Recurrence endRecurrence) {
1176             super(id);
1177             iStandardOffset = standardOffset;
1178             iStartRecurrence = startRecurrence;
1179             iEndRecurrence = endRecurrence;
1180         }
1181 
1182         public String getNameKey(long instant) {
1183             return findMatchingRecurrence(instant).getNameKey();
1184         }
1185 
1186         public int getOffset(long instant) {
1187             return iStandardOffset + findMatchingRecurrence(instant).getSaveMillis();
1188         }
1189 
1190         public int getStandardOffset(long instant) {
1191             return iStandardOffset;
1192         }
1193 
1194         public boolean isFixed() {
1195             return false;
1196         }
1197 
1198         public long nextTransition(long instant) {
1199             int standardOffset = iStandardOffset;
1200             Recurrence startRecurrence = iStartRecurrence;
1201             Recurrence endRecurrence = iEndRecurrence;
1202 
1203             long start, end;
1204 
1205             try {
1206                 start = startRecurrence.next
1207                     (instant, standardOffset, endRecurrence.getSaveMillis());
1208                 if (instant > 0 && start < 0) {
1209                     
1210                     start = instant;
1211                 }
1212             } catch (IllegalArgumentException e) {
1213                 
1214                 start = instant;
1215             } catch (ArithmeticException e) {
1216                 
1217                 start = instant;
1218             }
1219 
1220             try {
1221                 end = endRecurrence.next
1222                     (instant, standardOffset, startRecurrence.getSaveMillis());
1223                 if (instant > 0 && end < 0) {
1224                     
1225                     end = instant;
1226                 }
1227             } catch (IllegalArgumentException e) {
1228                 
1229                 end = instant;
1230             } catch (ArithmeticException e) {
1231                 
1232                 end = instant;
1233             }
1234 
1235             return (start > end) ? end : start;
1236         }
1237 
1238         public long previousTransition(long instant) {
1239             
1240             
1241             instant++;
1242 
1243             int standardOffset = iStandardOffset;
1244             Recurrence startRecurrence = iStartRecurrence;
1245             Recurrence endRecurrence = iEndRecurrence;
1246 
1247             long start, end;
1248 
1249             try {
1250                 start = startRecurrence.previous
1251                     (instant, standardOffset, endRecurrence.getSaveMillis());
1252                 if (instant < 0 && start > 0) {
1253                     
1254                     start = instant;
1255                 }
1256             } catch (IllegalArgumentException e) {
1257                 
1258                 start = instant;
1259             } catch (ArithmeticException e) {
1260                 
1261                 start = instant;
1262             }
1263 
1264             try {
1265                 end = endRecurrence.previous
1266                     (instant, standardOffset, startRecurrence.getSaveMillis());
1267                 if (instant < 0 && end > 0) {
1268                     
1269                     end = instant;
1270                 }
1271             } catch (IllegalArgumentException e) {
1272                 
1273                 end = instant;
1274             } catch (ArithmeticException e) {
1275                 
1276                 end = instant;
1277             }
1278 
1279             return ((start > end) ? start : end) - 1;
1280         }
1281 
1282         public boolean equals(Object obj) {
1283             if (this == obj) {
1284                 return true;
1285             }
1286             if (obj instanceof DSTZone) {
1287                 DSTZone other = (DSTZone)obj;
1288                 return
1289                     getID().equals(other.getID()) &&
1290                     iStandardOffset == other.iStandardOffset &&
1291                     iStartRecurrence.equals(other.iStartRecurrence) &&
1292                     iEndRecurrence.equals(other.iEndRecurrence);
1293             }
1294             return false;
1295         }
1296 
1297         public void writeTo(DataOutput out) throws IOException {
1298             writeMillis(out, iStandardOffset);
1299             iStartRecurrence.writeTo(out);
1300             iEndRecurrence.writeTo(out);
1301         }
1302 
1303         private Recurrence findMatchingRecurrence(long instant) {
1304             int standardOffset = iStandardOffset;
1305             Recurrence startRecurrence = iStartRecurrence;
1306             Recurrence endRecurrence = iEndRecurrence;
1307 
1308             long start, end;
1309 
1310             try {
1311                 start = startRecurrence.next
1312                     (instant, standardOffset, endRecurrence.getSaveMillis());
1313             } catch (IllegalArgumentException e) {
1314                 
1315                 start = instant;
1316             } catch (ArithmeticException e) {
1317                 
1318                 start = instant;
1319             }
1320 
1321             try {
1322                 end = endRecurrence.next
1323                     (instant, standardOffset, startRecurrence.getSaveMillis());
1324             } catch (IllegalArgumentException e) {
1325                 
1326                 end = instant;
1327             } catch (ArithmeticException e) {
1328                 
1329                 end = instant;
1330             }
1331 
1332             return (start > end) ? startRecurrence : endRecurrence;
1333         }
1334     }
1335 
1336     private static final class PrecalculatedZone extends DateTimeZone {
1337         private static final long serialVersionUID = 7811976468055766265L;
1338 
1339         static PrecalculatedZone readFrom(DataInput in, String id) throws IOException {
1340             
1341             int poolSize = in.readUnsignedShort();
1342             String[] pool = new String[poolSize];
1343             for (int i=0; i<poolSize; i++) {
1344                 pool[i] = in.readUTF();
1345             }
1346 
1347             int size = in.readInt();
1348             long[] transitions = new long[size];
1349             int[] wallOffsets = new int[size];
1350             int[] standardOffsets = new int[size];
1351             String[] nameKeys = new String[size];
1352             
1353             for (int i=0; i<size; i++) {
1354                 transitions[i] = readMillis(in);
1355                 wallOffsets[i] = (int)readMillis(in);
1356                 standardOffsets[i] = (int)readMillis(in);
1357                 try {
1358                     int index;
1359                     if (poolSize < 256) {
1360                         index = in.readUnsignedByte();
1361                     } else {
1362                         index = in.readUnsignedShort();
1363                     }
1364                     nameKeys[i] = pool[index];
1365                 } catch (ArrayIndexOutOfBoundsException e) {
1366                     throw new IOException("Invalid encoding");
1367                 }
1368             }
1369 
1370             DSTZone tailZone = null;
1371             if (in.readBoolean()) {
1372                 tailZone = DSTZone.readFrom(in, id);
1373             }
1374 
1375             return new PrecalculatedZone
1376                 (id, transitions, wallOffsets, standardOffsets, nameKeys, tailZone);
1377         }
1378 
1379         
1380 
1381 
1382 
1383 
1384 
1385 
1386 
1387         static PrecalculatedZone create(String id, boolean outputID, ArrayList<Transition> transitions,
1388                                         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 = 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             
1416             
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 
1426             Chronology chrono = ISOChronology.getInstanceUTC();
1427 
1428             for (int i = 0; i < nameKeys.length - 1; i++) {
1429                 String curNameKey = nameKeys[i];
1430                 String nextNameKey = nameKeys[i + 1];
1431                 long curOffset = wallOffsets[i];
1432                 long nextOffset = wallOffsets[i + 1];
1433                 long curStdOffset = standardOffsets[i];
1434                 long nextStdOffset = standardOffsets[i + 1];
1435                 Period p = new Period(trans[i], trans[i + 1], PeriodType.yearMonthDay(), chrono);
1436                 if (curOffset != nextOffset &&
1437                         curStdOffset == nextStdOffset &&
1438                         curNameKey.equals(nextNameKey) &&
1439                         p.getYears() == 0 && p.getMonths() > 4 && p.getMonths() < 8 &&
1440                         curNameKey.equals(zoneNameData[2]) &&
1441                         curNameKey.equals(zoneNameData[4])) {
1442                     
1443                     if (ZoneInfoCompiler.verbose()) {
1444                         System.out.println("Fixing duplicate name key - " + nextNameKey);
1445                         System.out.println("     - " + new DateTime(trans[i], chrono) +
1446                                            " - " + new DateTime(trans[i + 1], chrono));
1447                     }
1448                     if (curOffset > nextOffset) {
1449                         nameKeys[i] = (curNameKey + "-Summer").intern();
1450                     } else if (curOffset < nextOffset) {
1451                         nameKeys[i + 1] = (nextNameKey + "-Summer").intern();
1452                         i++;
1453                     }
1454                 }
1455             }
1456 
1457             if (tailZone != null) {
1458                 if (tailZone.iStartRecurrence.getNameKey()
1459                     .equals(tailZone.iEndRecurrence.getNameKey())) {
1460                     if (ZoneInfoCompiler.verbose()) {
1461                         System.out.println("Fixing duplicate recurrent name key - " +
1462                                            tailZone.iStartRecurrence.getNameKey());
1463                     }
1464                     if (tailZone.iStartRecurrence.getSaveMillis() > 0) {
1465                         tailZone = new DSTZone(
1466                             tailZone.getID(),
1467                             tailZone.iStandardOffset,
1468                             tailZone.iStartRecurrence.renameAppend("-Summer"),
1469                             tailZone.iEndRecurrence);
1470                     } else {
1471                         tailZone = new DSTZone(
1472                             tailZone.getID(),
1473                             tailZone.iStandardOffset,
1474                             tailZone.iStartRecurrence,
1475                             tailZone.iEndRecurrence.renameAppend("-Summer"));
1476                     }
1477                 }
1478             }
1479             
1480             return new PrecalculatedZone
1481                 ((outputID ? id : ""), trans, wallOffsets, standardOffsets, nameKeys, tailZone);
1482         }
1483 
1484         
1485 
1486         private final long[] iTransitions;
1487 
1488         private final int[] iWallOffsets;
1489         private final int[] iStandardOffsets;
1490         private final String[] iNameKeys;
1491 
1492         private final DSTZone iTailZone;
1493 
1494         
1495 
1496 
1497         private PrecalculatedZone(String id, long[] transitions, int[] wallOffsets,
1498                           int[] standardOffsets, String[] nameKeys, DSTZone tailZone)
1499         {
1500             super(id);
1501             iTransitions = transitions;
1502             iWallOffsets = wallOffsets;
1503             iStandardOffsets = standardOffsets;
1504             iNameKeys = nameKeys;
1505             iTailZone = tailZone;
1506         }
1507 
1508         public String getNameKey(long instant) {
1509             long[] transitions = iTransitions;
1510             int i = Arrays.binarySearch(transitions, instant);
1511             if (i >= 0) {
1512                 return iNameKeys[i];
1513             }
1514             i = ~i;
1515             if (i < transitions.length) {
1516                 if (i > 0) {
1517                     return iNameKeys[i - 1];
1518                 }
1519                 return "UTC";
1520             }
1521             if (iTailZone == null) {
1522                 return iNameKeys[i - 1];
1523             }
1524             return iTailZone.getNameKey(instant);
1525         }
1526 
1527         public int getOffset(long instant) {
1528             long[] transitions = iTransitions;
1529             int i = Arrays.binarySearch(transitions, instant);
1530             if (i >= 0) {
1531                 return iWallOffsets[i];
1532             }
1533             i = ~i;
1534             if (i < transitions.length) {
1535                 if (i > 0) {
1536                     return iWallOffsets[i - 1];
1537                 }
1538                 return 0;
1539             }
1540             if (iTailZone == null) {
1541                 return iWallOffsets[i - 1];
1542             }
1543             return iTailZone.getOffset(instant);
1544         }
1545 
1546         public int getStandardOffset(long instant) {
1547             long[] transitions = iTransitions;
1548             int i = Arrays.binarySearch(transitions, instant);
1549             if (i >= 0) {
1550                 return iStandardOffsets[i];
1551             }
1552             i = ~i;
1553             if (i < transitions.length) {
1554                 if (i > 0) {
1555                     return iStandardOffsets[i - 1];
1556                 }
1557                 return 0;
1558             }
1559             if (iTailZone == null) {
1560                 return iStandardOffsets[i - 1];
1561             }
1562             return iTailZone.getStandardOffset(instant);
1563         }
1564 
1565         public boolean isFixed() {
1566             return false;
1567         }
1568 
1569         public long nextTransition(long instant) {
1570             long[] transitions = iTransitions;
1571             int i = Arrays.binarySearch(transitions, instant);
1572             i = (i >= 0) ? (i + 1) : ~i;
1573             if (i < transitions.length) {
1574                 return transitions[i];
1575             }
1576             if (iTailZone == null) {
1577                 return instant;
1578             }
1579             long end = transitions[transitions.length - 1];
1580             if (instant < end) {
1581                 instant = end;
1582             }
1583             return iTailZone.nextTransition(instant);
1584         }
1585 
1586         public long previousTransition(long instant) {
1587             long[] transitions = iTransitions;
1588             int i = Arrays.binarySearch(transitions, instant);
1589             if (i >= 0) {
1590                 if (instant > Long.MIN_VALUE) {
1591                     return instant - 1;
1592                 }
1593                 return instant;
1594             }
1595             i = ~i;
1596             if (i < transitions.length) {
1597                 if (i > 0) {
1598                     long prev = transitions[i - 1];
1599                     if (prev > Long.MIN_VALUE) {
1600                         return prev - 1;
1601                     }
1602                 }
1603                 return instant;
1604             }
1605             if (iTailZone != null) {
1606                 long prev = iTailZone.previousTransition(instant);
1607                 if (prev < instant) {
1608                     return prev;
1609                 }
1610             }
1611             long prev = transitions[i - 1];
1612             if (prev > Long.MIN_VALUE) {
1613                 return prev - 1;
1614             }
1615             return instant;
1616         }
1617 
1618         public boolean equals(Object obj) {
1619             if (this == obj) {
1620                 return true;
1621             }
1622             if (obj instanceof PrecalculatedZone) {
1623                 PrecalculatedZone other = (PrecalculatedZone)obj;
1624                 return
1625                     getID().equals(other.getID()) &&
1626                     Arrays.equals(iTransitions, other.iTransitions) &&
1627                     Arrays.equals(iNameKeys, other.iNameKeys) &&
1628                     Arrays.equals(iWallOffsets, other.iWallOffsets) &&
1629                     Arrays.equals(iStandardOffsets, other.iStandardOffsets) &&
1630                     ((iTailZone == null)
1631                      ? (null == other.iTailZone)
1632                      : (iTailZone.equals(other.iTailZone)));
1633             }
1634             return false;
1635         }
1636 
1637         public void writeTo(DataOutput out) throws IOException {
1638             int size = iTransitions.length;
1639 
1640             
1641             Set<String> poolSet = new HashSet<String>();
1642             for (int i=0; i<size; i++) {
1643                 poolSet.add(iNameKeys[i]);
1644             }
1645 
1646             int poolSize = poolSet.size();
1647             if (poolSize > 65535) {
1648                 throw new UnsupportedOperationException("String pool is too large");
1649             }
1650             String[] pool = new String[poolSize];
1651             Iterator<String> it = poolSet.iterator();
1652             for (int i=0; it.hasNext(); i++) {
1653                 pool[i] = it.next();
1654             }
1655 
1656             
1657             out.writeShort(poolSize);
1658             for (int i=0; i<poolSize; i++) {
1659                 out.writeUTF(pool[i]);
1660             }
1661 
1662             out.writeInt(size);
1663 
1664             for (int i=0; i<size; i++) {
1665                 writeMillis(out, iTransitions[i]);
1666                 writeMillis(out, iWallOffsets[i]);
1667                 writeMillis(out, iStandardOffsets[i]);
1668                 
1669                 
1670                 String nameKey = iNameKeys[i];
1671                 for (int j=0; j<poolSize; j++) {
1672                     if (pool[j].equals(nameKey)) {
1673                         if (poolSize < 256) {
1674                             out.writeByte(j);
1675                         } else {
1676                             out.writeShort(j);
1677                         }
1678                         break;
1679                     }
1680                 }
1681             }
1682 
1683             out.writeBoolean(iTailZone != null);
1684             if (iTailZone != null) {
1685                 iTailZone.writeTo(out);
1686             }
1687         }
1688 
1689         public boolean isCachable() {
1690             if (iTailZone != null) {
1691                 return true;
1692             }
1693             long[] transitions = iTransitions;
1694             if (transitions.length <= 1) {
1695                 return false;
1696             }
1697 
1698             
1699             
1700             double distances = 0;
1701             int count = 0;
1702 
1703             for (int i=1; i<transitions.length; i++) {
1704                 long diff = transitions[i] - transitions[i - 1];
1705                 if (diff < ((366L + 365) * 24 * 60 * 60 * 1000)) {
1706                     distances += (double)diff;
1707                     count++;
1708                 }
1709             }
1710 
1711             if (count > 0) {
1712                 double avg = distances / count;
1713                 avg /= 24 * 60 * 60 * 1000;
1714                 if (avg >= 25) {
1715                     
1716                     
1717                     
1718                     
1719                     
1720                     
1721                     return true;
1722                 }
1723             }
1724 
1725             return false;
1726         }
1727     }
1728 }