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 }