EMMA Coverage Report (generated Tue Oct 28 00:01:11 GMT 2008)
[all classes][org.joda.time.tz]

COVERAGE SUMMARY FOR SOURCE FILE [ZoneInfoCompiler.java]

nameclass, %method, %block, %line, %
ZoneInfoCompiler.java100% (5/5)88%  (29/33)71%  (1226/1736)81%  (293.3/363)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ZoneInfoCompiler$Rule100% (1/1)75%  (3/4)57%  (104/181)76%  (22/29)
toString (): String 0%   (0/1)0%   (0/50)0%   (0/1)
formatName (String): String 100% (1/1)64%  (41/64)64%  (9/14)
ZoneInfoCompiler$Rule (StringTokenizer): void 100% (1/1)92%  (47/51)91%  (10/11)
addRecurring (DateTimeZoneBuilder, String): void 100% (1/1)100% (16/16)100% (3/3)
     
class ZoneInfoCompiler$Zone100% (1/1)83%  (5/6)59%  (122/207)81%  (34/42)
toString (): String 0%   (0/1)0%   (0/61)0%   (0/4)
addToBuilder (ZoneInfoCompiler$Zone, DateTimeZoneBuilder, Map): void 100% (1/1)66%  (47/71)75%  (12/16)
ZoneInfoCompiler$Zone (String, StringTokenizer): void 100% (1/1)100% (47/47)100% (14/14)
ZoneInfoCompiler$Zone (StringTokenizer): void 100% (1/1)100% (6/6)100% (2/2)
addToBuilder (DateTimeZoneBuilder, Map): void 100% (1/1)100% (5/5)100% (2/2)
chain (StringTokenizer): void 100% (1/1)100% (17/17)100% (4/4)
     
class ZoneInfoCompiler$DateTimeOfYear100% (1/1)80%  (4/5)69%  (166/240)87%  (48/55)
toString (): String 0%   (0/1)0%   (0/47)0%   (0/1)
ZoneInfoCompiler$DateTimeOfYear (StringTokenizer): void 100% (1/1)80%  (108/135)86%  (36/42)
ZoneInfoCompiler$DateTimeOfYear (): void 100% (1/1)100% (21/21)100% (8/8)
addCutover (DateTimeZoneBuilder, int): void 100% (1/1)100% (17/17)100% (2/2)
addRecurring (DateTimeZoneBuilder, String, int, int, int): void 100% (1/1)100% (20/20)100% (2/2)
     
class ZoneInfoCompiler100% (1/1)93%  (14/15)74%  (785/1054)79%  (178.3/225)
printUsage (): void 0%   (0/1)0%   (0/13)0%   (0/5)
test (String, DateTimeZone): boolean 100% (1/1)60%  (124/208)75%  (25.6/34)
compile (File, File []): Map 100% (1/1)68%  (190/278)79%  (41.8/53)
parseZoneChar (char): char 100% (1/1)75%  (6/8)75%  (3/4)
parseDataFile (BufferedReader): void 100% (1/1)78%  (114/146)85%  (28.1/33)
writeZoneInfoMap (DataOutputStream, Map): void 100% (1/1)79%  (120/152)82%  (27.2/33)
main (String []): void 100% (1/1)85%  (94/110)65%  (17.7/27)
parseYear (String, int): int 100% (1/1)94%  (30/32)88%  (7/8)
ZoneInfoCompiler (): void 100% (1/1)100% (18/18)100% (5/5)
getLenientISOChronology (): Chronology 100% (1/1)100% (7/7)100% (3/3)
getStartOfYear (): ZoneInfoCompiler$DateTimeOfYear 100% (1/1)100% (8/8)100% (3/3)
parseDayOfWeek (String): int 100% (1/1)100% (11/11)100% (2/2)
parseMonth (String): int 100% (1/1)100% (11/11)100% (2/2)
parseOptional (String): String 100% (1/1)100% (8/8)100% (1/1)
parseTime (String): int 100% (1/1)100% (44/44)100% (12/12)
     
class ZoneInfoCompiler$RuleSet100% (1/1)100% (3/3)91%  (49/54)92%  (11/12)
addRule (ZoneInfoCompiler$Rule): void 100% (1/1)76%  (16/21)75%  (3/4)
ZoneInfoCompiler$RuleSet (ZoneInfoCompiler$Rule): void 100% (1/1)100% (13/13)100% (4/4)
addRecurring (DateTimeZoneBuilder, String): void 100% (1/1)100% (20/20)100% (4/4)

1/*
2 *  Copyright 2001-2005 Stephen Colebourne
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 *  you may not use this file except in compliance with the License.
6 *  You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 */
16package org.joda.time.tz;
17 
18import java.io.BufferedReader;
19import java.io.DataOutputStream;
20import java.io.File;
21import java.io.FileInputStream;
22import java.io.FileOutputStream;
23import java.io.FileReader;
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.OutputStream;
27import java.util.ArrayList;
28import java.util.HashMap;
29import java.util.Iterator;
30import java.util.List;
31import java.util.Locale;
32import java.util.Map;
33import java.util.StringTokenizer;
34import java.util.TreeMap;
35 
36import org.joda.time.Chronology;
37import org.joda.time.DateTime;
38import org.joda.time.DateTimeField;
39import org.joda.time.DateTimeZone;
40import org.joda.time.MutableDateTime;
41import org.joda.time.chrono.ISOChronology;
42import org.joda.time.chrono.LenientChronology;
43import org.joda.time.format.DateTimeFormatter;
44import org.joda.time.format.ISODateTimeFormat;
45 
46/**
47 * Compiles Olson ZoneInfo database files into binary files for each time zone
48 * in the database. {@link DateTimeZoneBuilder} is used to construct and encode
49 * compiled data files. {@link ZoneInfoProvider} loads the encoded files and
50 * converts them back into {@link DateTimeZone} objects.
51 * <p>
52 * Although this tool is similar to zic, the binary formats are not
53 * compatible. The latest Olson database files may be obtained
54 * <a href="http://www.twinsun.com/tz/tz-link.htm">here</a>.
55 * <p>
56 * ZoneInfoCompiler is mutable and not thread-safe, although the main method
57 * may be safely invoked by multiple threads.
58 *
59 * @author Brian S O'Neill
60 * @since 1.0
61 */
62public class ZoneInfoCompiler {
63    static DateTimeOfYear cStartOfYear;
64 
65    static Chronology cLenientISO;
66 
67    /**
68     * Launches the ZoneInfoCompiler tool.
69     *
70     * <pre>
71     * Usage: java org.joda.time.tz.ZoneInfoCompiler &lt;options&gt; &lt;source files&gt;
72     * where possible options include:
73     *   -src &lt;directory&gt;    Specify where to read source files
74     *   -dst &lt;directory&gt;    Specify where to write generated files
75     * </pre>
76     */
77    public static void main(String[] args) throws Exception {
78        if (args.length == 0) {
79            printUsage();
80            return;
81        }
82 
83        File inputDir = null;
84        File outputDir = null;
85 
86        int i;
87        for (i=0; i<args.length; i++) {
88            try {
89                if ("-src".equals(args[i])) {
90                    inputDir = new File(args[++i]);
91                } else if ("-dst".equals(args[i])) {
92                    outputDir = new File(args[++i]);
93                } else if ("-?".equals(args[i])) {
94                    printUsage();
95                    return;
96                } else {
97                    break;
98                }
99            } catch (IndexOutOfBoundsException e) {
100                printUsage();
101                return;
102            }
103        }
104 
105        if (i >= args.length) {
106            printUsage();
107            return;
108        }
109 
110        File[] sources = new File[args.length - i];
111        for (int j=0; i<args.length; i++,j++) {
112            sources[j] = inputDir == null ? new File(args[i]) : new File(inputDir, args[i]);
113        }
114 
115        ZoneInfoCompiler zic = new ZoneInfoCompiler();
116        zic.compile(outputDir, sources);
117    }
118 
119    private static void printUsage() {
120        System.out.println("Usage: java org.joda.time.tz.ZoneInfoCompiler <options> <source files>");
121        System.out.println("where possible options include:");
122        System.out.println("  -src <directory>    Specify where to read source files");
123        System.out.println("  -dst <directory>    Specify where to write generated files");
124    }
125 
126    static DateTimeOfYear getStartOfYear() {
127        if (cStartOfYear == null) {
128            cStartOfYear = new DateTimeOfYear();
129        }
130        return cStartOfYear;
131    }
132 
133    static Chronology getLenientISOChronology() {
134        if (cLenientISO == null) {
135            cLenientISO = LenientChronology.getInstance(ISOChronology.getInstanceUTC());
136        }
137        return cLenientISO;
138    }
139 
140    /**
141     * @param zimap maps string ids to DateTimeZone objects.
142     */
143    static void writeZoneInfoMap(DataOutputStream dout, Map zimap) throws IOException {
144        // Build the string pool.
145        Map idToIndex = new HashMap(zimap.size());
146        TreeMap indexToId = new TreeMap();
147 
148        Iterator it = zimap.entrySet().iterator();
149        short count = 0;
150        while (it.hasNext()) {
151            Map.Entry entry = (Map.Entry)it.next();
152            String id = (String)entry.getKey();
153            if (!idToIndex.containsKey(id)) {
154                Short index = new Short(count);
155                idToIndex.put(id, index);
156                indexToId.put(index, id);
157                if (++count == 0) {
158                    throw new InternalError("Too many time zone ids");
159                }
160            }
161            id = ((DateTimeZone)entry.getValue()).getID();
162            if (!idToIndex.containsKey(id)) {
163                Short index = new Short(count);
164                idToIndex.put(id, index);
165                indexToId.put(index, id);
166                if (++count == 0) {
167                    throw new InternalError("Too many time zone ids");
168                }
169            }
170        }
171 
172        // Write the string pool, ordered by index.
173        dout.writeShort(indexToId.size());
174        it = indexToId.values().iterator();
175        while (it.hasNext()) {
176            dout.writeUTF((String)it.next());
177        }
178 
179        // Write the mappings.
180        dout.writeShort(zimap.size());
181        it = zimap.entrySet().iterator();
182        while (it.hasNext()) {
183            Map.Entry entry = (Map.Entry)it.next();
184            String id = (String)entry.getKey();
185            dout.writeShort(((Short)idToIndex.get(id)).shortValue());
186            id = ((DateTimeZone)entry.getValue()).getID();
187            dout.writeShort(((Short)idToIndex.get(id)).shortValue());
188        }
189    }
190 
191    static int parseYear(String str, int def) {
192        str = str.toLowerCase();
193        if (str.equals("minimum") || str.equals("min")) {
194            return Integer.MIN_VALUE;
195        } else if (str.equals("maximum") || str.equals("max")) {
196            return Integer.MAX_VALUE;
197        } else if (str.equals("only")) {
198            return def;
199        }
200        return Integer.parseInt(str);
201    }
202 
203    static int parseMonth(String str) {
204        DateTimeField field = ISOChronology.getInstanceUTC().monthOfYear();
205        return field.get(field.set(0, str, Locale.ENGLISH));
206    }
207 
208    static int parseDayOfWeek(String str) {
209        DateTimeField field = ISOChronology.getInstanceUTC().dayOfWeek();
210        return field.get(field.set(0, str, Locale.ENGLISH));
211    }
212    
213    static String parseOptional(String str) {
214        return (str.equals("-")) ? null : str;
215    }
216 
217    static int parseTime(String str) {
218        DateTimeFormatter p = ISODateTimeFormat.hourMinuteSecondFraction();
219        MutableDateTime mdt = new MutableDateTime(0, getLenientISOChronology());
220        int pos = 0;
221        if (str.startsWith("-")) {
222            pos = 1;
223        }
224        int newPos = p.parseInto(mdt, str, pos);
225        if (newPos == ~pos) {
226            throw new IllegalArgumentException(str);
227        }
228        int millis = (int)mdt.getMillis();
229        if (pos == 1) {
230            millis = -millis;
231        }
232        return millis;
233    }
234 
235    static char parseZoneChar(char c) {
236        switch (c) {
237        case 's': case 'S':
238            // Standard time
239            return 's';
240        case 'u': case 'U': case 'g': case 'G': case 'z': case 'Z':
241            // UTC
242            return 'u';
243        case 'w': case 'W': default:
244            // Wall time
245            return 'w';
246        }
247    }
248 
249    /**
250     * @return false if error.
251     */
252    static boolean test(String id, DateTimeZone tz) {
253        if (!id.equals(tz.getID())) {
254            return true;
255        }
256 
257        // Test to ensure that reported transitions are not duplicated.
258 
259        long millis = ISOChronology.getInstanceUTC().year().set(0, 1850);
260        long end = ISOChronology.getInstanceUTC().year().set(0, 2050);
261 
262        int offset = tz.getOffset(millis);
263        String key = tz.getNameKey(millis);
264 
265        List transitions = new ArrayList();
266 
267        while (true) {
268            long next = tz.nextTransition(millis);
269            if (next == millis || next > end) {
270                break;
271            }
272 
273            millis = next;
274 
275            int nextOffset = tz.getOffset(millis);
276            String nextKey = tz.getNameKey(millis);
277 
278            if (offset == nextOffset
279                && key.equals(nextKey)) {
280                System.out.println("*d* Error in " + tz.getID() + " "
281                                   + new DateTime(millis,
282                                                  ISOChronology.getInstanceUTC()));
283                return false;
284            }
285 
286            if (nextKey == null || (nextKey.length() < 3 && !"??".equals(nextKey))) {
287                System.out.println("*s* Error in " + tz.getID() + " "
288                                   + new DateTime(millis,
289                                                  ISOChronology.getInstanceUTC())
290                                   + ", nameKey=" + nextKey);
291                return false;
292            }
293 
294            transitions.add(new Long(millis));
295 
296            offset = nextOffset;
297            key = nextKey;
298        }
299 
300        // Now verify that reverse transitions match up.
301 
302        millis = ISOChronology.getInstanceUTC().year().set(0, 2050);
303        end = ISOChronology.getInstanceUTC().year().set(0, 1850);
304 
305        for (int i=transitions.size(); --i>= 0; ) {
306            long prev = tz.previousTransition(millis);
307            if (prev == millis || prev < end) {
308                break;
309            }
310 
311            millis = prev;
312 
313            long trans = ((Long)transitions.get(i)).longValue();
314            
315            if (trans - 1 != millis) {
316                System.out.println("*r* Error in " + tz.getID() + " "
317                                   + new DateTime(millis,
318                                                  ISOChronology.getInstanceUTC()) + " != "
319                                   + new DateTime(trans - 1,
320                                                  ISOChronology.getInstanceUTC()));
321                                   
322                return false;
323            }
324        }
325 
326        return true;
327    }
328 
329    // Maps names to RuleSets.
330    private Map iRuleSets;
331 
332    // List of Zone objects.
333    private List iZones;
334 
335    // List String pairs to link.
336    private List iLinks;
337 
338    public ZoneInfoCompiler() {
339        iRuleSets = new HashMap();
340        iZones = new ArrayList();
341        iLinks = new ArrayList();
342    }
343 
344    /**
345     * Returns a map of ids to DateTimeZones.
346     *
347     * @param outputDir optional directory to write compiled data files to
348     * @param sources optional list of source files to parse
349     */
350    public Map compile(File outputDir, File[] sources) throws IOException {
351        if (sources != null) {
352            for (int i=0; i<sources.length; i++) {
353                BufferedReader in = new BufferedReader(new FileReader(sources[i]));
354                parseDataFile(in);
355                in.close();
356            }
357        }
358 
359        if (outputDir != null) {
360            if (!outputDir.exists()) {
361                throw new IOException("Destination directory doesn't exist: " + outputDir);
362            }
363            if (!outputDir.isDirectory()) {
364                throw new IOException("Destination is not a directory: " + outputDir);
365            }
366        }
367 
368        Map map = new TreeMap();
369 
370        for (int i=0; i<iZones.size(); i++) {
371            Zone zone = (Zone)iZones.get(i);
372            DateTimeZoneBuilder builder = new DateTimeZoneBuilder();
373            zone.addToBuilder(builder, iRuleSets);
374            final DateTimeZone original = builder.toDateTimeZone(zone.iName, true);
375            DateTimeZone tz = original;
376            if (test(tz.getID(), tz)) {
377                map.put(tz.getID(), tz);
378                if (outputDir != null) {
379                    System.out.println("Writing " + tz.getID());
380                    File file = new File(outputDir, tz.getID());
381                    if (!file.getParentFile().exists()) {
382                        file.getParentFile().mkdirs();
383                    }
384                    OutputStream out = new FileOutputStream(file);
385                    builder.writeTo(zone.iName, out);
386                    out.close();
387 
388                    // Test if it can be read back.
389                    InputStream in = new FileInputStream(file);
390                    DateTimeZone tz2 = DateTimeZoneBuilder.readFrom(in, tz.getID());
391                    in.close();
392 
393                    if (!original.equals(tz2)) {
394                        System.out.println("*e* Error in " + tz.getID() +
395                                           ": Didn't read properly from file");
396                    }
397                }
398            }
399        }
400 
401        for (int pass=0; pass<2; pass++) {
402            for (int i=0; i<iLinks.size(); i += 2) {
403                String id = (String)iLinks.get(i);
404                String alias = (String)iLinks.get(i + 1);
405                DateTimeZone tz = (DateTimeZone)map.get(id);
406                if (tz == null) {
407                    if (pass > 0) {
408                        System.out.println("Cannot find time zone '" + id +
409                                           "' to link alias '" + alias + "' to");
410                    }
411                } else {
412                    map.put(alias, tz);
413                }
414            }
415        }
416 
417        if (outputDir != null) {
418            System.out.println("Writing ZoneInfoMap");
419            File file = new File(outputDir, "ZoneInfoMap");
420            if (!file.getParentFile().exists()) {
421                file.getParentFile().mkdirs();
422            }
423 
424            OutputStream out = new FileOutputStream(file);
425            DataOutputStream dout = new DataOutputStream(out);
426            // Sort and filter out any duplicates that match case.
427            Map zimap = new TreeMap(String.CASE_INSENSITIVE_ORDER);
428            zimap.putAll(map);
429            writeZoneInfoMap(dout, zimap);
430            dout.close();
431        }
432 
433        return map;
434    }
435 
436    public void parseDataFile(BufferedReader in) throws IOException {
437        Zone zone = null;
438        String line;
439        while ((line = in.readLine()) != null) {
440            String trimmed = line.trim();
441            if (trimmed.length() == 0 || trimmed.charAt(0) == '#') {
442                continue;
443            }
444 
445            int index = line.indexOf('#');
446            if (index >= 0) {
447                line = line.substring(0, index);
448            }
449 
450            //System.out.println(line);
451 
452            StringTokenizer st = new StringTokenizer(line, " \t");
453 
454            if (Character.isWhitespace(line.charAt(0)) && st.hasMoreTokens()) {
455                if (zone != null) {
456                    // Zone continuation
457                    zone.chain(st);
458                }
459                continue;
460            } else {
461                if (zone != null) {
462                    iZones.add(zone);
463                }
464                zone = null;
465            }
466 
467            if (st.hasMoreTokens()) {
468                String token = st.nextToken();
469                if (token.equalsIgnoreCase("Rule")) {
470                    Rule r = new Rule(st);
471                    RuleSet rs = (RuleSet)iRuleSets.get(r.iName);
472                    if (rs == null) {
473                        rs = new RuleSet(r);
474                        iRuleSets.put(r.iName, rs);
475                    } else {
476                        rs.addRule(r);
477                    }
478                } else if (token.equalsIgnoreCase("Zone")) {
479                    zone = new Zone(st);
480                } else if (token.equalsIgnoreCase("Link")) {
481                    iLinks.add(st.nextToken());
482                    iLinks.add(st.nextToken());
483                } else {
484                    System.out.println("Unknown line: " + line);
485                }
486            }
487        }
488 
489        if (zone != null) {
490            iZones.add(zone);
491        }
492    }
493 
494    private static class DateTimeOfYear {
495        public final int iMonthOfYear;
496        public final int iDayOfMonth;
497        public final int iDayOfWeek;
498        public final boolean iAdvanceDayOfWeek;
499        public final int iMillisOfDay;
500        public final char iZoneChar;
501 
502        DateTimeOfYear() {
503            iMonthOfYear = 1;
504            iDayOfMonth = 1;
505            iDayOfWeek = 0;
506            iAdvanceDayOfWeek = false;
507            iMillisOfDay = 0;
508            iZoneChar = 'w';
509        }
510 
511        DateTimeOfYear(StringTokenizer st) {
512            int month = 1;
513            int day = 1;
514            int dayOfWeek = 0;
515            int millis = 0;
516            boolean advance = false;
517            char zoneChar = 'w';
518 
519            if (st.hasMoreTokens()) {
520                month = parseMonth(st.nextToken());
521 
522                if (st.hasMoreTokens()) {
523                    String str = st.nextToken();
524                    if (str.startsWith("last")) {
525                        day = -1;
526                        dayOfWeek = parseDayOfWeek(str.substring(4));
527                        advance = false;
528                    } else {
529                        try {
530                            day = Integer.parseInt(str);
531                            dayOfWeek = 0;
532                            advance = false;
533                        } catch (NumberFormatException e) {
534                            int index = str.indexOf(">=");
535                            if (index > 0) {
536                                day = Integer.parseInt(str.substring(index + 2));
537                                dayOfWeek = parseDayOfWeek(str.substring(0, index));
538                                advance = true;
539                            } else {
540                                index = str.indexOf("<=");
541                                if (index > 0) {
542                                    day = Integer.parseInt(str.substring(index + 2));
543                                    dayOfWeek = parseDayOfWeek(str.substring(0, index));
544                                    advance = false;
545                                } else {
546                                    throw new IllegalArgumentException(str);
547                                }
548                            }
549                        }
550                    }
551 
552                    if (st.hasMoreTokens()) {
553                        str = st.nextToken();
554                        zoneChar = parseZoneChar(str.charAt(str.length() - 1));
555                        millis = parseTime(str);
556                    }
557                }
558            }
559 
560            iMonthOfYear = month;
561            iDayOfMonth = day;
562            iDayOfWeek = dayOfWeek;
563            iAdvanceDayOfWeek = advance;
564            iMillisOfDay = millis;
565            iZoneChar = zoneChar;
566        }
567 
568        /**
569         * Adds a recurring savings rule to the builder.
570         */
571        public void addRecurring(DateTimeZoneBuilder builder, String nameKey,
572                                 int saveMillis, int fromYear, int toYear)
573        {
574            builder.addRecurringSavings(nameKey, saveMillis,
575                                        fromYear, toYear,
576                                        iZoneChar,
577                                        iMonthOfYear,
578                                        iDayOfMonth,
579                                        iDayOfWeek,
580                                        iAdvanceDayOfWeek,
581                                        iMillisOfDay);
582        }
583 
584        /**
585         * Adds a cutover to the builder.
586         */
587        public void addCutover(DateTimeZoneBuilder builder, int year) {
588            builder.addCutover(year,
589                               iZoneChar,
590                               iMonthOfYear,
591                               iDayOfMonth,
592                               iDayOfWeek,
593                               iAdvanceDayOfWeek,
594                               iMillisOfDay);
595        }
596 
597        public String toString() {
598            return
599                "MonthOfYear: " + iMonthOfYear + "\n" +
600                "DayOfMonth: " + iDayOfMonth + "\n" +
601                "DayOfWeek: " + iDayOfWeek + "\n" +
602                "AdvanceDayOfWeek: " + iAdvanceDayOfWeek + "\n" +
603                "MillisOfDay: " + iMillisOfDay + "\n" +
604                "ZoneChar: " + iZoneChar + "\n";
605        }
606    }
607 
608    private static class Rule {
609        public final String iName;
610        public final int iFromYear;
611        public final int iToYear;
612        public final String iType;
613        public final DateTimeOfYear iDateTimeOfYear;
614        public final int iSaveMillis;
615        public final String iLetterS;
616 
617        Rule(StringTokenizer st) {
618            iName = st.nextToken().intern();
619            iFromYear = parseYear(st.nextToken(), 0);
620            iToYear = parseYear(st.nextToken(), iFromYear);
621            if (iToYear < iFromYear) {
622                throw new IllegalArgumentException();
623            }
624            iType = parseOptional(st.nextToken());
625            iDateTimeOfYear = new DateTimeOfYear(st);
626            iSaveMillis = parseTime(st.nextToken());
627            iLetterS = parseOptional(st.nextToken());
628        }
629 
630        /**
631         * Adds a recurring savings rule to the builder.
632         */
633        public void addRecurring(DateTimeZoneBuilder builder, String nameFormat) {
634            String nameKey = formatName(nameFormat);
635            iDateTimeOfYear.addRecurring
636                (builder, nameKey, iSaveMillis, iFromYear, iToYear);
637        }
638 
639        private String formatName(String nameFormat) {
640            int index = nameFormat.indexOf('/');
641            if (index > 0) {
642                if (iSaveMillis == 0) {
643                    // Extract standard name.
644                    return nameFormat.substring(0, index).intern();
645                } else {
646                    return nameFormat.substring(index + 1).intern();
647                }
648            }
649            index = nameFormat.indexOf("%s");
650            if (index < 0) {
651                return nameFormat;
652            }
653            String left = nameFormat.substring(0, index);
654            String right = nameFormat.substring(index + 2);
655            String name;
656            if (iLetterS == null) {
657                name = left.concat(right);
658            } else {
659                name = left + iLetterS + right;
660            }
661            return name.intern();
662        }
663 
664        public String toString() {
665            return
666                "[Rule]\n" + 
667                "Name: " + iName + "\n" +
668                "FromYear: " + iFromYear + "\n" +
669                "ToYear: " + iToYear + "\n" +
670                "Type: " + iType + "\n" +
671                iDateTimeOfYear +
672                "SaveMillis: " + iSaveMillis + "\n" +
673                "LetterS: " + iLetterS + "\n";
674        }
675    }
676 
677    private static class RuleSet {
678        private List iRules;
679 
680        RuleSet(Rule rule) {
681            iRules = new ArrayList();
682            iRules.add(rule);
683        }
684 
685        void addRule(Rule rule) {
686            if (!(rule.iName.equals(((Rule)iRules.get(0)).iName))) {
687                throw new IllegalArgumentException("Rule name mismatch");
688            }
689            iRules.add(rule);
690        }
691 
692        /**
693         * Adds recurring savings rules to the builder.
694         */
695        public void addRecurring(DateTimeZoneBuilder builder, String nameFormat) {
696            for (int i=0; i<iRules.size(); i++) {
697                Rule rule = (Rule)iRules.get(i);
698                rule.addRecurring(builder, nameFormat);
699            }
700        }
701    }
702 
703    private static class Zone {
704        public final String iName;
705        public final int iOffsetMillis;
706        public final String iRules;
707        public final String iFormat;
708        public final int iUntilYear;
709        public final DateTimeOfYear iUntilDateTimeOfYear;
710 
711        private Zone iNext;
712 
713        Zone(StringTokenizer st) {
714            this(st.nextToken(), st);
715        }
716 
717        private Zone(String name, StringTokenizer st) {
718            iName = name.intern();
719            iOffsetMillis = parseTime(st.nextToken());
720            iRules = parseOptional(st.nextToken());
721            iFormat = st.nextToken().intern();
722 
723            int year = Integer.MAX_VALUE;
724            DateTimeOfYear dtOfYear = getStartOfYear();
725 
726            if (st.hasMoreTokens()) {
727                year = Integer.parseInt(st.nextToken());
728                if (st.hasMoreTokens()) {
729                    dtOfYear = new DateTimeOfYear(st);
730                }
731            }
732 
733            iUntilYear = year;
734            iUntilDateTimeOfYear = dtOfYear;
735        }
736 
737        void chain(StringTokenizer st) {
738            if (iNext != null) {
739                iNext.chain(st);
740            } else {
741                iNext = new Zone(iName, st);
742            }
743        }
744 
745        /*
746        public DateTimeZone buildDateTimeZone(Map ruleSets) {
747            DateTimeZoneBuilder builder = new DateTimeZoneBuilder();
748            addToBuilder(builder, ruleSets);
749            return builder.toDateTimeZone(iName);
750        }
751        */
752 
753        /**
754         * Adds zone info to the builder.
755         */
756        public void addToBuilder(DateTimeZoneBuilder builder, Map ruleSets) {
757            addToBuilder(this, builder, ruleSets);
758        }
759 
760        private static void addToBuilder(Zone zone,
761                                         DateTimeZoneBuilder builder,
762                                         Map ruleSets)
763        {
764            for (; zone != null; zone = zone.iNext) {
765                builder.setStandardOffset(zone.iOffsetMillis);
766 
767                if (zone.iRules == null) {
768                    builder.setFixedSavings(zone.iFormat, 0);
769                } else {
770                    try {
771                        // Check if iRules actually just refers to a savings.
772                        int saveMillis = parseTime(zone.iRules);
773                        builder.setFixedSavings(zone.iFormat, saveMillis);
774                    }
775                    catch (Exception e) {
776                        RuleSet rs = (RuleSet)ruleSets.get(zone.iRules);
777                        if (rs == null) {
778                            throw new IllegalArgumentException
779                                ("Rules not found: " + zone.iRules);
780                        }
781                        rs.addRecurring(builder, zone.iFormat);
782                    }
783                }
784 
785                if (zone.iUntilYear == Integer.MAX_VALUE) {
786                    break;
787                }
788 
789                zone.iUntilDateTimeOfYear.addCutover(builder, zone.iUntilYear);
790            }
791        }
792 
793        public String toString() {
794            String str =
795                "[Zone]\n" + 
796                "Name: " + iName + "\n" +
797                "OffsetMillis: " + iOffsetMillis + "\n" +
798                "Rules: " + iRules + "\n" +
799                "Format: " + iFormat + "\n" +
800                "UntilYear: " + iUntilYear + "\n" +
801                iUntilDateTimeOfYear;
802 
803            if (iNext == null) {
804                return str;
805            }
806 
807            return str + "...\n" + iNext.toString();
808        }
809    }
810}
811 

[all classes][org.joda.time.tz]
EMMA 2.0.5312 (C) Vladimir Roubtsov