1 /*
   2  * Copyright (c) 2000, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.BufferedReader;
  25 import java.io.FileReader;
  26 import java.io.FileNotFoundException;
  27 import java.io.IOException;
  28 import java.util.HashMap;
  29 import java.util.List;
  30 import java.util.Map;
  31 import java.util.StringTokenizer;
  32 
  33 /**
  34  * Zoneinfo provides javazic compiler front-end functionality.
  35  * @since 1.4
  36  */
  37 class Zoneinfo {
  38 
  39     private static final int minYear = 1900;
  40     private static final int maxYear = 2037;
  41     private static final long minTime = Time.getLocalTime(minYear, Month.JANUARY, 1, 0);
  42     private static int startYear = minYear;
  43     private static int endYear = maxYear;
  44 
  45     /**
  46      * True if javazic should generate a list of SimpleTimeZone
  47      * instances for the SimpleTimeZone-based time zone support.
  48      */
  49     static boolean isYearForTimeZoneDataSpecified = false;
  50 
  51     /**
  52      * Zone name to Zone mappings
  53      */
  54     private Map<String,Zone> zones;
  55 
  56     /**
  57      * Rule name to Rule mappings
  58      */
  59     private Map<String,Rule> rules;
  60 
  61     /**
  62      * Alias name to real name mappings
  63      */
  64     private Map<String,String> aliases;
  65 
  66     /**
  67      * Constracts a Zoneinfo.
  68      */
  69     Zoneinfo() {
  70         zones = new HashMap<String,Zone>();
  71         rules = new HashMap<String,Rule>();
  72         aliases = new HashMap<String,String>();
  73     }
  74 
  75     /**
  76      * Adds the given zone to the list of Zones.
  77      * @param zone Zone to be added to the list.
  78      */
  79     void add(Zone zone) {
  80         String name = zone.getName();
  81         zones.put(name, zone);
  82     }
  83 
  84     /**
  85      * Adds the given rule to the list of Rules.
  86      * @param rule Rule to be added to the list.
  87      */
  88     void add(Rule rule) {
  89         String name = rule.getName();
  90         rules.put(name, rule);
  91     }
  92 
  93     /**
  94      * Puts the specifid name pair to the alias table.
  95      * @param name1 an alias time zone name
  96      * @param name2 the real time zone of the alias name
  97      */
  98     void putAlias(String name1, String name2) {
  99         aliases.put(name1, name2);
 100     }
 101 
 102     /**
 103      * Sets the given year for SimpleTimeZone list output.
 104      * This method is called when the -S option is specified.
 105      * @param year the year for which SimpleTimeZone list should be generated
 106      */
 107     static void setYear(int year) {
 108         setStartYear(year);
 109         setEndYear(year);
 110         isYearForTimeZoneDataSpecified = true;
 111     }
 112 
 113     /**
 114      * Sets the start year.
 115      * @param year the start year value
 116      * @throws IllegalArgumentException if the specified year value is
 117      * smaller than the minimum year or greater than the end year.
 118      */
 119     static void setStartYear(int year) {
 120         if (year < minYear || year > endYear) {
 121             throw new IllegalArgumentException("invalid start year specified: " + year);
 122         }
 123         startYear = year;
 124     }
 125 
 126     /**
 127      * @return the start year value
 128      */
 129     static int getStartYear() {
 130         return startYear;
 131     }
 132 
 133     /**
 134      * Sets the end year.
 135      * @param year the end year value
 136      * @throws IllegalArgumentException if the specified year value is
 137      * smaller than the start year or greater than the maximum year.
 138      */
 139     static void setEndYear(int year) {
 140         if (year < startYear || year > maxYear) {
 141             throw new IllegalArgumentException();
 142         }
 143         endYear = year;
 144     }
 145 
 146     /**
 147      * @return the end year value
 148      */
 149     static int getEndYear() {
 150         return endYear;
 151     }
 152 
 153     /**
 154      * @return the minimum year value
 155      */
 156     static int getMinYear() {
 157         return minYear;
 158     }
 159 
 160     /**
 161      * @return the maximum year value
 162      */
 163     static int getMaxYear() {
 164         return maxYear;
 165     }
 166 
 167     /**
 168      * @return the alias table
 169      */
 170     Map<String,String> getAliases() {
 171         return aliases;
 172     }
 173 
 174     /**
 175      * @return the Zone list
 176      */
 177     Map<String,Zone> getZones() {
 178         return zones;
 179     }
 180 
 181     /**
 182      * @return a Zone specified by name.
 183      * @param name a zone name
 184      */
 185     Zone getZone(String name) {
 186         return zones.get(name);
 187     }
 188 
 189     /**
 190      * @return a Rule specified by name.
 191      * @param name a rule name
 192      */
 193     Rule getRule(String name) {
 194         return rules.get(name);
 195     }
 196 
 197     private static String line;
 198 
 199     private static int lineNum;
 200 
 201     /**
 202      * Parses the specified time zone data file and creates a Zoneinfo
 203      * that has all Rules, Zones and Links (aliases) information.
 204      * @param fname the time zone data file name
 205      * @return a Zoneinfo object
 206      */
 207     static Zoneinfo parse(String fname) {
 208         BufferedReader in = null;
 209         try {
 210             FileReader fr = new FileReader(fname);
 211             in = new BufferedReader(fr);
 212         } catch (FileNotFoundException e) {
 213             panic("can't open file: "+fname);
 214         }
 215         Zoneinfo zi = new Zoneinfo();
 216         boolean continued = false;
 217         Zone zone = null;
 218         String l;
 219         lineNum = 0;
 220 
 221         try {
 222             while ((line = in.readLine()) != null) {
 223                 lineNum++;
 224                 // skip blank and comment lines
 225                 if (line.length() == 0 || line.charAt(0) == '#') {
 226                     continue;
 227                 }
 228 
 229                 // trim trailing comments
 230                 int rindex = line.lastIndexOf('#');
 231                 if (rindex != -1) {
 232                     // take the data part of the line
 233                     l = line.substring(0, rindex);
 234                 } else {
 235                     l = line;
 236                 }
 237 
 238                 StringTokenizer tokens = new StringTokenizer(l);
 239                 if (!tokens.hasMoreTokens()) {
 240                     continue;
 241                 }
 242                 String token = tokens.nextToken();
 243 
 244                 if (continued || "Zone".equals(token)) {
 245                     if (zone == null) {
 246                         if (!tokens.hasMoreTokens()) {
 247                             panic("syntax error: zone no more token");
 248                         }
 249                         token = tokens.nextToken();
 250                         // if the zone name is in "GMT+hh" or "GMT-hh"
 251                         // format, ignore it due to spec conflict.
 252                         if (token.startsWith("GMT+") || token.startsWith("GMT-")) {
 253                             continue;
 254                         }
 255                         zone = new Zone(token);
 256                     } else {
 257                         // no way to push the current token back...
 258                         tokens = new StringTokenizer(l);
 259                     }
 260 
 261                     ZoneRec zrec = ZoneRec.parse(tokens);
 262                     zrec.setLine(line);
 263                     zone.add(zrec);
 264                     if ((continued = zrec.hasUntil()) == false) {
 265                         if (Zone.isTargetZone(zone.getName())) {
 266                             // zone.resolve(zi);
 267                             zi.add(zone);
 268                         }
 269                         zone = null;
 270                     }
 271                 } else if ("Rule".equals(token)) {
 272                     if (!tokens.hasMoreTokens()) {
 273                         panic("syntax error: rule no more token");
 274                     }
 275                     token = tokens.nextToken();
 276                     Rule rule = zi.getRule(token);
 277                     if (rule == null) {
 278                         rule = new Rule(token);
 279                         zi.add(rule);
 280                     }
 281                     RuleRec rrec = RuleRec.parse(tokens);
 282                     rrec.setLine(line);
 283                     rule.add(rrec);
 284                 } else if ("Link".equals(token)) {
 285                     // Link <newname> <oldname>
 286                     try {
 287                         String name1 = tokens.nextToken();
 288                         String name2 = tokens.nextToken();
 289 
 290                         // if the zone name is in "GMT+hh" or "GMT-hh"
 291                         // format, ignore it due to spec conflict with
 292                         // custom time zones. Also, ignore "ROC" for
 293                         // PC-ness.
 294                         if (name2.startsWith("GMT+") || name2.startsWith("GMT-")
 295                             || "ROC".equals(name2)) {
 296                             continue;
 297                         }
 298                         zi.putAlias(name2, name1);
 299                     } catch (Exception e) {
 300                         panic("syntax error: no more token for Link");
 301                     }
 302                 }
 303             }
 304             in.close();
 305         } catch (IOException ex) {
 306             panic("IO error: " + ex.getMessage());
 307         }
 308 
 309         return zi;
 310     }
 311 
 312     /**
 313      * Interprets a zone and constructs a Timezone object that
 314      * contains enough information on GMT offsets and DST schedules to
 315      * generate a zone info database.
 316      *
 317      * @param zoneName the zone name for which a Timezone object is
 318      * constructed.
 319      *
 320      * @return a Timezone object that contains all GMT offsets and DST
 321      * rules information.
 322      */
 323     Timezone phase2(String zoneName) {
 324         Timezone tz = new Timezone(zoneName);
 325         Zone zone = getZone(zoneName);
 326         zone.resolve(this);
 327 
 328         // TODO: merge phase2's for the regular and SimpleTimeZone ones.
 329         if (isYearForTimeZoneDataSpecified) {
 330             ZoneRec zrec = zone.get(zone.size()-1);
 331             tz.setLastZoneRec(zrec);
 332             tz.setRawOffset(zrec.getGmtOffset());
 333             if (zrec.hasRuleReference()) {
 334                 /*
 335                  * This part assumes that the specified year is covered by
 336                  * the rules referred to by the last zone record.
 337                  */
 338                 List<RuleRec> rrecs = zrec.getRuleRef().getRules(startYear);
 339 
 340                 if (rrecs.size() == 2) {
 341                     // make sure that one is a start rule and the other is
 342                     // an end rule.
 343                     RuleRec r0 = rrecs.get(0);
 344                     RuleRec r1 = rrecs.get(1);
 345                     if (r0.getSave() == 0 && r1.getSave() > 0) {
 346                         rrecs.set(0, r1);
 347                         rrecs.set(1, r0);
 348                     } else if (!(r0.getSave() > 0 && r1.getSave() == 0)) {
 349                         rrecs = null;
 350                         Main.error(zoneName + ": rules for " +  startYear + " not found.");
 351                     }
 352                 } else {
 353                     rrecs = null;
 354                 }
 355                 if (rrecs != null) {
 356                     tz.setLastRules(rrecs);
 357                 }
 358             }
 359             return tz;
 360         }
 361 
 362         int gmtOffset;
 363         int year = minYear;
 364         int fromYear = year;
 365         long fromTime = Time.getLocalTime(startYear,
 366                                           Month.JANUARY,
 367                                           1, 0);
 368 
 369         // take the index 0 for the GMT offset of the last zone record
 370         ZoneRec zrec = zone.get(zone.size()-1);
 371         tz.getOffsetIndex(zrec.getGmtOffset());
 372 
 373         int lastGmtOffsetValue = -1;
 374         ZoneRec prevzrec = null;
 375         int currentSave = 0;
 376         boolean usedZone;
 377         for (int zindex = 0; zindex < zone.size(); zindex++) {
 378             zrec = zone.get(zindex);
 379             usedZone = false;
 380             gmtOffset = zrec.getGmtOffset();
 381             int stdOffset = zrec.getDirectSave();
 382 
 383             if (gmtOffset != lastGmtOffsetValue) {
 384                 tz.setRawOffset(gmtOffset, fromTime);
 385                 lastGmtOffsetValue = gmtOffset;
 386             }
 387             // If this is the last zone record, take the last rule info.
 388             if (!zrec.hasUntil()) {
 389                 if (zrec.hasRuleReference()) {
 390                     tz.setLastRules(zrec.getRuleRef().getLastRules());
 391                 } else if (stdOffset != 0) {
 392                     // in case the last rule is all year round DST-only
 393                     // (Asia/Amman once announced this rule.)
 394                     tz.setLastDSTSaving(stdOffset);
 395                 }
 396             }
 397             if (!zrec.hasRuleReference()) {
 398                 if (!zrec.hasUntil() || zrec.getUntilTime(stdOffset) >= fromTime) {
 399                     tz.addTransition(fromTime,
 400                                      tz.getOffsetIndex(gmtOffset+stdOffset),
 401                                      tz.getDstOffsetIndex(stdOffset));
 402                     usedZone = true;
 403                 }
 404                 currentSave = stdOffset;
 405                 // optimization in case the last rule is fixed.
 406                 if (!zrec.hasUntil()) {
 407                     if (tz.getNTransitions() > 0) {
 408                         if (stdOffset == 0) {
 409                             tz.setDSTType(Timezone.X_DST);
 410                         } else {
 411                             tz.setDSTType(Timezone.LAST_DST);
 412                         }
 413                         long time = Time.getLocalTime(maxYear,
 414                                                       Month.JANUARY, 1, 0);
 415                         time -= zrec.getGmtOffset();
 416                         tz.addTransition(time,
 417                                          tz.getOffsetIndex(gmtOffset+stdOffset),
 418                                          tz.getDstOffsetIndex(stdOffset));
 419                         tz.addUsedRec(zrec);
 420                     } else {
 421                         tz.setDSTType(Timezone.NO_DST);
 422                     }
 423                     break;
 424                 }
 425             } else {
 426                 Rule rule = zrec.getRuleRef();
 427                 boolean fromTimeUsed = false;
 428                 currentSave = 0;
 429             year_loop:
 430                 for (year = getMinYear(); year <= endYear; year++) {
 431                     if (zrec.hasUntil() && year > zrec.getUntilYear()) {
 432                         break;
 433                     }
 434                     List<RuleRec> rules = rule.getRules(year);
 435                     if (rules.size() > 0) {
 436                         for (int i = 0; i < rules.size(); i++) {
 437                             RuleRec rrec = rules.get(i);
 438                             long transition = rrec.getTransitionTime(year,
 439                                                                      gmtOffset,
 440                                                                      currentSave);
 441                             if (zrec.hasUntil()) {
 442                                 if (transition >= zrec.getUntilTime(currentSave)) {
 443                                     // If the GMT offset changed from the previous one,
 444                                     // record fromTime as a transition.
 445                                     if (!fromTimeUsed && prevzrec != null
 446                                         && gmtOffset != prevzrec.getGmtOffset()) {
 447                                         tz.addTransition(fromTime,
 448                                                          tz.getOffsetIndex(gmtOffset+currentSave),
 449                                                          tz.getDstOffsetIndex(currentSave));
 450                                         fromTimeUsed = true; // for consistency
 451                                     }
 452                                     break year_loop;
 453                                 }
 454                             }
 455 
 456                             if (fromTimeUsed == false) {
 457                                 if (fromTime <= transition) {
 458                                     fromTimeUsed = true;
 459 
 460                                     if (fromTime != minTime) {
 461                                         int prevsave;
 462 
 463                                         // See if until time in the previous
 464                                         // ZoneRec is the same thing as the
 465                                         // local time in the next rule.
 466                                         // (examples are Asia/Ashkhabad in 1991,
 467                                         // Europe/Riga in 1989)
 468 
 469                                         if (i > 0) {
 470                                             prevsave = rules.get(i-1).getSave();
 471                                         } else {
 472                                             List<RuleRec> prevrules = rule.getRules(year-1);
 473 
 474                                             if (prevrules.size() > 0) {
 475                                                 prevsave = prevrules.get(prevrules.size()-1).getSave();
 476                                             } else {
 477                                                 prevsave = 0;
 478                                             }
 479                                         }
 480 
 481                                         if (rrec.isSameTransition(prevzrec, prevsave, gmtOffset)) {
 482                                             currentSave = rrec.getSave();
 483                                             tz.addTransition(fromTime,
 484                                                          tz.getOffsetIndex(gmtOffset+currentSave),
 485                                                          tz.getDstOffsetIndex(currentSave));
 486                                             tz.addUsedRec(rrec);
 487                                             usedZone = true;
 488                                             continue;
 489                                         }
 490                                         if (!prevzrec.hasRuleReference()
 491                                             || rule != prevzrec.getRuleRef()
 492                                             || (rule == prevzrec.getRuleRef()
 493                                                 && gmtOffset != prevzrec.getGmtOffset())) {
 494                                             int save = (fromTime == transition) ? rrec.getSave() : currentSave;
 495                                             tz.addTransition(fromTime,
 496                                                          tz.getOffsetIndex(gmtOffset+save),
 497                                                          tz.getDstOffsetIndex(save));
 498                                             tz.addUsedRec(rrec);
 499                                             usedZone = true;
 500                                         }
 501                                     } else {  // fromTime == minTime
 502                                         int save = rrec.getSave();
 503                                         tz.addTransition(minTime,
 504                                                          tz.getOffsetIndex(gmtOffset),
 505                                                          tz.getDstOffsetIndex(0));
 506 
 507                                         tz.addTransition(transition,
 508                                                          tz.getOffsetIndex(gmtOffset+save),
 509                                                          tz.getDstOffsetIndex(save));
 510 
 511                                         tz.addUsedRec(rrec);
 512                                         usedZone = true;
 513                                     }
 514                                 } else if (year == fromYear && i == rules.size()-1) {
 515                                     int save = rrec.getSave();
 516                                     tz.addTransition(fromTime,
 517                                                      tz.getOffsetIndex(gmtOffset+save),
 518                                                      tz.getDstOffsetIndex(save));
 519                                 }
 520                             }
 521 
 522                             currentSave = rrec.getSave();
 523                             if (fromTime < transition) {
 524                                 tz.addTransition(transition,
 525                                                  tz.getOffsetIndex(gmtOffset+currentSave),
 526                                                  tz.getDstOffsetIndex(currentSave));
 527                                 tz.addUsedRec(rrec);
 528                                 usedZone = true;
 529                             }
 530                         }
 531                     } else {
 532                         if (year == fromYear) {
 533                             tz.addTransition(fromTime,
 534                                              tz.getOffsetIndex(gmtOffset+currentSave),
 535                                              tz.getDstOffsetIndex(currentSave));
 536                             fromTimeUsed = true;
 537                         }
 538                         if (year == endYear && !zrec.hasUntil()) {
 539                             if (tz.getNTransitions() > 0) {
 540                                 // Assume that this Zone stopped DST
 541                                 tz.setDSTType(Timezone.X_DST);
 542                                 long time = Time.getLocalTime(maxYear, Month.JANUARY,
 543                                                               1, 0);
 544                                 time -= zrec.getGmtOffset();
 545                                 tz.addTransition(time,
 546                                                  tz.getOffsetIndex(gmtOffset),
 547                                                  tz.getDstOffsetIndex(0));
 548                                 usedZone = true;
 549                             } else {
 550                                 tz.setDSTType(Timezone.NO_DST);
 551                             }
 552                         }
 553                     }
 554                 }
 555             }
 556             if (usedZone) {
 557                 tz.addUsedRec(zrec);
 558             }
 559             if (zrec.hasUntil() && zrec.getUntilTime(currentSave) > fromTime) {
 560                 fromTime = zrec.getUntilTime(currentSave);
 561                 fromYear = zrec.getUntilYear();
 562                 year = zrec.getUntilYear();
 563             }
 564             prevzrec = zrec;
 565         }
 566 
 567         if (tz.getDSTType() == Timezone.UNDEF_DST) {
 568             tz.setDSTType(Timezone.DST);
 569         }
 570         tz.optimize();
 571         tz.checksum();
 572         return tz;
 573     }
 574 
 575     private static void panic(String msg) {
 576         Main.panic(msg);
 577     }
 578 }