|
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 } |