1 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.util.ArrayList ; 26 import java.util.Arrays ; 27 import java.util.HashSet ; 28 import java.util.Iterator ; 29 import java.util.Set ; 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 81 public class DateTimeZoneBuilder { 82 89 public static DateTimeZone readFrom(InputStream in, String id) throws IOException { 90 if (in instanceof DataInput ) { 91 return readFrom((DataInput )in, id); 92 } else { 93 return readFrom((DataInput )new DataInputStream (in), id); 94 } 95 } 96 97 104 public static DateTimeZone readFrom(DataInput in, String id) throws IOException { 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 ("Invalid encoding"); 119 } 120 } 121 122 134 static void writeMillis(DataOutput out, long millis) throws IOException { 135 if (millis % (30 * 60000L) == 0) { 136 long units = millis / (30 * 60000L); 138 if (((units << (64 - 6)) >> (64 - 6)) == units) { 139 out.writeByte((int)(units & 0x3f)); 141 return; 142 } 143 } 144 145 if (millis % 60000L == 0) { 146 long minutes = millis / 60000L; 148 if (((minutes << (64 - 30)) >> (64 - 30)) == minutes) { 149 out.writeInt(0x40000000 | (int)(minutes & 0x3fffffff)); 151 return; 152 } 153 } 154 155 if (millis % 1000L == 0) { 156 long seconds = millis / 1000L; 158 if (((seconds << (64 - 38)) >> (64 - 38)) == seconds) { 159 out.writeByte(0x80 | (int)((seconds >> 32) & 0x3f)); 161 out.writeInt((int)(seconds & 0xffffffff)); 162 return; 163 } 164 } 165 166 169 out.writeByte(millis < 0 ? 0xff : 0xc0); 171 out.writeLong(millis); 172 } 173 174 177 static long readMillis(DataInput in) throws IOException { 178 int v = in.readUnsignedByte(); 179 switch (v >> 6) { 180 case 0: default: 181 v = (v << (32 - 6)) >> (32 - 6); 183 return v * (30 * 60000L); 184 185 case 1: 186 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 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 return in.readLong(); 205 } 206 } 207 208 private static DateTimeZone buildFixedZone(String id, String 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 private final ArrayList iRuleSets; 219 220 public DateTimeZoneBuilder() { 221 iRuleSets = new ArrayList (10); 222 } 223 224 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 261 public DateTimeZoneBuilder setStandardOffset(int standardOffset) { 262 getLastRuleSet().setStandardOffset(standardOffset); 263 return this; 264 } 265 266 269 public DateTimeZoneBuilder setFixedSavings(String nameKey, int saveMillis) { 270 getLastRuleSet().setFixedSavings(nameKey, saveMillis); 271 return this; 272 } 273 274 294 public DateTimeZoneBuilder addRecurringSavings(String 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 325 public DateTimeZone toDateTimeZone(String id) { 326 if (id == null) { 327 throw new IllegalArgumentException (); 328 } 329 330 ArrayList transitions = new ArrayList (); 333 334 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 rs = new RuleSet(rs); 354 355 while ((next = rs.nextTransition(millis, saveMillis)) != null) { 356 if (addTransition(transitions, next)) { 357 if (tailZone != null) { 358 break; 360 } 361 } 362 millis = next.getMillis(); 363 saveMillis = next.getSaveMillis(); 364 if (tailZone == null && i == ruleSetCount - 1) { 365 tailZone = rs.buildTailZone(id); 366 } 370 } 371 372 millis = rs.getUpperLimit(saveMillis); 373 } 374 375 if (transitions.size() == 0) { 377 if (tailZone != null) { 378 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 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 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 434 public void writeTo(OutputStream out) throws IOException { 435 if (out instanceof DataOutput ) { 436 writeTo((DataOutput )out); 437 } else { 438 writeTo((DataOutput )new DataOutputStream (out)); 439 } 440 } 441 442 448 public void writeTo(DataOutput out) throws IOException { 449 DateTimeZone zone = toDateTimeZone(""); 451 452 if (zone instanceof FixedDateTimeZone) { 453 out.writeByte('F'); 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'); zone = ((CachedDateTimeZone)zone).getUncachedZone(); 461 } else { 462 out.writeByte('P'); } 464 ((PrecalculatedZone)zone).writeTo(out); 465 } 466 } 467 468 471 private static final class OfYear { 472 static OfYear readFrom(DataInput in) throws IOException { 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 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 ("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 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 return millis - offset; 533 } 534 535 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 instant += offset; 550 551 Chronology chrono = ISOChronology.getInstanceUTC(); 552 long next = chrono.monthOfYear().set(instant, iMonthOfYear); 553 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 return next - offset; 575 } 576 577 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 instant += offset; 592 593 Chronology chrono = ISOChronology.getInstanceUTC(); 594 long prev = chrono.monthOfYear().set(instant, iMonthOfYear); 595 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 return prev - offset; 617 } 618 619 public boolean equals(Object 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 648 649 public void writeTo(DataOutput out) throws IOException { 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 661 private long setDayOfMonthNext(Chronology chrono, long next) { 662 try { 663 next = setDayOfMonth(chrono, next); 664 } catch (IllegalArgumentException 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 680 private long setDayOfMonthPrevious(Chronology chrono, long prev) { 681 try { 682 prev = setDayOfMonth(chrono, prev); 683 } catch (IllegalArgumentException 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 729 private static final class Recurrence { 730 static Recurrence readFrom(DataInput in) throws IOException { 731 return new Recurrence(OfYear.readFrom(in), in.readUTF(), (int)readMillis(in)); 732 } 733 734 final OfYear iOfYear; 735 final String iNameKey; 736 final int iSaveMillis; 737 738 Recurrence(OfYear ofYear, String nameKey, int saveMillis) { 739 iOfYear = ofYear; 740 iNameKey = nameKey; 741 iSaveMillis = saveMillis; 742 } 743 744 public OfYear getOfYear() { 745 return iOfYear; 746 } 747 748 751 public long next(long instant, int standardOffset, int saveMillis) { 752 return iOfYear.next(instant, standardOffset, saveMillis); 753 } 754 755 758 public long previous(long instant, int standardOffset, int saveMillis) { 759 return iOfYear.previous(instant, standardOffset, saveMillis); 760 } 761 762 public String getNameKey() { 763 return iNameKey; 764 } 765 766 public int getSaveMillis() { 767 return iSaveMillis; 768 } 769 770 public boolean equals(Object 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 out) throws IOException { 785 iOfYear.writeTo(out); 786 out.writeUTF(iNameKey); 787 writeMillis(out, iSaveMillis); 788 } 789 } 790 791 794 private static final class Rule { 795 final Recurrence iRecurrence; 796 final int iFromYear; final int iToYear; 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 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 testInstant = chrono.year().set(0, iFromYear) - wallOffset; 841 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 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 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 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 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 911 public boolean isTransitionFrom(Transition other) { 912 if (other == null) { 913 return true; 914 } 915 return iMillis > other.iMillis && 916 (iWallOffset != other.iWallOffset || 917 !(iNameKey.equals(other.iNameKey))); 919 } 920 } 921 922 private static final class RuleSet { 923 private static final int YEAR_LIMIT; 924 925 static { 926 long now = DateTimeUtils.currentTimeMillis(); 932 YEAR_LIMIT = ISOChronology.getInstanceUTC().year().get(now) + 100; 933 } 934 935 private int iStandardOffset; 936 private ArrayList iRules; 937 938 private String iInitialNameKey; 940 private int iInitialSaveMillis; 941 942 private int iUpperYear; 944 private OfYear iUpperOfYear; 945 946 RuleSet() { 947 iRules = new ArrayList (10); 948 iUpperYear = Integer.MAX_VALUE; 949 } 950 951 954 RuleSet(RuleSet rs) { 955 iStandardOffset = rs.iStandardOffset; 956 iRules = new ArrayList (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 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 993 public Transition firstTransition(final long firstMillis) { 994 if (iInitialNameKey != null) { 995 return new Transition(firstMillis, iInitialNameKey, 997 iStandardOffset + iInitialSaveMillis, iStandardOffset); 998 } 999 1000 ArrayList copy = new ArrayList (iRules); 1002 1003 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 Iterator 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 first = new Transition(firstMillis, next.getNameKey(), 1039 iStandardOffset, iStandardOffset); 1040 } 1041 break; 1042 } 1043 1044 first = new Transition(firstMillis, next); 1047 1048 saveMillis = next.getSaveMillis(); 1049 } 1050 1051 iRules = copy; 1052 return first; 1053 } 1054 1055 1066 public Transition nextTransition(final long instant, final int saveMillis) { 1067 Chronology chrono = ISOChronology.getInstanceUTC(); 1068 1069 Rule nextRule = null; 1071 long nextMillis = Long.MAX_VALUE; 1072 1073 Iterator 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 if (next <= nextMillis) { 1084 nextRule = rule; 1086 nextMillis = next; 1087 } 1088 } 1089 1090 if (nextRule == null) { 1091 return null; 1092 } 1093 1094 if (chrono.year().get(nextMillis) >= YEAR_LIMIT) { 1096 return null; 1097 } 1098 1099 if (iUpperYear < Integer.MAX_VALUE) { 1101 long upperMillis = 1102 iUpperOfYear.setInstant(iUpperYear, iStandardOffset, saveMillis); 1103 if (nextMillis >= upperMillis) { 1104 return null; 1106 } 1107 } 1108 1109 return new Transition(nextMillis, nextRule, iStandardOffset); 1110 } 1111 1112 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 1125 public DSTZone buildTailZone(String 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 1135 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 in, String id) throws IOException { 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 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 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 start = instant; 1196 } 1197 } catch (IllegalArgumentException e) { 1198 start = instant; 1200 } catch (ArithmeticException e) { 1201 start = instant; 1203 } 1204 1205 try { 1206 end = endRecurrence.next 1207 (instant, standardOffset, startRecurrence.getSaveMillis()); 1208 if (instant > 0 && end < 0) { 1209 end = instant; 1211 } 1212 } catch (IllegalArgumentException e) { 1213 end = instant; 1215 } catch (ArithmeticException e) { 1216 end = instant; 1218 } 1219 1220 return (start > end) ? end : start; 1221 } 1222 1223 public long previousTransition(long instant) { 1224 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 start = instant; 1240 } 1241 } catch (IllegalArgumentException e) { 1242 start = instant; 1244 } catch (ArithmeticException e) { 1245 start = instant; 1247 } 1248 1249 try { 1250 end = endRecurrence.previous 1251 (instant, standardOffset, startRecurrence.getSaveMillis()); 1252 if (instant < 0 && end > 0) { 1253 end = instant; 1255 } 1256 } catch (IllegalArgumentException e) { 1257 end = instant; 1259 } catch (ArithmeticException e) { 1260 end = instant; 1262 } 1263 1264 return ((start > end) ? start : end) - 1; 1265 } 1266 1267 public boolean equals(Object 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 out) throws IOException { 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 e) { 1299 start = instant; 1301 } catch (ArithmeticException e) { 1302 start = instant; 1304 } 1305 1306 try { 1307 end = endRecurrence.next 1308 (instant, standardOffset, startRecurrence.getSaveMillis()); 1309 } catch (IllegalArgumentException e) { 1310 end = instant; 1312 } catch (ArithmeticException e) { 1313 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 in, String id) throws IOException { 1325 int poolSize = in.readUnsignedShort(); 1327 String [] pool = new String [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 [] nameKeys = new String [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 e) { 1351 throw new IOException ("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 1366 private final long[] iTransitions; 1367 1368 private final int[] iWallOffsets; 1369 private final int[] iStandardOffsets; 1370 private final String [] iNameKeys; 1371 1372 private final DSTZone iTailZone; 1373 1374 PrecalculatedZone(String id, long[] transitions, int[] wallOffsets, 1375 int[] standardOffsets, String [] 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 1389 PrecalculatedZone(String id, ArrayList transitions, DSTZone tailZone) { 1390 super(id); 1391 1392 int size = transitions.size(); 1393 if (size == 0) { 1394 throw new IllegalArgumentException (); 1395 } 1396 1397 iTransitions = new long[size]; 1398 iWallOffsets = new int[size]; 1399 iStandardOffsets = new int[size]; 1400 iNameKeys = new String [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 (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 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 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 out) throws IOException { 1551 int size = iTransitions.length; 1552 1553 Set poolSet = new HashSet (); 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 ("String pool is too large"); 1562 } 1563 String [] pool = new String [poolSize]; 1564 Iterator it = poolSet.iterator(); 1565 for (int i=0; it.hasNext(); i++) { 1566 pool[i] = (String )it.next(); 1567 } 1568 1569 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 String 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 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 return true; 1635 } 1636 } 1637 1638 return false; 1639 } 1640 } 1641} 1642 | Popular Tags |