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 }