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