001: /*
002: * @(#)Zoneinfo.java 1.7 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package sun.tools.javazic;
028:
029: import java.io.BufferedReader;
030: import java.io.FileReader;
031: import java.io.FileNotFoundException;
032: import java.io.IOException;
033: import java.util.ArrayList;
034: import java.util.HashMap;
035: import java.util.Iterator;
036: import java.util.StringTokenizer;
037:
038: /**
039: * Zoneinfo provides javazic compiler front-end functionality.
040: * @since 1.4
041: */
042: class Zoneinfo {
043:
044: private static final int minYear = 1900;
045: private static final int maxYear = 2037;
046: private static int startYear = minYear;
047: private static int endYear = maxYear;
048:
049: /**
050: * True if javazic should generate a list of SimpleTimeZone
051: * instantiations for the SimpleTimeZone-based time zone support.
052: */
053: static boolean isYearForTimeZoneDataSpecified = false;
054:
055: private HashMap zones;
056: private HashMap rules;
057: private HashMap aliases;
058:
059: /**
060: * Constracts a Zoneinfo.
061: */
062: Zoneinfo() {
063: zones = new HashMap();
064: rules = new HashMap();
065: aliases = new HashMap();
066: }
067:
068: /**
069: * Adds the given zone to the list of Zones.
070: * @param zone Zone to be added to the list.
071: */
072: void add(Zone zone) {
073: String name = zone.getName();
074: zones.put(name, zone);
075: }
076:
077: /**
078: * Adds the given rule to the list of Rules.
079: * @param rule Rule to be added to the list.
080: */
081: void add(Rule rule) {
082: String name = rule.getName();
083: rules.put(name, rule);
084: }
085:
086: /**
087: * Puts the specifid name pair to the alias table.
088: * @param name1 an alias time zone name
089: * @param name2 the real time zone of the alias name
090: */
091: void putAlias(String name1, String name2) {
092: aliases.put(name1, name2);
093: }
094:
095: /**
096: * Sets the given year for SimpleTimeZone list output.
097: * This method is called when the -S option is specified.
098: * @param year the year for which SimpleTimeZone list should be generated
099: */
100: static void setYear(int year) {
101: setStartYear(year);
102: setEndYear(year);
103: isYearForTimeZoneDataSpecified = true;
104: }
105:
106: /**
107: * Sets the start year.
108: * @param year the start year value
109: * @throws IllegalArgumentException if the specified year value is
110: * smaller than the minimum year or greater than the end year.
111: */
112: static void setStartYear(int year) {
113: if (year < minYear || year > endYear) {
114: throw new IllegalArgumentException(
115: "invalid start year specified: " + year);
116: }
117: startYear = year;
118: }
119:
120: /**
121: * @return the start year value
122: */
123: static int getStartYear() {
124: return startYear;
125: }
126:
127: /**
128: * Sets the end year.
129: * @param year the end year value
130: * @throws IllegalArgumentException if the specified year value is
131: * smaller than the start year or greater than the maximum year.
132: */
133: static void setEndYear(int year) {
134: if (year < startYear || year > maxYear) {
135: throw new IllegalArgumentException();
136: }
137: endYear = year;
138: }
139:
140: /**
141: * @return the end year value
142: */
143: static int getEndYear() {
144: return endYear;
145: }
146:
147: /**
148: * @return the minimum year value
149: */
150: static int getMinYear() {
151: return minYear;
152: }
153:
154: /**
155: * @return the maximum year value
156: */
157: static int getMaxYear() {
158: return maxYear;
159: }
160:
161: /**
162: * @return the alias table
163: */
164: HashMap getAliases() {
165: return (aliases);
166: }
167:
168: /**
169: * @return the Zone list
170: */
171: HashMap getZones() {
172: return (zones);
173: }
174:
175: /**
176: * @return a Zone specified by name.
177: * @param name a zone name
178: */
179: Zone getZone(String name) {
180: return (Zone) zones.get(name);
181: }
182:
183: /**
184: * @return a Rule specified by name.
185: * @param name a rule name
186: */
187: Rule getRule(String name) {
188: return (Rule) rules.get(name);
189: }
190:
191: /**
192: * @return an interator of the Zone list
193: */
194: Iterator getZoneIterator() {
195: return zones.keySet().iterator();
196: }
197:
198: private static String line;
199:
200: private static int lineNum;
201:
202: /**
203: * Parses the specified time zone data file and creates a Zoneinfo
204: * that has all Rules, Zones and Links (aliases) information.
205: * @param fname the time zone data file name
206: * @return a Zoneinfo object
207: */
208: static Zoneinfo parse(String fname) {
209: BufferedReader in = null;
210: try {
211: FileReader fr = new FileReader(fname);
212: in = new BufferedReader(fr);
213: } catch (FileNotFoundException e) {
214: panic("can't open file: " + fname);
215: }
216: Zoneinfo zi = new Zoneinfo();
217: boolean continued = false;
218: Zone zone = null;
219: String l;
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+")
253: || token.startsWith("GMT-")) {
254: continue;
255: }
256: zone = new Zone(token);
257: } else {
258: // no way to push the current token back...
259: tokens = new StringTokenizer(l);
260: }
261:
262: ZoneRec zrec = ZoneRec.parse(tokens);
263: zrec.setLine(line);
264: zone.add(zrec);
265: if ((continued = zrec.hasUntil()) == false) {
266: if (Zone.isTargetZone(zone.getName())) {
267: // zone.resolve(zi);
268: zi.add(zone);
269: }
270: zone = null;
271: }
272: } else if ("Rule".equals(token)) {
273: if (!tokens.hasMoreTokens()) {
274: panic("syntax error: rule no more token");
275: }
276: token = tokens.nextToken();
277: Rule rule = zi.getRule(token);
278: if (rule == null) {
279: rule = new Rule(token);
280: zi.add(rule);
281: }
282: RuleRec rrec = RuleRec.parse(tokens);
283: rrec.setLine(line);
284: rule.add(rrec);
285: } else if ("Link".equals(token)) {
286: // Link <newname> <oldname>
287: try {
288: String name1 = tokens.nextToken();
289: String name2 = tokens.nextToken();
290:
291: // if the zone name is in "GMT+hh" or "GMT-hh"
292: // format, ignore it due to spec
293: // conflict. Also, ignore "ROC" for PC-ness.
294: if (name2.startsWith("GMT+")
295: || name2.startsWith("GMT-")
296: || "ROC".equals(name2)) {
297: continue;
298: }
299: zi.putAlias(name2, name1);
300: } catch (Exception e) {
301: panic("syntax error: no more token for Link");
302: }
303: }
304: }
305: in.close();
306: } catch (IOException ex) {
307: panic("IO error: " + ex.getMessage());
308: }
309:
310: return zi;
311: }
312:
313: /**
314: * Interprets a zone and constructs a Timezone object that
315: * contains enough information on GMT offsets and DST schedules to
316: * generate a zone info database.
317: *
318: * @param zoneName the zone name for which a Timezone object is
319: * constructed.
320: *
321: * @return a Timezone object that contains all GMT offsets and DST
322: * rules information.
323: */
324: Timezone phase2(String zoneName) {
325: Timezone tz = new Timezone(zoneName);
326: Zone zone = getZone(zoneName);
327: zone.resolve(this );
328:
329: // TODO: merge phase2's for the regular and SimpleTimeZone ones.
330: if (isYearForTimeZoneDataSpecified) {
331: ZoneRec zrec = zone.get(zone.size() - 1);
332: tz.setLastZoneRec(zrec);
333: tz.setRawOffset(zrec.getGmtOffset());
334: if (zrec.hasRuleReference()) {
335: /*
336: * This part assumes that the specified year is covered by
337: * the rules referred to by the last zone record.
338: */
339: ArrayList rrecs = zrec.getRuleRef().getRules(startYear);
340:
341: if (rrecs.size() == 2) {
342: // make sure that one is a start rule and the other is
343: // an end rule.
344: RuleRec r0 = (RuleRec) rrecs.get(0);
345: RuleRec r1 = (RuleRec) rrecs.get(1);
346: if (r0.getSave() == 0 && r1.getSave() > 0) {
347: rrecs.set(0, r1);
348: rrecs.set(1, r0);
349: } else if (!(r0.getSave() > 0 && r1.getSave() == 0)) {
350: rrecs = null;
351: Main.error(zoneName + ": rules for "
352: + 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.parse("Jan"), 1, 0);
369:
370: // take the index 0 for the GMT offset of the last zone record
371: ZoneRec zrec = zone.get(zone.size() - 1);
372: tz.getOffsetIndex(zrec.getGmtOffset());
373:
374: int currentSave = 0;
375: boolean usedZone;
376: for (int zindex = 0; zindex < zone.size(); zindex++) {
377: zrec = zone.get(zindex);
378: usedZone = false;
379: gmtOffset = zrec.getGmtOffset();
380: int stdOffset = zrec.getDirectSave();
381:
382: // If this is the last zone record, take the last rule info.
383: if (!zrec.hasUntil()) {
384: tz.setRawOffset(gmtOffset, fromTime);
385: if (zrec.hasRuleReference()) {
386: tz.setLastRules(zrec.getRuleRef().getLastRules());
387: } else if (stdOffset != 0) {
388: // in case the last rule is all year round DST-only
389: // (Asia/Amman once announced this rule.)
390: tz.setLastDSTSaving(stdOffset);
391: }
392: }
393: if (!zrec.hasRuleReference()) {
394: if (!zrec.hasUntil()
395: || zrec.getUntilTime(stdOffset) >= fromTime) {
396: tz.addTransition(fromTime, tz
397: .getOffsetIndex(gmtOffset + stdOffset), tz
398: .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(tz.X_DST);
407: } else {
408: tz.setDSTType(tz.LAST_DST);
409: }
410: long time = Time.getLocalTime(maxYear, Month
411: .parse("Jan"), 1, 0);
412: time -= zrec.getGmtOffset();
413: tz.addTransition(time, tz
414: .getOffsetIndex(gmtOffset + stdOffset),
415: tz.getDstOffsetIndex(stdOffset));
416: tz.addUsedRec(zrec);
417: } else {
418: tz.setDSTType(tz.NO_DST);
419: }
420: break;
421: }
422: } else {
423: Rule rule = zrec.getRuleRef();
424: boolean fromTimeUsed = false;
425: currentSave = 0;
426: year_loop: for (year = getMinYear(); year <= endYear; year++) {
427: if (zrec.hasUntil() && year > zrec.getUntilYear()) {
428: break;
429: }
430: ArrayList rules = rule.getRules(year);
431: if (rules.size() > 0) {
432: for (int i = 0; i < rules.size(); i++) {
433: RuleRec rrec = (RuleRec) rules.get(i);
434: long transition = rrec.getTransitionTime(
435: year, gmtOffset, currentSave);
436: if (zrec.hasUntil()) {
437: if (transition >= zrec
438: .getUntilTime(currentSave)) {
439: break year_loop;
440: }
441: }
442:
443: if (fromTimeUsed == false) {
444: int prevsave;
445:
446: if (fromTime <= transition) {
447: ZoneRec prevzrec = zone
448: .get(zindex - 1);
449: fromTimeUsed = true;
450:
451: // See if until time in the previous ZoneRec is the same thing
452: // as the local time in the next rule. (examples are
453: // Asia/Ashkhabad in 1991, Europe/Riga in 1989)
454:
455: if (i > 0)
456: prevsave = ((RuleRec) (rules
457: .get(i - 1))).getSave();
458: else {
459: ArrayList prevrules = rule
460: .getRules(year - 1);
461:
462: if (prevrules.size() > 0)
463: prevsave = ((RuleRec) (prevrules
464: .get(prevrules
465: .size() - 1)))
466: .getSave();
467: else
468: prevsave = 0;
469: }
470:
471: if (rrec.isSameTransition(prevzrec,
472: prevsave, gmtOffset)) {
473: currentSave = rrec.getSave();
474: tz
475: .addTransition(
476: fromTime,
477: tz
478: .getOffsetIndex(gmtOffset
479: + currentSave),
480: tz
481: .getDstOffsetIndex(currentSave));
482: usedZone = true;
483: tz.addUsedRec(rrec);
484: continue;
485: }
486: if (!prevzrec.hasRuleReference()
487: || rule != prevzrec
488: .getRuleRef()
489: || (rule == prevzrec
490: .getRuleRef() && gmtOffset != prevzrec
491: .getGmtOffset())) {
492: int save = (fromTime == transition) ? rrec
493: .getSave()
494: : currentSave;
495: tz
496: .addTransition(
497: fromTime,
498: tz
499: .getOffsetIndex(gmtOffset
500: + save),
501: tz
502: .getDstOffsetIndex(save));
503: tz.addUsedRec(rrec);
504: usedZone = true;
505: }
506: } else if (year == fromYear
507: && i == rules.size() - 1) {
508: int save = rrec.getSave();
509: tz.addTransition(fromTime, tz
510: .getOffsetIndex(gmtOffset
511: + save), tz
512: .getDstOffsetIndex(save));
513: }
514: }
515:
516: currentSave = rrec.getSave();
517: if (fromTime < transition) {
518: tz
519: .addTransition(
520: transition,
521: tz
522: .getOffsetIndex(gmtOffset
523: + currentSave),
524: tz
525: .getDstOffsetIndex(currentSave));
526: tz.addUsedRec(rrec);
527: usedZone = true;
528: }
529: }
530: } else {
531: if (year == fromYear) {
532: tz.addTransition(fromTime, tz
533: .getOffsetIndex(gmtOffset
534: + currentSave), tz
535: .getDstOffsetIndex(currentSave));
536: fromTimeUsed = true;
537: }
538: if (year == endYear && !zrec.hasUntil()) {
539: if (tz.getNTransitions() > 0) {
540: // Assume this Zone stopped DST
541: tz.setDSTType(tz.X_DST);
542: long time = Time.getLocalTime(maxYear,
543: Month.parse("Jan"), 1, 0);
544: time -= zrec.getGmtOffset();
545: tz.addTransition(time, tz
546: .getOffsetIndex(gmtOffset), tz
547: .getDstOffsetIndex(0));
548: usedZone = true;
549: } else {
550: tz.setDSTType(tz.NO_DST);
551: }
552: }
553: }
554: }
555: }
556: if (usedZone) {
557: tz.addUsedRec(zrec);
558: }
559: if (zrec.hasUntil()
560: && zrec.getUntilTime(currentSave) > fromTime) {
561: fromTime = zrec.getUntilTime(currentSave);
562: fromYear = zrec.getUntilYear();
563: year = zrec.getUntilYear();
564: }
565: }
566:
567: if (tz.getDSTType() == tz.UNDEF_DST) {
568: tz.setDSTType(tz.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: }
|