< prev index next >

make/jdk/src/classes/build/tools/tzdb/TzdbZoneRulesProvider.java

Print this page
rev 55814 : imported patch 8212970
   1 /*
   2  * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 
  27 package build.tools.tzdb;
  28 
  29 import java.io.IOException;
  30 import java.nio.charset.StandardCharsets;
  31 import java.nio.file.Files;
  32 import java.nio.file.Path;
  33 import java.nio.file.Paths;
  34 import java.util.ArrayList;
  35 import java.util.Collections;
  36 import java.util.List;
  37 import java.util.Map;
  38 import java.util.Map.Entry;
  39 import java.util.NavigableMap;
  40 import java.util.Objects;
  41 import java.util.Set;
  42 import java.util.TreeMap;
  43 import java.util.TreeSet;
  44 import java.util.concurrent.ConcurrentSkipListMap;
  45 import java.time.*;
  46 import java.time.Year;
  47 import java.time.chrono.IsoChronology;
  48 import java.time.temporal.TemporalAdjusters;
  49 import java.time.zone.ZoneOffsetTransition;
  50 import java.time.zone.ZoneOffsetTransitionRule;
  51 import java.time.zone.ZoneOffsetTransitionRule.TimeDefinition;
  52 import java.time.zone.ZoneRulesException;
  53 
  54 /**
  55  * Compile and build time-zone rules from IANA timezone data
  56  *
  57  * @author Xueming Shen
  58  * @author Stephen Colebourne
  59  * @author Michael Nascimento Santos
  60  *
  61  * @since   9
  62  */
  63 
  64 class TzdbZoneRulesProvider {
  65 
  66     /**
  67      * Creates an instance.
  68      *
  69      * @throws ZoneRulesException if unable to load
  70      */
  71     public TzdbZoneRulesProvider(List<Path> files) {


 255      * Class representing a month-day-time in the TZDB file.
 256      */
 257     private static abstract class MonthDayTime {
 258         /** The month of the cutover. */
 259         Month month = Month.JANUARY;
 260 
 261         /** The day-of-month of the cutover. */
 262         int dayOfMonth = 1;
 263 
 264         /** Whether to adjust forwards. */
 265         boolean adjustForwards = true;
 266 
 267         /** The day-of-week of the cutover. */
 268         DayOfWeek dayOfWeek;
 269 
 270         /** The time of the cutover, in second of day */
 271         int secsOfDay = 0;
 272 
 273         /** Whether this is midnight end of day. */
 274         boolean endOfDay;
 275         /** The time of the cutover. */
 276 

 277         TimeDefinition timeDefinition = TimeDefinition.WALL;
 278 
 279         void adjustToForwards(int year) {
 280             if (adjustForwards == false && dayOfMonth > 0) {
 281                 // weekDay<=monthDay case, don't have it in tzdb data for now
 282                 LocalDate adjustedDate = LocalDate.of(year, month, dayOfMonth).minusDays(6);
 283                 dayOfMonth = adjustedDate.getDayOfMonth();
 284                 month = adjustedDate.getMonth();
 285                 adjustForwards = true;
 286             }
 287         }
 288 
 289         LocalDateTime toDateTime(int year) {
 290             LocalDate date;
 291             if (dayOfMonth < 0) {
 292                 int monthLen = month.length(IsoChronology.INSTANCE.isLeapYear(year));
 293                 date = LocalDate.of(year, month, monthLen + 1 + dayOfMonth);
 294                 if (dayOfWeek != null) {
 295                     date = date.with(TemporalAdjusters.previousOrSame(dayOfWeek));
 296                 }


 326                         index = dayRule.indexOf("<=");
 327                         if (index > 0) {
 328                             dayOfWeek = parseDayOfWeek(dayRule.substring(0, index));
 329                             adjustForwards = false;
 330                             dayRule = dayRule.substring(index + 2);
 331                         }
 332                     }
 333                     dayOfMonth = Integer.parseInt(dayRule);
 334                     if (dayOfMonth < -28 || dayOfMonth > 31 || dayOfMonth == 0) {
 335                        throw new IllegalArgumentException(
 336                           "Day of month indicator must be between -28 and 31 inclusive excluding zero");
 337                     }
 338                 }
 339                 if (off < tokens.length) {
 340                     String timeStr = tokens[off++];
 341                     secsOfDay = parseSecs(timeStr);
 342                     if (secsOfDay == 86400) {
 343                         // time must be midnight when end of day flag is true
 344                         endOfDay = true;
 345                         secsOfDay = 0;














 346                     }
 347                     timeDefinition = parseTimeDefinition(timeStr.charAt(timeStr.length() - 1));
 348                 }
 349             }
 350         }
 351 
 352         int parseYear(String year, int defaultYear) {
 353             switch (year.toLowerCase()) {
 354             case "min":  return 1900;
 355             case "max":  return Year.MAX_VALUE;
 356             case "only": return defaultYear;
 357             }
 358             return Integer.parseInt(year);
 359         }
 360 
 361         Month parseMonth(String mon) {
 362             switch (mon) {
 363             case "Jan": return Month.JANUARY;
 364             case "Feb": return Month.FEBRUARY;
 365             case "Mar": return Month.MARCH;


 480      * Class representing a rule line in the TZDB file.
 481      */
 482     private static class RuleLine extends MonthDayTime {
 483         /** The start year. */
 484         int startYear;
 485 
 486         /** The end year. */
 487         int endYear;
 488 
 489         /** The amount of savings, in seconds. */
 490         int savingsAmount;
 491 
 492         /** The text name of the zone. */
 493         String text;
 494 
 495         /**
 496          * Converts this to a transition rule.
 497          *
 498          * @param standardOffset  the active standard offset, not null
 499          * @param savingsBeforeSecs  the active savings before the transition in seconds


 500          * @return the transition, not null
 501         */
 502         ZoneOffsetTransitionRule toTransitionRule(ZoneOffset stdOffset, int savingsBefore) {
 503             // rule shared by different zones, so don't change it
 504             Month month = this.month;
 505             int dayOfMonth = this.dayOfMonth;
 506             DayOfWeek dayOfWeek = this.dayOfWeek;
 507             boolean endOfDay = this.endOfDay;
 508 
 509             // optimize stored format
 510             if (dayOfMonth < 0) {
 511                 if (month != Month.FEBRUARY) {    // not Month.FEBRUARY
 512                     dayOfMonth = month.maxLength() - 6;
 513                 }
 514             }
 515             if (endOfDay && dayOfMonth > 0 &&
 516                 (dayOfMonth == 28 && month == Month.FEBRUARY) == false) {
 517                 LocalDate date = LocalDate.of(2004, month, dayOfMonth).plusDays(1);  // leap-year
 518                 month = date.getMonth();
 519                 dayOfMonth = date.getDayOfMonth();
 520                 if (dayOfWeek != null) {
 521                     dayOfWeek = dayOfWeek.plus(1);
 522                 }
 523                 endOfDay = false;
 524             }

 525             // build rule
 526             return ZoneOffsetTransitionRule.of(
 527                     //month, dayOfMonth, dayOfWeek, time, endOfDay, timeDefinition,
 528                     month, dayOfMonth, dayOfWeek,
 529                     LocalTime.ofSecondOfDay(secsOfDay), endOfDay, timeDefinition,
 530                     stdOffset,
 531                     ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savingsBefore),
 532                     ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savingsAmount));
 533         }
 534 
 535         RuleLine parse(String[] tokens) {
 536             startYear = parseYear(tokens[2], 0);
 537             endYear = parseYear(tokens[3], startYear);
 538             if (startYear > endYear) {
 539                 throw new IllegalArgumentException(
 540                     "Invalid <Rule> line/Year order invalid:" + startYear + " > " + endYear);
 541             }
 542             //parseOptional(s.next());  // type is unused
 543             super.parse(tokens, 5);     // monthdaytime parsing
 544             savingsAmount = parsePeriod(tokens[8]);
 545             //rule.text = parseOptional(s.next());
 546             return this;
 547         }
 548     }
 549 
 550     /**
 551      * Class representing a linked set of zone lines in the TZDB file.
 552      */


 626      * Class representing a rule line in the TZDB file for a particular year.
 627      */
 628     private static class TransRule implements Comparable<TransRule>
 629     {
 630         private int year;
 631         private RuleLine rule;
 632 
 633         /** The trans date/time */
 634         private LocalDateTime ldt;
 635 
 636         /** The trans date/time in epoch seconds (assume UTC) */
 637         long ldtSecs;
 638 
 639         TransRule(int year, RuleLine rule) {
 640             this.year = year;
 641             this.rule = rule;
 642             this.ldt = rule.toDateTime(year);
 643             this.ldtSecs = ldt.toEpochSecond(ZoneOffset.UTC);
 644         }
 645 
 646         ZoneOffsetTransition toTransition(ZoneOffset standardOffset, int savingsBeforeSecs) {
 647             // copy of code in ZoneOffsetTransitionRule to avoid infinite loop
 648             ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(
 649                 standardOffset.getTotalSeconds() + savingsBeforeSecs);
 650             ZoneOffset offsetAfter = ZoneOffset.ofTotalSeconds(
 651                 standardOffset.getTotalSeconds() + rule.savingsAmount);
 652             LocalDateTime dt = rule.timeDefinition
 653                                    .createDateTime(ldt, standardOffset, wallOffset);
 654             return ZoneOffsetTransition.of(dt, wallOffset, offsetAfter);
 655         }
 656 
 657         long toEpochSecond(ZoneOffset stdOffset, int savingsBeforeSecs) {
 658             switch(rule.timeDefinition) {
 659             case UTC:      return ldtSecs;
 660             case STANDARD: return ldtSecs - stdOffset.getTotalSeconds();
 661             default:       return ldtSecs - (stdOffset.getTotalSeconds() + savingsBeforeSecs); // WALL
 662             }
 663         }
 664 
 665         /**
 666          * Tests if this a real transition with the active savings in seconds
 667          *
 668          * @param savingsBefore the active savings in seconds


 669          * @return true, if savings changes
 670          */
 671         boolean isTransition(int savingsBefore) {
 672             return rule.savingsAmount != savingsBefore;
 673         }
 674 
 675         public int compareTo(TransRule other) {
 676             return (ldtSecs < other.ldtSecs)? -1 : ((ldtSecs == other.ldtSecs) ? 0 : 1);
 677         }
 678     }
 679 
 680     private ZoneRules buildRules(String zoneId, List<ZoneLine> zones) {
 681         if (zones.isEmpty()) {
 682             throw new IllegalStateException("No available zone window");
 683         }
 684         final List<ZoneOffsetTransition> standardTransitionList = new ArrayList<>(4);
 685         final List<ZoneOffsetTransition> transitionList = new ArrayList<>(256);
 686         final List<ZoneOffsetTransitionRule> lastTransitionRuleList = new ArrayList<>(2);
 687 
 688         final ZoneLine zone0 = zones.get(0);
 689         // initialize the standard offset, wallOffset and savings for loop
 690 
 691         //ZoneOffset stdOffset = zone0.standardOffset;
 692         ZoneOffset stdOffset = ZoneOffset.ofTotalSeconds(zone0.stdOffsetSecs);
 693 
 694         int savings = zone0.fixedSavingsSecs;
 695         ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savings);
 696 
 697         // start ldt of each zone window
 698         LocalDateTime zoneStart = LocalDateTime.MIN;
 699 
 700         // first stanard offset
 701         ZoneOffset firstStdOffset = stdOffset;
 702         // first wall offset
 703         ZoneOffset firstWallOffset = wallOffset;
 704 
 705         for (ZoneLine zone : zones) {










 706             // check if standard offset changed, update it if yes
 707             ZoneOffset stdOffsetPrev = stdOffset;  // for effectiveSavings check
 708             if (zone.stdOffsetSecs != stdOffset.getTotalSeconds()) {
 709                 ZoneOffset stdOffsetNew = ZoneOffset.ofTotalSeconds(zone.stdOffsetSecs);
 710                 standardTransitionList.add(
 711                     ZoneOffsetTransition.of(
 712                         LocalDateTime.ofEpochSecond(zoneStart.toEpochSecond(wallOffset),
 713                                                     0,
 714                                                     stdOffset),
 715                         stdOffset,
 716                         stdOffsetNew));
 717                 stdOffset = stdOffsetNew;
 718             }
 719 
 720             LocalDateTime zoneEnd;
 721             if (zone.year == Year.MAX_VALUE) {
 722                 zoneEnd = LocalDateTime.MAX;
 723             } else {
 724                 zoneEnd = zone.toDateTime();
 725             }


 774                 // last rules, fill the gap years between different last rules
 775                 if (zoneEnd.equals(LocalDateTime.MAX)) {
 776                     lastRulesStartYear = Math.max(lastRulesStartYear, zoneStart.getYear()) + 1;
 777                     for (TransRule rule : lastRules) {
 778                         if (rule.year <= lastRulesStartYear) {
 779                             int year = rule.year;
 780                             while (year <= lastRulesStartYear) {
 781                                 trules.add(new TransRule(year, rule.rule));
 782                                 year++;
 783                             }
 784                             rule.year = lastRulesStartYear;
 785                             rule.ldt = rule.rule.toDateTime(year);
 786                             rule.ldtSecs = rule.ldt.toEpochSecond(ZoneOffset.UTC);
 787                         }
 788                     }
 789                     Collections.sort(lastRules);
 790                 }
 791                 // sort the merged rules
 792                 Collections.sort(trules);
 793 
 794                 effectiveSavings = 0;
 795                 for (TransRule rule : trules) {
 796                     if (rule.toEpochSecond(stdOffsetPrev, savings) >
 797                         zoneStart.toEpochSecond(wallOffset)) {
 798                         // previous savings amount found, which could be the
 799                         // savings amount at the instant that the window starts
 800                         // (hence isAfter)
 801                         break;
 802                     }
 803                     effectiveSavings = rule.rule.savingsAmount;
 804                 }
 805             }
 806             // check if the start of the window represents a transition
 807             ZoneOffset effectiveWallOffset =
 808                 ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + effectiveSavings);
 809 
 810             if (!wallOffset.equals(effectiveWallOffset)) {
 811                 transitionList.add(ZoneOffsetTransition.of(zoneStart,
 812                                                            wallOffset,
 813                                                            effectiveWallOffset));
 814             }
 815             savings = effectiveSavings;
 816             // apply rules within the window
 817             if (trules != null) {
 818                 long zoneStartEpochSecs = zoneStart.toEpochSecond(wallOffset);
 819                 for (TransRule trule : trules) {
 820                     if (trule.isTransition(savings)) {
 821                         long epochSecs = trule.toEpochSecond(stdOffset, savings);
 822                         if (epochSecs < zoneStartEpochSecs ||
 823                             epochSecs >= zone.toDateTimeEpochSecond(savings)) {
 824                             continue;
 825                         }
 826                         transitionList.add(trule.toTransition(stdOffset, savings));
 827                         savings = trule.rule.savingsAmount;
 828                     }
 829                 }
 830             }
 831             if (lastRules != null) {
 832                 for (TransRule trule : lastRules) {
 833                     lastTransitionRuleList.add(trule.rule.toTransitionRule(stdOffset, savings));
 834                     savings = trule.rule.savingsAmount;
 835                 }
 836             }
 837 
 838             // finally we can calculate the true end of the window, passing it to the next window
 839             wallOffset = ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savings);
 840             zoneStart = LocalDateTime.ofEpochSecond(zone.toDateTimeEpochSecond(savings),
 841                                                     0,
 842                                                     wallOffset);
 843         }
 844         return new ZoneRules(firstStdOffset,
 845                              firstWallOffset,
 846                              standardTransitionList,
 847                              transitionList,
 848                              lastTransitionRuleList);
 849     }
 850 


































 851 }
   1 /*
   2  * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 
  27 package build.tools.tzdb;
  28 
  29 import java.io.IOException;
  30 import java.nio.charset.StandardCharsets;
  31 import java.nio.file.Files;
  32 import java.nio.file.Path;
  33 import java.nio.file.Paths;
  34 import java.util.*;



  35 import java.util.Map.Entry;





  36 import java.util.concurrent.ConcurrentSkipListMap;
  37 import java.time.*;
  38 import java.time.Year;
  39 import java.time.chrono.IsoChronology;
  40 import java.time.temporal.TemporalAdjusters;
  41 import build.tools.tzdb.ZoneOffsetTransitionRule.TimeDefinition;


  42 import java.time.zone.ZoneRulesException;
  43 
  44 /**
  45  * Compile and build time-zone rules from IANA timezone data
  46  *
  47  * @author Xueming Shen
  48  * @author Stephen Colebourne
  49  * @author Michael Nascimento Santos
  50  *
  51  * @since   9
  52  */
  53 
  54 class TzdbZoneRulesProvider {
  55 
  56     /**
  57      * Creates an instance.
  58      *
  59      * @throws ZoneRulesException if unable to load
  60      */
  61     public TzdbZoneRulesProvider(List<Path> files) {


 245      * Class representing a month-day-time in the TZDB file.
 246      */
 247     private static abstract class MonthDayTime {
 248         /** The month of the cutover. */
 249         Month month = Month.JANUARY;
 250 
 251         /** The day-of-month of the cutover. */
 252         int dayOfMonth = 1;
 253 
 254         /** Whether to adjust forwards. */
 255         boolean adjustForwards = true;
 256 
 257         /** The day-of-week of the cutover. */
 258         DayOfWeek dayOfWeek;
 259 
 260         /** The time of the cutover, in second of day */
 261         int secsOfDay = 0;
 262 
 263         /** Whether this is midnight end of day. */
 264         boolean endOfDay;

 265 
 266         /** The time definition of the cutover. */
 267         TimeDefinition timeDefinition = TimeDefinition.WALL;
 268 
 269         void adjustToForwards(int year) {
 270             if (adjustForwards == false && dayOfMonth > 0) {
 271                 // weekDay<=monthDay case, don't have it in tzdb data for now
 272                 LocalDate adjustedDate = LocalDate.of(year, month, dayOfMonth).minusDays(6);
 273                 dayOfMonth = adjustedDate.getDayOfMonth();
 274                 month = adjustedDate.getMonth();
 275                 adjustForwards = true;
 276             }
 277         }
 278 
 279         LocalDateTime toDateTime(int year) {
 280             LocalDate date;
 281             if (dayOfMonth < 0) {
 282                 int monthLen = month.length(IsoChronology.INSTANCE.isLeapYear(year));
 283                 date = LocalDate.of(year, month, monthLen + 1 + dayOfMonth);
 284                 if (dayOfWeek != null) {
 285                     date = date.with(TemporalAdjusters.previousOrSame(dayOfWeek));
 286                 }


 316                         index = dayRule.indexOf("<=");
 317                         if (index > 0) {
 318                             dayOfWeek = parseDayOfWeek(dayRule.substring(0, index));
 319                             adjustForwards = false;
 320                             dayRule = dayRule.substring(index + 2);
 321                         }
 322                     }
 323                     dayOfMonth = Integer.parseInt(dayRule);
 324                     if (dayOfMonth < -28 || dayOfMonth > 31 || dayOfMonth == 0) {
 325                        throw new IllegalArgumentException(
 326                           "Day of month indicator must be between -28 and 31 inclusive excluding zero");
 327                     }
 328                 }
 329                 if (off < tokens.length) {
 330                     String timeStr = tokens[off++];
 331                     secsOfDay = parseSecs(timeStr);
 332                     if (secsOfDay == 86400) {
 333                         // time must be midnight when end of day flag is true
 334                         endOfDay = true;
 335                         secsOfDay = 0;
 336                     } else if (secsOfDay < 0 || secsOfDay > 86400) {
 337                         // beyond 0:00-24:00 range. Adjust the cutover date.
 338                         int beyondDays = secsOfDay / 86400;
 339                         secsOfDay %= 86400;
 340                         if (secsOfDay < 0) {
 341                             secsOfDay = 86400 + secsOfDay;
 342                             beyondDays -= 1;
 343                         }
 344                         LocalDate date = LocalDate.of(2004, month, dayOfMonth).plusDays(beyondDays);  // leap-year
 345                         month = date.getMonth();
 346                         dayOfMonth = date.getDayOfMonth();
 347                         if (dayOfWeek != null) {
 348                             dayOfWeek = dayOfWeek.plus(beyondDays);
 349                         }
 350                     }
 351                     timeDefinition = parseTimeDefinition(timeStr.charAt(timeStr.length() - 1));
 352                 }
 353             }
 354         }
 355 
 356         int parseYear(String year, int defaultYear) {
 357             switch (year.toLowerCase()) {
 358             case "min":  return 1900;
 359             case "max":  return Year.MAX_VALUE;
 360             case "only": return defaultYear;
 361             }
 362             return Integer.parseInt(year);
 363         }
 364 
 365         Month parseMonth(String mon) {
 366             switch (mon) {
 367             case "Jan": return Month.JANUARY;
 368             case "Feb": return Month.FEBRUARY;
 369             case "Mar": return Month.MARCH;


 484      * Class representing a rule line in the TZDB file.
 485      */
 486     private static class RuleLine extends MonthDayTime {
 487         /** The start year. */
 488         int startYear;
 489 
 490         /** The end year. */
 491         int endYear;
 492 
 493         /** The amount of savings, in seconds. */
 494         int savingsAmount;
 495 
 496         /** The text name of the zone. */
 497         String text;
 498 
 499         /**
 500          * Converts this to a transition rule.
 501          *
 502          * @param standardOffset  the active standard offset, not null
 503          * @param savingsBeforeSecs  the active savings before the transition in seconds
 504          * @param negativeSavings minimum savings in the rule, usually zero, but negative if negative DST is
 505          *                   in effect.
 506          * @return the transition, not null
 507         */
 508         ZoneOffsetTransitionRule toTransitionRule(ZoneOffset stdOffset, int savingsBefore, int negativeSavings) {
 509             // rule shared by different zones, so don't change it
 510             Month month = this.month;
 511             int dayOfMonth = this.dayOfMonth;
 512             DayOfWeek dayOfWeek = this.dayOfWeek;
 513             boolean endOfDay = this.endOfDay;
 514 
 515             // optimize stored format
 516             if (dayOfMonth < 0) {
 517                 if (month != Month.FEBRUARY) {    // not Month.FEBRUARY
 518                     dayOfMonth = month.maxLength() - 6;
 519                 }
 520             }
 521             if (endOfDay && dayOfMonth > 0 &&
 522                 (dayOfMonth == 28 && month == Month.FEBRUARY) == false) {
 523                 LocalDate date = LocalDate.of(2004, month, dayOfMonth).plusDays(1);  // leap-year
 524                 month = date.getMonth();
 525                 dayOfMonth = date.getDayOfMonth();
 526                 if (dayOfWeek != null) {
 527                     dayOfWeek = dayOfWeek.plus(1);
 528                 }
 529                 endOfDay = false;
 530             }
 531 
 532             // build rule
 533             return ZoneOffsetTransitionRule.of(
 534                     //month, dayOfMonth, dayOfWeek, time, endOfDay, timeDefinition,
 535                     month, dayOfMonth, dayOfWeek,
 536                     LocalTime.ofSecondOfDay(secsOfDay), endOfDay, timeDefinition,
 537                     stdOffset,
 538                     ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savingsBefore),
 539                     ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savingsAmount - negativeSavings));
 540         }
 541 
 542         RuleLine parse(String[] tokens) {
 543             startYear = parseYear(tokens[2], 0);
 544             endYear = parseYear(tokens[3], startYear);
 545             if (startYear > endYear) {
 546                 throw new IllegalArgumentException(
 547                     "Invalid <Rule> line/Year order invalid:" + startYear + " > " + endYear);
 548             }
 549             //parseOptional(s.next());  // type is unused
 550             super.parse(tokens, 5);     // monthdaytime parsing
 551             savingsAmount = parsePeriod(tokens[8]);
 552             //rule.text = parseOptional(s.next());
 553             return this;
 554         }
 555     }
 556 
 557     /**
 558      * Class representing a linked set of zone lines in the TZDB file.
 559      */


 633      * Class representing a rule line in the TZDB file for a particular year.
 634      */
 635     private static class TransRule implements Comparable<TransRule>
 636     {
 637         private int year;
 638         private RuleLine rule;
 639 
 640         /** The trans date/time */
 641         private LocalDateTime ldt;
 642 
 643         /** The trans date/time in epoch seconds (assume UTC) */
 644         long ldtSecs;
 645 
 646         TransRule(int year, RuleLine rule) {
 647             this.year = year;
 648             this.rule = rule;
 649             this.ldt = rule.toDateTime(year);
 650             this.ldtSecs = ldt.toEpochSecond(ZoneOffset.UTC);
 651         }
 652 
 653         ZoneOffsetTransition toTransition(ZoneOffset standardOffset, int savingsBeforeSecs, int negativeSavings) {
 654             // copy of code in ZoneOffsetTransitionRule to avoid infinite loop
 655             ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(
 656                 standardOffset.getTotalSeconds() + savingsBeforeSecs);
 657             ZoneOffset offsetAfter = ZoneOffset.ofTotalSeconds(
 658                 standardOffset.getTotalSeconds() + rule.savingsAmount - negativeSavings);
 659             LocalDateTime dt = rule.timeDefinition
 660                                    .createDateTime(ldt, standardOffset, wallOffset);
 661             return ZoneOffsetTransition.of(dt, wallOffset, offsetAfter);
 662         }
 663 
 664         long toEpochSecond(ZoneOffset stdOffset, int savingsBeforeSecs) {
 665             switch(rule.timeDefinition) {
 666             case UTC:      return ldtSecs;
 667             case STANDARD: return ldtSecs - stdOffset.getTotalSeconds();
 668             default:       return ldtSecs - (stdOffset.getTotalSeconds() + savingsBeforeSecs); // WALL
 669             }
 670         }
 671 
 672         /**
 673          * Tests if this a real transition with the active savings in seconds
 674          *
 675          * @param savingsBefore the active savings in seconds
 676          * @param negativeSavings minimum savings in the rule, usually zero, but negative if negative DST is
 677          *                   in effect.
 678          * @return true, if savings changes
 679          */
 680         boolean isTransition(int savingsBefore, int negativeSavings) {
 681             return rule.savingsAmount - negativeSavings != savingsBefore;
 682         }
 683 
 684         public int compareTo(TransRule other) {
 685             return (ldtSecs < other.ldtSecs)? -1 : ((ldtSecs == other.ldtSecs) ? 0 : 1);
 686         }
 687     }
 688 
 689     private ZoneRules buildRules(String zoneId, List<ZoneLine> zones) {
 690         if (zones.isEmpty()) {
 691             throw new IllegalStateException("No available zone window");
 692         }
 693         final List<ZoneOffsetTransition> standardTransitionList = new ArrayList<>(4);
 694         final List<ZoneOffsetTransition> transitionList = new ArrayList<>(256);
 695         final List<ZoneOffsetTransitionRule> lastTransitionRuleList = new ArrayList<>(2);
 696 
 697         final ZoneLine zone0 = zones.get(0);
 698         // initialize the standard offset, wallOffset and savings for loop
 699 
 700         //ZoneOffset stdOffset = zone0.standardOffset;
 701         ZoneOffset stdOffset = ZoneOffset.ofTotalSeconds(zone0.stdOffsetSecs);
 702 
 703         int savings = zone0.fixedSavingsSecs;
 704         ZoneOffset wallOffset = ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savings);
 705 
 706         // start ldt of each zone window
 707         LocalDateTime zoneStart = LocalDateTime.MIN;
 708 
 709         // first standard offset
 710         ZoneOffset firstStdOffset = stdOffset;
 711         // first wall offset
 712         ZoneOffset firstWallOffset = wallOffset;
 713 
 714         for (ZoneLine zone : zones) {
 715             // Adjust stdOffset, if negative DST is observed. It should be either
 716             // fixed amount, or expressed in the named Rules.
 717             int negativeSavings = Math.min(zone.fixedSavingsSecs, findNegativeSavings(zoneStart, zone));
 718             if (negativeSavings < 0) {
 719                 zone.stdOffsetSecs += negativeSavings;
 720                 if (zone.fixedSavingsSecs < 0) {
 721                     zone.fixedSavingsSecs = 0;
 722                 }
 723             }
 724 
 725             // check if standard offset changed, update it if yes
 726             ZoneOffset stdOffsetPrev = stdOffset;  // for effectiveSavings check
 727             if (zone.stdOffsetSecs != stdOffset.getTotalSeconds()) {
 728                 ZoneOffset stdOffsetNew = ZoneOffset.ofTotalSeconds(zone.stdOffsetSecs);
 729                 standardTransitionList.add(
 730                     ZoneOffsetTransition.of(
 731                         LocalDateTime.ofEpochSecond(zoneStart.toEpochSecond(wallOffset),
 732                                                     0,
 733                                                     stdOffset),
 734                         stdOffset,
 735                         stdOffsetNew));
 736                 stdOffset = stdOffsetNew;
 737             }
 738 
 739             LocalDateTime zoneEnd;
 740             if (zone.year == Year.MAX_VALUE) {
 741                 zoneEnd = LocalDateTime.MAX;
 742             } else {
 743                 zoneEnd = zone.toDateTime();
 744             }


 793                 // last rules, fill the gap years between different last rules
 794                 if (zoneEnd.equals(LocalDateTime.MAX)) {
 795                     lastRulesStartYear = Math.max(lastRulesStartYear, zoneStart.getYear()) + 1;
 796                     for (TransRule rule : lastRules) {
 797                         if (rule.year <= lastRulesStartYear) {
 798                             int year = rule.year;
 799                             while (year <= lastRulesStartYear) {
 800                                 trules.add(new TransRule(year, rule.rule));
 801                                 year++;
 802                             }
 803                             rule.year = lastRulesStartYear;
 804                             rule.ldt = rule.rule.toDateTime(year);
 805                             rule.ldtSecs = rule.ldt.toEpochSecond(ZoneOffset.UTC);
 806                         }
 807                     }
 808                     Collections.sort(lastRules);
 809                 }
 810                 // sort the merged rules
 811                 Collections.sort(trules);
 812 
 813                 effectiveSavings = -negativeSavings;
 814                 for (TransRule rule : trules) {
 815                     if (rule.toEpochSecond(stdOffsetPrev, savings) >
 816                         zoneStart.toEpochSecond(wallOffset)) {
 817                         // previous savings amount found, which could be the
 818                         // savings amount at the instant that the window starts
 819                         // (hence isAfter)
 820                         break;
 821                     }
 822                     effectiveSavings = rule.rule.savingsAmount - negativeSavings;
 823                 }
 824             }
 825             // check if the start of the window represents a transition
 826             ZoneOffset effectiveWallOffset =
 827                 ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + effectiveSavings);
 828 
 829             if (!wallOffset.equals(effectiveWallOffset)) {
 830                 transitionList.add(ZoneOffsetTransition.of(zoneStart,
 831                                                            wallOffset,
 832                                                            effectiveWallOffset));
 833             }
 834             savings = effectiveSavings;
 835             // apply rules within the window
 836             if (trules != null) {
 837                 long zoneStartEpochSecs = zoneStart.toEpochSecond(wallOffset);
 838                 for (TransRule trule : trules) {
 839                     if (trule.isTransition(savings, negativeSavings)) {
 840                         long epochSecs = trule.toEpochSecond(stdOffset, savings);
 841                         if (epochSecs < zoneStartEpochSecs ||
 842                             epochSecs >= zone.toDateTimeEpochSecond(savings)) {
 843                             continue;
 844                         }
 845                         transitionList.add(trule.toTransition(stdOffset, savings, negativeSavings));
 846                         savings = trule.rule.savingsAmount - negativeSavings;
 847                     }
 848                 }
 849             }
 850             if (lastRules != null) {
 851                 for (TransRule trule : lastRules) {
 852                     lastTransitionRuleList.add(trule.rule.toTransitionRule(stdOffset, savings, negativeSavings));
 853                     savings = trule.rule.savingsAmount - negativeSavings;
 854                 }
 855             }
 856 
 857             // finally we can calculate the true end of the window, passing it to the next window
 858             wallOffset = ZoneOffset.ofTotalSeconds(stdOffset.getTotalSeconds() + savings);
 859             zoneStart = LocalDateTime.ofEpochSecond(zone.toDateTimeEpochSecond(savings),
 860                                                     0,
 861                                                     wallOffset);
 862         }
 863         return new ZoneRules(firstStdOffset,
 864                              firstWallOffset,
 865                              standardTransitionList,
 866                              transitionList,
 867                              lastTransitionRuleList);
 868     }
 869 
 870     /**
 871      * Find the minimum negative savings in named Rules for a Zone. Savings are only
 872      * looked at for the period of the subject Zone.
 873      *
 874      * @param zoneStart start LDT of the zone
 875      * @param zl ZoneLine to look at
 876      */
 877     private int findNegativeSavings(LocalDateTime zoneStart, ZoneLine zl) {
 878         int negativeSavings = 0;
 879         LocalDateTime zoneEnd = zl.toDateTime();
 880 
 881         if (zl.savingsRule != null) {
 882             List<RuleLine> rlines = rules.get(zl.savingsRule);
 883             if (rlines == null) {
 884                 throw new IllegalArgumentException("<Rule> not found: " +
 885                         zl.savingsRule);
 886             }
 887 
 888             negativeSavings = Math.min(0, rlines.stream()
 889                     .filter(l -> windowOverlap(l, zoneStart.getYear(), zoneEnd.getYear()))
 890                     .map(l -> l.savingsAmount)
 891                     .min(Comparator.naturalOrder())
 892                     .orElse(0));
 893         }
 894 
 895         return negativeSavings;
 896     }
 897 
 898     private boolean windowOverlap(RuleLine ruleLine, int zoneStartYear, int zoneEndYear) {
 899         boolean overlap = zoneStartYear <= ruleLine.startYear && zoneEndYear >= ruleLine.startYear ||
 900                           zoneStartYear <= ruleLine.endYear && zoneEndYear >= ruleLine.endYear;
 901 
 902         return overlap;
 903     }
 904 }
< prev index next >