1 /* 2 * Copyright (c) 2000, 2011, 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 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 this is the last zone record, take the last rule info. 384 if (!zrec.hasUntil()) { 385 tz.setRawOffset(gmtOffset, fromTime); 386 if (zrec.hasRuleReference()) { 387 tz.setLastRules(zrec.getRuleRef().getLastRules()); 388 } else if (stdOffset != 0) { 389 // in case the last rule is all year round DST-only 390 // (Asia/Amman once announced this rule.) 391 tz.setLastDSTSaving(stdOffset); 392 } 393 } 394 if (!zrec.hasRuleReference()) { 395 if (!zrec.hasUntil() || zrec.getUntilTime(stdOffset) >= fromTime) { 396 tz.addTransition(fromTime, 397 tz.getOffsetIndex(gmtOffset+stdOffset), 398 tz.getDstOffsetIndex(stdOffset)); 399 usedZone = true; 400 } 401 currentSave = stdOffset; 402 // optimization in case the last rule is fixed. 403 if (!zrec.hasUntil()) { 404 if (tz.getNTransitions() > 0) { 405 if (stdOffset == 0) { 406 tz.setDSTType(Timezone.X_DST); 407 } else { 408 tz.setDSTType(Timezone.LAST_DST); 409 } 410 long time = Time.getLocalTime(maxYear, 411 Month.JANUARY, 1, 0); 412 time -= zrec.getGmtOffset(); 413 tz.addTransition(time, 414 tz.getOffsetIndex(gmtOffset+stdOffset), 415 tz.getDstOffsetIndex(stdOffset)); 416 tz.addUsedRec(zrec); 417 } else { 418 tz.setDSTType(Timezone.NO_DST); 419 } 420 break; 421 } 422 } else { 423 Rule rule = zrec.getRuleRef(); 424 boolean fromTimeUsed = false; 425 currentSave = 0; 426 year_loop: 427 for (year = getMinYear(); year <= endYear; year++) { 428 if (zrec.hasUntil() && year > zrec.getUntilYear()) { 429 break; 430 } 431 List<RuleRec> rules = rule.getRules(year); 432 if (rules.size() > 0) { 433 for (int i = 0; i < rules.size(); i++) { 434 RuleRec rrec = rules.get(i); 435 long transition = rrec.getTransitionTime(year, 436 gmtOffset, 437 currentSave); 438 if (zrec.hasUntil()) { 439 if (transition >= zrec.getUntilTime(currentSave)) { 440 break year_loop; 441 } 442 } 443 444 if (fromTimeUsed == false) { 445 if (fromTime <= transition) { 446 fromTimeUsed = true; 447 448 if (fromTime != minTime) { 449 int prevsave; 450 451 ZoneRec prevzrec = zone.get(zindex - 1); 452 453 // See if until time in the previous 454 // ZoneRec is the same thing as the 455 // local time in the next rule. 456 // (examples are Asia/Ashkhabad in 1991, 457 // Europe/Riga in 1989) 458 459 if (i > 0) { 460 prevsave = rules.get(i-1).getSave(); 461 } else { 462 List<RuleRec> prevrules = rule.getRules(year-1); 463 464 if (prevrules.size() > 0) { 465 prevsave = prevrules.get(prevrules.size()-1).getSave(); 466 } else { 467 prevsave = 0; 468 } 469 } 470 471 if (rrec.isSameTransition(prevzrec, prevsave, gmtOffset)) { 472 currentSave = rrec.getSave(); 473 tz.addTransition(fromTime, 474 tz.getOffsetIndex(gmtOffset+currentSave), 475 tz.getDstOffsetIndex(currentSave)); 476 tz.addUsedRec(rrec); 477 usedZone = true; 478 continue; 479 } 480 if (!prevzrec.hasRuleReference() 481 || rule != prevzrec.getRuleRef() 482 || (rule == prevzrec.getRuleRef() 483 && gmtOffset != prevzrec.getGmtOffset())) { 484 int save = (fromTime == transition) ? rrec.getSave() : currentSave; 485 tz.addTransition(fromTime, 486 tz.getOffsetIndex(gmtOffset+save), 487 tz.getDstOffsetIndex(save)); 488 tz.addUsedRec(rrec); 489 usedZone = true; 490 } 491 } else { // fromTime == minTime 492 int save = rrec.getSave(); 493 tz.addTransition(minTime, 494 tz.getOffsetIndex(gmtOffset), 495 tz.getDstOffsetIndex(0)); 496 497 tz.addTransition(transition, 498 tz.getOffsetIndex(gmtOffset+save), 499 tz.getDstOffsetIndex(save)); 500 501 tz.addUsedRec(rrec); 502 usedZone = true; 503 } 504 } else if (year == fromYear && i == rules.size()-1) { 505 int save = rrec.getSave(); 506 tz.addTransition(fromTime, 507 tz.getOffsetIndex(gmtOffset+save), 508 tz.getDstOffsetIndex(save)); 509 } 510 } 511 512 currentSave = rrec.getSave(); 513 if (fromTime < transition) { 514 tz.addTransition(transition, 515 tz.getOffsetIndex(gmtOffset+currentSave), 516 tz.getDstOffsetIndex(currentSave)); 517 tz.addUsedRec(rrec); 518 usedZone = true; 519 } 520 } 521 } else { 522 if (year == fromYear) { 523 tz.addTransition(fromTime, 524 tz.getOffsetIndex(gmtOffset+currentSave), 525 tz.getDstOffsetIndex(currentSave)); 526 fromTimeUsed = true; 527 } 528 if (year == endYear && !zrec.hasUntil()) { 529 if (tz.getNTransitions() > 0) { 530 // Assume that this Zone stopped DST 531 tz.setDSTType(Timezone.X_DST); 532 long time = Time.getLocalTime(maxYear, Month.JANUARY, 533 1, 0); 534 time -= zrec.getGmtOffset(); 535 tz.addTransition(time, 536 tz.getOffsetIndex(gmtOffset), 537 tz.getDstOffsetIndex(0)); 538 usedZone = true; 539 } else { 540 tz.setDSTType(Timezone.NO_DST); 541 } 542 } 543 } 544 } 545 } 546 if (usedZone) { 547 tz.addUsedRec(zrec); 548 } 549 if (zrec.hasUntil() && zrec.getUntilTime(currentSave) > fromTime) { 550 fromTime = zrec.getUntilTime(currentSave); 551 fromYear = zrec.getUntilYear(); 552 year = zrec.getUntilYear(); 553 } 554 } 555 556 if (tz.getDSTType() == Timezone.UNDEF_DST) { 557 tz.setDSTType(Timezone.DST); 558 } 559 tz.optimize(); 560 tz.checksum(); 561 return tz; 562 } 563 564 private static void panic(String msg) { 565 Main.panic(msg); 566 } 567 }