Source Code Cross Referenced for DateTimeZoneBuilder.java in  » Development » Joda-Time » org » joda » time » tz » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Development » Joda Time » org.joda.time.tz 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         *  Copyright 2001-2005 Stephen Colebourne
0003:         *
0004:         *  Licensed under the Apache License, Version 2.0 (the "License");
0005:         *  you may not use this file except in compliance with the License.
0006:         *  You may obtain a copy of the License at
0007:         *
0008:         *      http://www.apache.org/licenses/LICENSE-2.0
0009:         *
0010:         *  Unless required by applicable law or agreed to in writing, software
0011:         *  distributed under the License is distributed on an "AS IS" BASIS,
0012:         *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013:         *  See the License for the specific language governing permissions and
0014:         *  limitations under the License.
0015:         */
0016:        package org.joda.time.tz;
0017:
0018:        import java.io.DataInput;
0019:        import java.io.DataInputStream;
0020:        import java.io.DataOutput;
0021:        import java.io.DataOutputStream;
0022:        import java.io.IOException;
0023:        import java.io.InputStream;
0024:        import java.io.OutputStream;
0025:        import java.text.DateFormatSymbols;
0026:        import java.util.ArrayList;
0027:        import java.util.Arrays;
0028:        import java.util.HashSet;
0029:        import java.util.Iterator;
0030:        import java.util.Locale;
0031:        import java.util.Set;
0032:
0033:        import org.joda.time.Chronology;
0034:        import org.joda.time.DateTime;
0035:        import org.joda.time.DateTimeUtils;
0036:        import org.joda.time.DateTimeZone;
0037:        import org.joda.time.Period;
0038:        import org.joda.time.PeriodType;
0039:        import org.joda.time.chrono.ISOChronology;
0040:
0041:        /**
0042:         * DateTimeZoneBuilder allows complex DateTimeZones to be constructed. Since
0043:         * creating a new DateTimeZone this way is a relatively expensive operation,
0044:         * built zones can be written to a file. Reading back the encoded data is a
0045:         * quick operation.
0046:         * <p>
0047:         * DateTimeZoneBuilder itself is mutable and not thread-safe, but the
0048:         * DateTimeZone objects that it builds are thread-safe and immutable.
0049:         * <p>
0050:         * It is intended that {@link ZoneInfoCompiler} be used to read time zone data
0051:         * files, indirectly calling DateTimeZoneBuilder. The following complex
0052:         * example defines the America/Los_Angeles time zone, with all historical
0053:         * transitions:
0054:         * 
0055:         * <pre>
0056:         * DateTimeZone America_Los_Angeles = new DateTimeZoneBuilder()
0057:         *     .addCutover(-2147483648, 'w', 1, 1, 0, false, 0)
0058:         *     .setStandardOffset(-28378000)
0059:         *     .setFixedSavings("LMT", 0)
0060:         *     .addCutover(1883, 'w', 11, 18, 0, false, 43200000)
0061:         *     .setStandardOffset(-28800000)
0062:         *     .addRecurringSavings("PDT", 3600000, 1918, 1919, 'w',  3, -1, 7, false, 7200000)
0063:         *     .addRecurringSavings("PST",       0, 1918, 1919, 'w', 10, -1, 7, false, 7200000)
0064:         *     .addRecurringSavings("PWT", 3600000, 1942, 1942, 'w',  2,  9, 0, false, 7200000)
0065:         *     .addRecurringSavings("PPT", 3600000, 1945, 1945, 'u',  8, 14, 0, false, 82800000)
0066:         *     .addRecurringSavings("PST",       0, 1945, 1945, 'w',  9, 30, 0, false, 7200000)
0067:         *     .addRecurringSavings("PDT", 3600000, 1948, 1948, 'w',  3, 14, 0, false, 7200000)
0068:         *     .addRecurringSavings("PST",       0, 1949, 1949, 'w',  1,  1, 0, false, 7200000)
0069:         *     .addRecurringSavings("PDT", 3600000, 1950, 1966, 'w',  4, -1, 7, false, 7200000)
0070:         *     .addRecurringSavings("PST",       0, 1950, 1961, 'w',  9, -1, 7, false, 7200000)
0071:         *     .addRecurringSavings("PST",       0, 1962, 1966, 'w', 10, -1, 7, false, 7200000)
0072:         *     .addRecurringSavings("PST",       0, 1967, 2147483647, 'w', 10, -1, 7, false, 7200000)
0073:         *     .addRecurringSavings("PDT", 3600000, 1967, 1973, 'w', 4, -1,  7, false, 7200000)
0074:         *     .addRecurringSavings("PDT", 3600000, 1974, 1974, 'w', 1,  6,  0, false, 7200000)
0075:         *     .addRecurringSavings("PDT", 3600000, 1975, 1975, 'w', 2, 23,  0, false, 7200000)
0076:         *     .addRecurringSavings("PDT", 3600000, 1976, 1986, 'w', 4, -1,  7, false, 7200000)
0077:         *     .addRecurringSavings("PDT", 3600000, 1987, 2147483647, 'w', 4, 1, 7, true, 7200000)
0078:         *     .toDateTimeZone("America/Los_Angeles");
0079:         * </pre>
0080:         *
0081:         * @author Brian S O'Neill
0082:         * @see ZoneInfoCompiler
0083:         * @see ZoneInfoProvider
0084:         * @since 1.0
0085:         */
0086:        public class DateTimeZoneBuilder {
0087:            /**
0088:             * Decodes a built DateTimeZone from the given stream, as encoded by
0089:             * writeTo.
0090:             *
0091:             * @param in input stream to read encoded DateTimeZone from.
0092:             * @param id time zone id to assign
0093:             */
0094:            public static DateTimeZone readFrom(InputStream in, String id)
0095:                    throws IOException {
0096:                if (in instanceof  DataInput) {
0097:                    return readFrom((DataInput) in, id);
0098:                } else {
0099:                    return readFrom((DataInput) new DataInputStream(in), id);
0100:                }
0101:            }
0102:
0103:            /**
0104:             * Decodes a built DateTimeZone from the given stream, as encoded by
0105:             * writeTo.
0106:             *
0107:             * @param in input stream to read encoded DateTimeZone from.
0108:             * @param id time zone id to assign
0109:             */
0110:            public static DateTimeZone readFrom(DataInput in, String id)
0111:                    throws IOException {
0112:                switch (in.readUnsignedByte()) {
0113:                case 'F':
0114:                    DateTimeZone fixed = new FixedDateTimeZone(id,
0115:                            in.readUTF(), (int) readMillis(in),
0116:                            (int) readMillis(in));
0117:                    if (fixed.equals(DateTimeZone.UTC)) {
0118:                        fixed = DateTimeZone.UTC;
0119:                    }
0120:                    return fixed;
0121:                case 'C':
0122:                    return CachedDateTimeZone.forZone(PrecalculatedZone
0123:                            .readFrom(in, id));
0124:                case 'P':
0125:                    return PrecalculatedZone.readFrom(in, id);
0126:                default:
0127:                    throw new IOException("Invalid encoding");
0128:                }
0129:            }
0130:
0131:            /**
0132:             * Millisecond encoding formats:
0133:             *
0134:             * upper two bits  units       field length  approximate range
0135:             * ---------------------------------------------------------------
0136:             * 00              30 minutes  1 byte        +/- 16 hours
0137:             * 01              minutes     4 bytes       +/- 1020 years
0138:             * 10              seconds     5 bytes       +/- 4355 years
0139:             * 11              millis      9 bytes       +/- 292,000,000 years
0140:             *
0141:             * Remaining bits in field form signed offset from 1970-01-01T00:00:00Z.
0142:             */
0143:            static void writeMillis(DataOutput out, long millis)
0144:                    throws IOException {
0145:                if (millis % (30 * 60000L) == 0) {
0146:                    // Try to write in 30 minute units.
0147:                    long units = millis / (30 * 60000L);
0148:                    if (((units << (64 - 6)) >> (64 - 6)) == units) {
0149:                        // Form 00 (6 bits effective precision)
0150:                        out.writeByte((int) (units & 0x3f));
0151:                        return;
0152:                    }
0153:                }
0154:
0155:                if (millis % 60000L == 0) {
0156:                    // Try to write minutes.
0157:                    long minutes = millis / 60000L;
0158:                    if (((minutes << (64 - 30)) >> (64 - 30)) == minutes) {
0159:                        // Form 01 (30 bits effective precision)
0160:                        out.writeInt(0x40000000 | (int) (minutes & 0x3fffffff));
0161:                        return;
0162:                    }
0163:                }
0164:
0165:                if (millis % 1000L == 0) {
0166:                    // Try to write seconds.
0167:                    long seconds = millis / 1000L;
0168:                    if (((seconds << (64 - 38)) >> (64 - 38)) == seconds) {
0169:                        // Form 10 (38 bits effective precision)
0170:                        out.writeByte(0x80 | (int) ((seconds >> 32) & 0x3f));
0171:                        out.writeInt((int) (seconds & 0xffffffff));
0172:                        return;
0173:                    }
0174:                }
0175:
0176:                // Write milliseconds either because the additional precision is
0177:                // required or the minutes didn't fit in the field.
0178:
0179:                // Form 11 (64 bits effective precision, but write as if 70 bits)
0180:                out.writeByte(millis < 0 ? 0xff : 0xc0);
0181:                out.writeLong(millis);
0182:            }
0183:
0184:            /**
0185:             * Reads encoding generated by writeMillis.
0186:             */
0187:            static long readMillis(DataInput in) throws IOException {
0188:                int v = in.readUnsignedByte();
0189:                switch (v >> 6) {
0190:                case 0:
0191:                default:
0192:                    // Form 00 (6 bits effective precision)
0193:                    v = (v << (32 - 6)) >> (32 - 6);
0194:                    return v * (30 * 60000L);
0195:
0196:                case 1:
0197:                    // Form 01 (30 bits effective precision)
0198:                    v = (v << (32 - 6)) >> (32 - 30);
0199:                    v |= (in.readUnsignedByte()) << 16;
0200:                    v |= (in.readUnsignedByte()) << 8;
0201:                    v |= (in.readUnsignedByte());
0202:                    return v * 60000L;
0203:
0204:                case 2:
0205:                    // Form 10 (38 bits effective precision)
0206:                    long w = (((long) v) << (64 - 6)) >> (64 - 38);
0207:                    w |= (in.readUnsignedByte()) << 24;
0208:                    w |= (in.readUnsignedByte()) << 16;
0209:                    w |= (in.readUnsignedByte()) << 8;
0210:                    w |= (in.readUnsignedByte());
0211:                    return w * 1000L;
0212:
0213:                case 3:
0214:                    // Form 11 (64 bits effective precision)
0215:                    return in.readLong();
0216:                }
0217:            }
0218:
0219:            private static DateTimeZone buildFixedZone(String id,
0220:                    String nameKey, int wallOffset, int standardOffset) {
0221:                if ("UTC".equals(id) && id.equals(nameKey) && wallOffset == 0
0222:                        && standardOffset == 0) {
0223:                    return DateTimeZone.UTC;
0224:                }
0225:                return new FixedDateTimeZone(id, nameKey, wallOffset,
0226:                        standardOffset);
0227:            }
0228:
0229:            // List of RuleSets.
0230:            private final ArrayList iRuleSets;
0231:
0232:            public DateTimeZoneBuilder() {
0233:                iRuleSets = new ArrayList(10);
0234:            }
0235:
0236:            /**
0237:             * Adds a cutover for added rules. The standard offset at the cutover
0238:             * defaults to 0. Call setStandardOffset afterwards to change it.
0239:             *
0240:             * @param year year of cutover
0241:             * @param mode 'u' - cutover is measured against UTC, 'w' - against wall
0242:             * offset, 's' - against standard offset.
0243:             * @param dayOfMonth if negative, set to ((last day of month) - ~dayOfMonth).
0244:             * For example, if -1, set to last day of month
0245:             * @param dayOfWeek if 0, ignore
0246:             * @param advanceDayOfWeek if dayOfMonth does not fall on dayOfWeek, advance to
0247:             * dayOfWeek when true, retreat when false.
0248:             * @param millisOfDay additional precision for specifying time of day of
0249:             * cutover
0250:             */
0251:            public DateTimeZoneBuilder addCutover(int year, char mode,
0252:                    int monthOfYear, int dayOfMonth, int dayOfWeek,
0253:                    boolean advanceDayOfWeek, int millisOfDay) {
0254:                OfYear ofYear = new OfYear(mode, monthOfYear, dayOfMonth,
0255:                        dayOfWeek, advanceDayOfWeek, millisOfDay);
0256:                if (iRuleSets.size() > 0) {
0257:                    RuleSet lastRuleSet = (RuleSet) iRuleSets.get(iRuleSets
0258:                            .size() - 1);
0259:                    lastRuleSet.setUpperLimit(year, ofYear);
0260:                }
0261:                iRuleSets.add(new RuleSet());
0262:                return this ;
0263:            }
0264:
0265:            /**
0266:             * Sets the standard offset to use for newly added rules until the next
0267:             * cutover is added.
0268:             */
0269:            public DateTimeZoneBuilder setStandardOffset(int standardOffset) {
0270:                getLastRuleSet().setStandardOffset(standardOffset);
0271:                return this ;
0272:            }
0273:
0274:            /**
0275:             * Set a fixed savings rule at the cutover.
0276:             */
0277:            public DateTimeZoneBuilder setFixedSavings(String nameKey,
0278:                    int saveMillis) {
0279:                getLastRuleSet().setFixedSavings(nameKey, saveMillis);
0280:                return this ;
0281:            }
0282:
0283:            /**
0284:             * Add a recurring daylight saving time rule.
0285:             *
0286:             * @param nameKey name key of new rule
0287:             * @param saveMillis milliseconds to add to standard offset
0288:             * @param fromYear First year that rule is in effect. MIN_VALUE indicates
0289:             * beginning of time.
0290:             * @param toYear Last year (inclusive) that rule is in effect. MAX_VALUE
0291:             * indicates end of time.
0292:             * @param mode 'u' - transitions are calculated against UTC, 'w' -
0293:             * transitions are calculated against wall offset, 's' - transitions are
0294:             * calculated against standard offset.
0295:             * @param dayOfMonth if negative, set to ((last day of month) - ~dayOfMonth).
0296:             * For example, if -1, set to last day of month
0297:             * @param dayOfWeek if 0, ignore
0298:             * @param advanceDayOfWeek if dayOfMonth does not fall on dayOfWeek, advance to
0299:             * dayOfWeek when true, retreat when false.
0300:             * @param millisOfDay additional precision for specifying time of day of
0301:             * transitions
0302:             */
0303:            public DateTimeZoneBuilder addRecurringSavings(String nameKey,
0304:                    int saveMillis, int fromYear, int toYear, char mode,
0305:                    int monthOfYear, int dayOfMonth, int dayOfWeek,
0306:                    boolean advanceDayOfWeek, int millisOfDay) {
0307:                if (fromYear <= toYear) {
0308:                    OfYear ofYear = new OfYear(mode, monthOfYear, dayOfMonth,
0309:                            dayOfWeek, advanceDayOfWeek, millisOfDay);
0310:                    Recurrence recurrence = new Recurrence(ofYear, nameKey,
0311:                            saveMillis);
0312:                    Rule rule = new Rule(recurrence, fromYear, toYear);
0313:                    getLastRuleSet().addRule(rule);
0314:                }
0315:                return this ;
0316:            }
0317:
0318:            private RuleSet getLastRuleSet() {
0319:                if (iRuleSets.size() == 0) {
0320:                    addCutover(Integer.MIN_VALUE, 'w', 1, 1, 0, false, 0);
0321:                }
0322:                return (RuleSet) iRuleSets.get(iRuleSets.size() - 1);
0323:            }
0324:
0325:            /**
0326:             * Processes all the rules and builds a DateTimeZone.
0327:             *
0328:             * @param id  time zone id to assign
0329:             * @param outputID  true if the zone id should be output
0330:             */
0331:            public DateTimeZone toDateTimeZone(String id, boolean outputID) {
0332:                if (id == null) {
0333:                    throw new IllegalArgumentException();
0334:                }
0335:
0336:                // Discover where all the transitions occur and store the results in
0337:                // these lists.
0338:                ArrayList transitions = new ArrayList();
0339:
0340:                // Tail zone picks up remaining transitions in the form of an endless
0341:                // DST cycle.
0342:                DSTZone tailZone = null;
0343:
0344:                long millis = Long.MIN_VALUE;
0345:                int saveMillis = 0;
0346:
0347:                int ruleSetCount = iRuleSets.size();
0348:                for (int i = 0; i < ruleSetCount; i++) {
0349:                    RuleSet rs = (RuleSet) iRuleSets.get(i);
0350:                    Transition next = rs.firstTransition(millis);
0351:                    if (next == null) {
0352:                        continue;
0353:                    }
0354:                    addTransition(transitions, next);
0355:                    millis = next.getMillis();
0356:                    saveMillis = next.getSaveMillis();
0357:
0358:                    // Copy it since we're going to destroy it.
0359:                    rs = new RuleSet(rs);
0360:
0361:                    while ((next = rs.nextTransition(millis, saveMillis)) != null) {
0362:                        if (addTransition(transitions, next)) {
0363:                            if (tailZone != null) {
0364:                                // Got the extra transition before DSTZone.
0365:                                break;
0366:                            }
0367:                        }
0368:                        millis = next.getMillis();
0369:                        saveMillis = next.getSaveMillis();
0370:                        if (tailZone == null && i == ruleSetCount - 1) {
0371:                            tailZone = rs.buildTailZone(id);
0372:                            // If tailZone is not null, don't break out of main loop until
0373:                            // at least one more transition is calculated. This ensures a
0374:                            // correct 'seam' to the DSTZone.
0375:                        }
0376:                    }
0377:
0378:                    millis = rs.getUpperLimit(saveMillis);
0379:                }
0380:
0381:                // Check if a simpler zone implementation can be returned.
0382:                if (transitions.size() == 0) {
0383:                    if (tailZone != null) {
0384:                        // This shouldn't happen, but handle just in case.
0385:                        return tailZone;
0386:                    }
0387:                    return buildFixedZone(id, "UTC", 0, 0);
0388:                }
0389:                if (transitions.size() == 1 && tailZone == null) {
0390:                    Transition tr = (Transition) transitions.get(0);
0391:                    return buildFixedZone(id, tr.getNameKey(), tr
0392:                            .getWallOffset(), tr.getStandardOffset());
0393:                }
0394:
0395:                PrecalculatedZone zone = PrecalculatedZone.create(id, outputID,
0396:                        transitions, tailZone);
0397:                if (zone.isCachable()) {
0398:                    return CachedDateTimeZone.forZone(zone);
0399:                }
0400:                return zone;
0401:            }
0402:
0403:            private boolean addTransition(ArrayList transitions, Transition tr) {
0404:                int size = transitions.size();
0405:                if (size == 0) {
0406:                    transitions.add(tr);
0407:                    return true;
0408:                }
0409:
0410:                Transition last = (Transition) transitions.get(size - 1);
0411:                if (!tr.isTransitionFrom(last)) {
0412:                    return false;
0413:                }
0414:
0415:                // If local time of new transition is same as last local time, just
0416:                // replace last transition with new one.
0417:                int offsetForLast = 0;
0418:                if (size >= 2) {
0419:                    offsetForLast = ((Transition) transitions.get(size - 2))
0420:                            .getWallOffset();
0421:                }
0422:                int offsetForNew = last.getWallOffset();
0423:
0424:                long lastLocal = last.getMillis() + offsetForLast;
0425:                long newLocal = tr.getMillis() + offsetForNew;
0426:
0427:                if (newLocal != lastLocal) {
0428:                    transitions.add(tr);
0429:                    return true;
0430:                }
0431:
0432:                transitions.remove(size - 1);
0433:                return addTransition(transitions, tr);
0434:            }
0435:
0436:            /**
0437:             * Encodes a built DateTimeZone to the given stream. Call readFrom to
0438:             * decode the data into a DateTimeZone object.
0439:             *
0440:             * @param out output stream to receive encoded DateTimeZone.
0441:             * @since 1.5 (parameter added)
0442:             */
0443:            public void writeTo(String zoneID, OutputStream out)
0444:                    throws IOException {
0445:                if (out instanceof  DataOutput) {
0446:                    writeTo(zoneID, (DataOutput) out);
0447:                } else {
0448:                    writeTo(zoneID, (DataOutput) new DataOutputStream(out));
0449:                }
0450:            }
0451:
0452:            /**
0453:             * Encodes a built DateTimeZone to the given stream. Call readFrom to
0454:             * decode the data into a DateTimeZone object.
0455:             *
0456:             * @param out output stream to receive encoded DateTimeZone.
0457:             * @since 1.5 (parameter added)
0458:             */
0459:            public void writeTo(String zoneID, DataOutput out)
0460:                    throws IOException {
0461:                // pass false so zone id is not written out
0462:                DateTimeZone zone = toDateTimeZone(zoneID, false);
0463:
0464:                if (zone instanceof  FixedDateTimeZone) {
0465:                    out.writeByte('F'); // 'F' for fixed
0466:                    out.writeUTF(zone.getNameKey(0));
0467:                    writeMillis(out, zone.getOffset(0));
0468:                    writeMillis(out, zone.getStandardOffset(0));
0469:                } else {
0470:                    if (zone instanceof  CachedDateTimeZone) {
0471:                        out.writeByte('C'); // 'C' for cached, precalculated
0472:                        zone = ((CachedDateTimeZone) zone).getUncachedZone();
0473:                    } else {
0474:                        out.writeByte('P'); // 'P' for precalculated, uncached
0475:                    }
0476:                    ((PrecalculatedZone) zone).writeTo(out);
0477:                }
0478:            }
0479:
0480:            /**
0481:             * Supports setting fields of year and moving between transitions.
0482:             */
0483:            private static final class OfYear {
0484:                static OfYear readFrom(DataInput in) throws IOException {
0485:                    return new OfYear((char) in.readUnsignedByte(), (int) in
0486:                            .readUnsignedByte(), (int) in.readByte(), (int) in
0487:                            .readUnsignedByte(), in.readBoolean(),
0488:                            (int) readMillis(in));
0489:                }
0490:
0491:                // Is 'u', 'w', or 's'.
0492:                final char iMode;
0493:
0494:                final int iMonthOfYear;
0495:                final int iDayOfMonth;
0496:                final int iDayOfWeek;
0497:                final boolean iAdvance;
0498:                final int iMillisOfDay;
0499:
0500:                OfYear(char mode, int monthOfYear, int dayOfMonth,
0501:                        int dayOfWeek, boolean advanceDayOfWeek, int millisOfDay) {
0502:                    if (mode != 'u' && mode != 'w' && mode != 's') {
0503:                        throw new IllegalArgumentException("Unknown mode: "
0504:                                + mode);
0505:                    }
0506:
0507:                    iMode = mode;
0508:                    iMonthOfYear = monthOfYear;
0509:                    iDayOfMonth = dayOfMonth;
0510:                    iDayOfWeek = dayOfWeek;
0511:                    iAdvance = advanceDayOfWeek;
0512:                    iMillisOfDay = millisOfDay;
0513:                }
0514:
0515:                /**
0516:                 * @param standardOffset standard offset just before instant
0517:                 */
0518:                public long setInstant(int year, int standardOffset,
0519:                        int saveMillis) {
0520:                    int offset;
0521:                    if (iMode == 'w') {
0522:                        offset = standardOffset + saveMillis;
0523:                    } else if (iMode == 's') {
0524:                        offset = standardOffset;
0525:                    } else {
0526:                        offset = 0;
0527:                    }
0528:
0529:                    Chronology chrono = ISOChronology.getInstanceUTC();
0530:                    long millis = chrono.year().set(0, year);
0531:                    millis = chrono.monthOfYear().set(millis, iMonthOfYear);
0532:                    millis = chrono.millisOfDay().set(millis, iMillisOfDay);
0533:                    millis = setDayOfMonth(chrono, millis);
0534:
0535:                    if (iDayOfWeek != 0) {
0536:                        millis = setDayOfWeek(chrono, millis);
0537:                    }
0538:
0539:                    // Convert from local time to UTC.
0540:                    return millis - offset;
0541:                }
0542:
0543:                /**
0544:                 * @param standardOffset standard offset just before next recurrence
0545:                 */
0546:                public long next(long instant, int standardOffset,
0547:                        int saveMillis) {
0548:                    int offset;
0549:                    if (iMode == 'w') {
0550:                        offset = standardOffset + saveMillis;
0551:                    } else if (iMode == 's') {
0552:                        offset = standardOffset;
0553:                    } else {
0554:                        offset = 0;
0555:                    }
0556:
0557:                    // Convert from UTC to local time.
0558:                    instant += offset;
0559:
0560:                    Chronology chrono = ISOChronology.getInstanceUTC();
0561:                    long next = chrono.monthOfYear().set(instant, iMonthOfYear);
0562:                    // Be lenient with millisOfDay.
0563:                    next = chrono.millisOfDay().set(next, 0);
0564:                    next = chrono.millisOfDay().add(next, iMillisOfDay);
0565:                    next = setDayOfMonthNext(chrono, next);
0566:
0567:                    if (iDayOfWeek == 0) {
0568:                        if (next <= instant) {
0569:                            next = chrono.year().add(next, 1);
0570:                            next = setDayOfMonthNext(chrono, next);
0571:                        }
0572:                    } else {
0573:                        next = setDayOfWeek(chrono, next);
0574:                        if (next <= instant) {
0575:                            next = chrono.year().add(next, 1);
0576:                            next = chrono.monthOfYear().set(next, iMonthOfYear);
0577:                            next = setDayOfMonthNext(chrono, next);
0578:                            next = setDayOfWeek(chrono, next);
0579:                        }
0580:                    }
0581:
0582:                    // Convert from local time to UTC.
0583:                    return next - offset;
0584:                }
0585:
0586:                /**
0587:                 * @param standardOffset standard offset just before previous recurrence
0588:                 */
0589:                public long previous(long instant, int standardOffset,
0590:                        int saveMillis) {
0591:                    int offset;
0592:                    if (iMode == 'w') {
0593:                        offset = standardOffset + saveMillis;
0594:                    } else if (iMode == 's') {
0595:                        offset = standardOffset;
0596:                    } else {
0597:                        offset = 0;
0598:                    }
0599:
0600:                    // Convert from UTC to local time.
0601:                    instant += offset;
0602:
0603:                    Chronology chrono = ISOChronology.getInstanceUTC();
0604:                    long prev = chrono.monthOfYear().set(instant, iMonthOfYear);
0605:                    // Be lenient with millisOfDay.
0606:                    prev = chrono.millisOfDay().set(prev, 0);
0607:                    prev = chrono.millisOfDay().add(prev, iMillisOfDay);
0608:                    prev = setDayOfMonthPrevious(chrono, prev);
0609:
0610:                    if (iDayOfWeek == 0) {
0611:                        if (prev >= instant) {
0612:                            prev = chrono.year().add(prev, -1);
0613:                            prev = setDayOfMonthPrevious(chrono, prev);
0614:                        }
0615:                    } else {
0616:                        prev = setDayOfWeek(chrono, prev);
0617:                        if (prev >= instant) {
0618:                            prev = chrono.year().add(prev, -1);
0619:                            prev = chrono.monthOfYear().set(prev, iMonthOfYear);
0620:                            prev = setDayOfMonthPrevious(chrono, prev);
0621:                            prev = setDayOfWeek(chrono, prev);
0622:                        }
0623:                    }
0624:
0625:                    // Convert from local time to UTC.
0626:                    return prev - offset;
0627:                }
0628:
0629:                public boolean equals(Object obj) {
0630:                    if (this  == obj) {
0631:                        return true;
0632:                    }
0633:                    if (obj instanceof  OfYear) {
0634:                        OfYear other = (OfYear) obj;
0635:                        return iMode == other.iMode
0636:                                && iMonthOfYear == other.iMonthOfYear
0637:                                && iDayOfMonth == other.iDayOfMonth
0638:                                && iDayOfWeek == other.iDayOfWeek
0639:                                && iAdvance == other.iAdvance
0640:                                && iMillisOfDay == other.iMillisOfDay;
0641:                    }
0642:                    return false;
0643:                }
0644:
0645:                /*
0646:                public String toString() {
0647:                    return
0648:                        "[OfYear]\n" + 
0649:                        "Mode: " + iMode + '\n' +
0650:                        "MonthOfYear: " + iMonthOfYear + '\n' +
0651:                        "DayOfMonth: " + iDayOfMonth + '\n' +
0652:                        "DayOfWeek: " + iDayOfWeek + '\n' +
0653:                        "AdvanceDayOfWeek: " + iAdvance + '\n' +
0654:                        "MillisOfDay: " + iMillisOfDay + '\n';
0655:                }
0656:                 */
0657:
0658:                public void writeTo(DataOutput out) throws IOException {
0659:                    out.writeByte(iMode);
0660:                    out.writeByte(iMonthOfYear);
0661:                    out.writeByte(iDayOfMonth);
0662:                    out.writeByte(iDayOfWeek);
0663:                    out.writeBoolean(iAdvance);
0664:                    writeMillis(out, iMillisOfDay);
0665:                }
0666:
0667:                /**
0668:                 * If month-day is 02-29 and year isn't leap, advances to next leap year.
0669:                 */
0670:                private long setDayOfMonthNext(Chronology chrono, long next) {
0671:                    try {
0672:                        next = setDayOfMonth(chrono, next);
0673:                    } catch (IllegalArgumentException e) {
0674:                        if (iMonthOfYear == 2 && iDayOfMonth == 29) {
0675:                            while (chrono.year().isLeap(next) == false) {
0676:                                next = chrono.year().add(next, 1);
0677:                            }
0678:                            next = setDayOfMonth(chrono, next);
0679:                        } else {
0680:                            throw e;
0681:                        }
0682:                    }
0683:                    return next;
0684:                }
0685:
0686:                /**
0687:                 * If month-day is 02-29 and year isn't leap, retreats to previous leap year.
0688:                 */
0689:                private long setDayOfMonthPrevious(Chronology chrono, long prev) {
0690:                    try {
0691:                        prev = setDayOfMonth(chrono, prev);
0692:                    } catch (IllegalArgumentException e) {
0693:                        if (iMonthOfYear == 2 && iDayOfMonth == 29) {
0694:                            while (chrono.year().isLeap(prev) == false) {
0695:                                prev = chrono.year().add(prev, -1);
0696:                            }
0697:                            prev = setDayOfMonth(chrono, prev);
0698:                        } else {
0699:                            throw e;
0700:                        }
0701:                    }
0702:                    return prev;
0703:                }
0704:
0705:                private long setDayOfMonth(Chronology chrono, long instant) {
0706:                    if (iDayOfMonth >= 0) {
0707:                        instant = chrono.dayOfMonth().set(instant, iDayOfMonth);
0708:                    } else {
0709:                        instant = chrono.dayOfMonth().set(instant, 1);
0710:                        instant = chrono.monthOfYear().add(instant, 1);
0711:                        instant = chrono.dayOfMonth().add(instant, iDayOfMonth);
0712:                    }
0713:                    return instant;
0714:                }
0715:
0716:                private long setDayOfWeek(Chronology chrono, long instant) {
0717:                    int dayOfWeek = chrono.dayOfWeek().get(instant);
0718:                    int daysToAdd = iDayOfWeek - dayOfWeek;
0719:                    if (daysToAdd != 0) {
0720:                        if (iAdvance) {
0721:                            if (daysToAdd < 0) {
0722:                                daysToAdd += 7;
0723:                            }
0724:                        } else {
0725:                            if (daysToAdd > 0) {
0726:                                daysToAdd -= 7;
0727:                            }
0728:                        }
0729:                        instant = chrono.dayOfWeek().add(instant, daysToAdd);
0730:                    }
0731:                    return instant;
0732:                }
0733:            }
0734:
0735:            /**
0736:             * Extends OfYear with a nameKey and savings.
0737:             */
0738:            private static final class Recurrence {
0739:                static Recurrence readFrom(DataInput in) throws IOException {
0740:                    return new Recurrence(OfYear.readFrom(in), in.readUTF(),
0741:                            (int) readMillis(in));
0742:                }
0743:
0744:                final OfYear iOfYear;
0745:                final String iNameKey;
0746:                final int iSaveMillis;
0747:
0748:                Recurrence(OfYear ofYear, String nameKey, int saveMillis) {
0749:                    iOfYear = ofYear;
0750:                    iNameKey = nameKey;
0751:                    iSaveMillis = saveMillis;
0752:                }
0753:
0754:                public OfYear getOfYear() {
0755:                    return iOfYear;
0756:                }
0757:
0758:                /**
0759:                 * @param standardOffset standard offset just before next recurrence
0760:                 */
0761:                public long next(long instant, int standardOffset,
0762:                        int saveMillis) {
0763:                    return iOfYear.next(instant, standardOffset, saveMillis);
0764:                }
0765:
0766:                /**
0767:                 * @param standardOffset standard offset just before previous recurrence
0768:                 */
0769:                public long previous(long instant, int standardOffset,
0770:                        int saveMillis) {
0771:                    return iOfYear
0772:                            .previous(instant, standardOffset, saveMillis);
0773:                }
0774:
0775:                public String getNameKey() {
0776:                    return iNameKey;
0777:                }
0778:
0779:                public int getSaveMillis() {
0780:                    return iSaveMillis;
0781:                }
0782:
0783:                public boolean equals(Object obj) {
0784:                    if (this  == obj) {
0785:                        return true;
0786:                    }
0787:                    if (obj instanceof  Recurrence) {
0788:                        Recurrence other = (Recurrence) obj;
0789:                        return iSaveMillis == other.iSaveMillis
0790:                                && iNameKey.equals(other.iNameKey)
0791:                                && iOfYear.equals(other.iOfYear);
0792:                    }
0793:                    return false;
0794:                }
0795:
0796:                public void writeTo(DataOutput out) throws IOException {
0797:                    iOfYear.writeTo(out);
0798:                    out.writeUTF(iNameKey);
0799:                    writeMillis(out, iSaveMillis);
0800:                }
0801:
0802:                Recurrence rename(String nameKey) {
0803:                    return new Recurrence(iOfYear, nameKey, iSaveMillis);
0804:                }
0805:
0806:                Recurrence renameAppend(String appendNameKey) {
0807:                    return rename((iNameKey + appendNameKey).intern());
0808:                }
0809:            }
0810:
0811:            /**
0812:             * Extends Recurrence with inclusive year limits.
0813:             */
0814:            private static final class Rule {
0815:                final Recurrence iRecurrence;
0816:                final int iFromYear; // inclusive
0817:                final int iToYear; // inclusive
0818:
0819:                Rule(Recurrence recurrence, int fromYear, int toYear) {
0820:                    iRecurrence = recurrence;
0821:                    iFromYear = fromYear;
0822:                    iToYear = toYear;
0823:                }
0824:
0825:                public int getFromYear() {
0826:                    return iFromYear;
0827:                }
0828:
0829:                public int getToYear() {
0830:                    return iToYear;
0831:                }
0832:
0833:                public OfYear getOfYear() {
0834:                    return iRecurrence.getOfYear();
0835:                }
0836:
0837:                public String getNameKey() {
0838:                    return iRecurrence.getNameKey();
0839:                }
0840:
0841:                public int getSaveMillis() {
0842:                    return iRecurrence.getSaveMillis();
0843:                }
0844:
0845:                public long next(final long instant, int standardOffset,
0846:                        int saveMillis) {
0847:                    Chronology chrono = ISOChronology.getInstanceUTC();
0848:
0849:                    final int wallOffset = standardOffset + saveMillis;
0850:                    long testInstant = instant;
0851:
0852:                    int year;
0853:                    if (instant == Long.MIN_VALUE) {
0854:                        year = Integer.MIN_VALUE;
0855:                    } else {
0856:                        year = chrono.year().get(instant + wallOffset);
0857:                    }
0858:
0859:                    if (year < iFromYear) {
0860:                        // First advance instant to start of from year.
0861:                        testInstant = chrono.year().set(0, iFromYear)
0862:                                - wallOffset;
0863:                        // Back off one millisecond to account for next recurrence
0864:                        // being exactly at the beginning of the year.
0865:                        testInstant -= 1;
0866:                    }
0867:
0868:                    long next = iRecurrence.next(testInstant, standardOffset,
0869:                            saveMillis);
0870:
0871:                    if (next > instant) {
0872:                        year = chrono.year().get(next + wallOffset);
0873:                        if (year > iToYear) {
0874:                            // Out of range, return original value.
0875:                            next = instant;
0876:                        }
0877:                    }
0878:
0879:                    return next;
0880:                }
0881:            }
0882:
0883:            private static final class Transition {
0884:                private final long iMillis;
0885:                private final String iNameKey;
0886:                private final int iWallOffset;
0887:                private final int iStandardOffset;
0888:
0889:                Transition(long millis, Transition tr) {
0890:                    iMillis = millis;
0891:                    iNameKey = tr.iNameKey;
0892:                    iWallOffset = tr.iWallOffset;
0893:                    iStandardOffset = tr.iStandardOffset;
0894:                }
0895:
0896:                Transition(long millis, Rule rule, int standardOffset) {
0897:                    iMillis = millis;
0898:                    iNameKey = rule.getNameKey();
0899:                    iWallOffset = standardOffset + rule.getSaveMillis();
0900:                    iStandardOffset = standardOffset;
0901:                }
0902:
0903:                Transition(long millis, String nameKey, int wallOffset,
0904:                        int standardOffset) {
0905:                    iMillis = millis;
0906:                    iNameKey = nameKey;
0907:                    iWallOffset = wallOffset;
0908:                    iStandardOffset = standardOffset;
0909:                }
0910:
0911:                public long getMillis() {
0912:                    return iMillis;
0913:                }
0914:
0915:                public String getNameKey() {
0916:                    return iNameKey;
0917:                }
0918:
0919:                public int getWallOffset() {
0920:                    return iWallOffset;
0921:                }
0922:
0923:                public int getStandardOffset() {
0924:                    return iStandardOffset;
0925:                }
0926:
0927:                public int getSaveMillis() {
0928:                    return iWallOffset - iStandardOffset;
0929:                }
0930:
0931:                /**
0932:                 * There must be a change in the millis, wall offsets or name keys.
0933:                 */
0934:                public boolean isTransitionFrom(Transition other) {
0935:                    if (other == null) {
0936:                        return true;
0937:                    }
0938:                    return iMillis > other.iMillis
0939:                            && (iWallOffset != other.iWallOffset ||
0940:                            //iStandardOffset != other.iStandardOffset ||
0941:                            !(iNameKey.equals(other.iNameKey)));
0942:                }
0943:            }
0944:
0945:            private static final class RuleSet {
0946:                private static final int YEAR_LIMIT;
0947:
0948:                static {
0949:                    // Don't pre-calculate more than 100 years into the future. Almost
0950:                    // all zones will stop pre-calculating far sooner anyhow. Either a
0951:                    // simple DST cycle is detected or the last rule is a fixed
0952:                    // offset. If a zone has a fixed offset set more than 100 years
0953:                    // into the future, then it won't be observed.
0954:                    long now = DateTimeUtils.currentTimeMillis();
0955:                    YEAR_LIMIT = ISOChronology.getInstanceUTC().year().get(now) + 100;
0956:                }
0957:
0958:                private int iStandardOffset;
0959:                private ArrayList iRules;
0960:
0961:                // Optional.
0962:                private String iInitialNameKey;
0963:                private int iInitialSaveMillis;
0964:
0965:                // Upper limit is exclusive.
0966:                private int iUpperYear;
0967:                private OfYear iUpperOfYear;
0968:
0969:                RuleSet() {
0970:                    iRules = new ArrayList(10);
0971:                    iUpperYear = Integer.MAX_VALUE;
0972:                }
0973:
0974:                /**
0975:                 * Copy constructor.
0976:                 */
0977:                RuleSet(RuleSet rs) {
0978:                    iStandardOffset = rs.iStandardOffset;
0979:                    iRules = new ArrayList(rs.iRules);
0980:                    iInitialNameKey = rs.iInitialNameKey;
0981:                    iInitialSaveMillis = rs.iInitialSaveMillis;
0982:                    iUpperYear = rs.iUpperYear;
0983:                    iUpperOfYear = rs.iUpperOfYear;
0984:                }
0985:
0986:                public int getStandardOffset() {
0987:                    return iStandardOffset;
0988:                }
0989:
0990:                public void setStandardOffset(int standardOffset) {
0991:                    iStandardOffset = standardOffset;
0992:                }
0993:
0994:                public void setFixedSavings(String nameKey, int saveMillis) {
0995:                    iInitialNameKey = nameKey;
0996:                    iInitialSaveMillis = saveMillis;
0997:                }
0998:
0999:                public void addRule(Rule rule) {
1000:                    if (!iRules.contains(rule)) {
1001:                        iRules.add(rule);
1002:                    }
1003:                }
1004:
1005:                public void setUpperLimit(int year, OfYear ofYear) {
1006:                    iUpperYear = year;
1007:                    iUpperOfYear = ofYear;
1008:                }
1009:
1010:                /**
1011:                 * Returns a transition at firstMillis with the first name key and
1012:                 * offsets for this rule set. This method may return null.
1013:                 *
1014:                 * @param firstMillis millis of first transition
1015:                 */
1016:                public Transition firstTransition(final long firstMillis) {
1017:                    if (iInitialNameKey != null) {
1018:                        // Initial zone info explicitly set, so don't search the rules.
1019:                        return new Transition(firstMillis, iInitialNameKey,
1020:                                iStandardOffset + iInitialSaveMillis,
1021:                                iStandardOffset);
1022:                    }
1023:
1024:                    // Make a copy before we destroy the rules.
1025:                    ArrayList copy = new ArrayList(iRules);
1026:
1027:                    // Iterate through all the transitions until firstMillis is
1028:                    // reached. Use the name key and savings for whatever rule reaches
1029:                    // the limit.
1030:
1031:                    long millis = Long.MIN_VALUE;
1032:                    int saveMillis = 0;
1033:                    Transition first = null;
1034:
1035:                    Transition next;
1036:                    while ((next = nextTransition(millis, saveMillis)) != null) {
1037:                        millis = next.getMillis();
1038:
1039:                        if (millis == firstMillis) {
1040:                            first = new Transition(firstMillis, next);
1041:                            break;
1042:                        }
1043:
1044:                        if (millis > firstMillis) {
1045:                            if (first == null) {
1046:                                // Find first rule without savings. This way a more
1047:                                // accurate nameKey is found even though no rule
1048:                                // extends to the RuleSet's lower limit.
1049:                                Iterator it = copy.iterator();
1050:                                while (it.hasNext()) {
1051:                                    Rule rule = (Rule) it.next();
1052:                                    if (rule.getSaveMillis() == 0) {
1053:                                        first = new Transition(firstMillis,
1054:                                                rule, iStandardOffset);
1055:                                        break;
1056:                                    }
1057:                                }
1058:                            }
1059:                            if (first == null) {
1060:                                // Found no rule without savings. Create a transition
1061:                                // with no savings anyhow, and use the best available
1062:                                // name key.
1063:                                first = new Transition(firstMillis, next
1064:                                        .getNameKey(), iStandardOffset,
1065:                                        iStandardOffset);
1066:                            }
1067:                            break;
1068:                        }
1069:
1070:                        // Set first to the best transition found so far, but next
1071:                        // iteration may find something closer to lower limit.
1072:                        first = new Transition(firstMillis, next);
1073:
1074:                        saveMillis = next.getSaveMillis();
1075:                    }
1076:
1077:                    iRules = copy;
1078:                    return first;
1079:                }
1080:
1081:                /**
1082:                 * Returns null if RuleSet is exhausted or upper limit reached. Calling
1083:                 * this method will throw away rules as they each become
1084:                 * exhausted. Copy the RuleSet before using it to compute transitions.
1085:                 *
1086:                 * Returned transition may be a duplicate from previous
1087:                 * transition. Caller must call isTransitionFrom to filter out
1088:                 * duplicates.
1089:                 *
1090:                 * @param saveMillis savings before next transition
1091:                 */
1092:                public Transition nextTransition(final long instant,
1093:                        final int saveMillis) {
1094:                    Chronology chrono = ISOChronology.getInstanceUTC();
1095:
1096:                    // Find next matching rule.
1097:                    Rule nextRule = null;
1098:                    long nextMillis = Long.MAX_VALUE;
1099:
1100:                    Iterator it = iRules.iterator();
1101:                    while (it.hasNext()) {
1102:                        Rule rule = (Rule) it.next();
1103:                        long next = rule.next(instant, iStandardOffset,
1104:                                saveMillis);
1105:                        if (next <= instant) {
1106:                            it.remove();
1107:                            continue;
1108:                        }
1109:                        // Even if next is same as previous next, choose the rule
1110:                        // in order for more recently added rules to override.
1111:                        if (next <= nextMillis) {
1112:                            // Found a better match.
1113:                            nextRule = rule;
1114:                            nextMillis = next;
1115:                        }
1116:                    }
1117:
1118:                    if (nextRule == null) {
1119:                        return null;
1120:                    }
1121:
1122:                    // Stop precalculating if year reaches some arbitrary limit.
1123:                    if (chrono.year().get(nextMillis) >= YEAR_LIMIT) {
1124:                        return null;
1125:                    }
1126:
1127:                    // Check if upper limit reached or passed.
1128:                    if (iUpperYear < Integer.MAX_VALUE) {
1129:                        long upperMillis = iUpperOfYear.setInstant(iUpperYear,
1130:                                iStandardOffset, saveMillis);
1131:                        if (nextMillis >= upperMillis) {
1132:                            // At or after upper limit.
1133:                            return null;
1134:                        }
1135:                    }
1136:
1137:                    return new Transition(nextMillis, nextRule, iStandardOffset);
1138:                }
1139:
1140:                /**
1141:                 * @param saveMillis savings before upper limit
1142:                 */
1143:                public long getUpperLimit(int saveMillis) {
1144:                    if (iUpperYear == Integer.MAX_VALUE) {
1145:                        return Long.MAX_VALUE;
1146:                    }
1147:                    return iUpperOfYear.setInstant(iUpperYear, iStandardOffset,
1148:                            saveMillis);
1149:                }
1150:
1151:                /**
1152:                 * Returns null if none can be built.
1153:                 */
1154:                public DSTZone buildTailZone(String id) {
1155:                    if (iRules.size() == 2) {
1156:                        Rule startRule = (Rule) iRules.get(0);
1157:                        Rule endRule = (Rule) iRules.get(1);
1158:                        if (startRule.getToYear() == Integer.MAX_VALUE
1159:                                && endRule.getToYear() == Integer.MAX_VALUE) {
1160:
1161:                            // With exactly two infinitely recurring rules left, a
1162:                            // simple DSTZone can be formed.
1163:
1164:                            // The order of rules can come in any order, and it doesn't
1165:                            // really matter which rule was chosen the 'start' and
1166:                            // which is chosen the 'end'. DSTZone works properly either
1167:                            // way.
1168:                            return new DSTZone(id, iStandardOffset,
1169:                                    startRule.iRecurrence, endRule.iRecurrence);
1170:                        }
1171:                    }
1172:                    return null;
1173:                }
1174:            }
1175:
1176:            private static final class DSTZone extends DateTimeZone {
1177:                private static final long serialVersionUID = 6941492635554961361L;
1178:
1179:                static DSTZone readFrom(DataInput in, String id)
1180:                        throws IOException {
1181:                    return new DSTZone(id, (int) readMillis(in), Recurrence
1182:                            .readFrom(in), Recurrence.readFrom(in));
1183:                }
1184:
1185:                final int iStandardOffset;
1186:                final Recurrence iStartRecurrence;
1187:                final Recurrence iEndRecurrence;
1188:
1189:                DSTZone(String id, int standardOffset,
1190:                        Recurrence startRecurrence, Recurrence endRecurrence) {
1191:                    super (id);
1192:                    iStandardOffset = standardOffset;
1193:                    iStartRecurrence = startRecurrence;
1194:                    iEndRecurrence = endRecurrence;
1195:                }
1196:
1197:                public String getNameKey(long instant) {
1198:                    return findMatchingRecurrence(instant).getNameKey();
1199:                }
1200:
1201:                public int getOffset(long instant) {
1202:                    return iStandardOffset
1203:                            + findMatchingRecurrence(instant).getSaveMillis();
1204:                }
1205:
1206:                public int getStandardOffset(long instant) {
1207:                    return iStandardOffset;
1208:                }
1209:
1210:                public boolean isFixed() {
1211:                    return false;
1212:                }
1213:
1214:                public long nextTransition(long instant) {
1215:                    int standardOffset = iStandardOffset;
1216:                    Recurrence startRecurrence = iStartRecurrence;
1217:                    Recurrence endRecurrence = iEndRecurrence;
1218:
1219:                    long start, end;
1220:
1221:                    try {
1222:                        start = startRecurrence.next(instant, standardOffset,
1223:                                endRecurrence.getSaveMillis());
1224:                        if (instant > 0 && start < 0) {
1225:                            // Overflowed.
1226:                            start = instant;
1227:                        }
1228:                    } catch (IllegalArgumentException e) {
1229:                        // Overflowed.
1230:                        start = instant;
1231:                    } catch (ArithmeticException e) {
1232:                        // Overflowed.
1233:                        start = instant;
1234:                    }
1235:
1236:                    try {
1237:                        end = endRecurrence.next(instant, standardOffset,
1238:                                startRecurrence.getSaveMillis());
1239:                        if (instant > 0 && end < 0) {
1240:                            // Overflowed.
1241:                            end = instant;
1242:                        }
1243:                    } catch (IllegalArgumentException e) {
1244:                        // Overflowed.
1245:                        end = instant;
1246:                    } catch (ArithmeticException e) {
1247:                        // Overflowed.
1248:                        end = instant;
1249:                    }
1250:
1251:                    return (start > end) ? end : start;
1252:                }
1253:
1254:                public long previousTransition(long instant) {
1255:                    // Increment in order to handle the case where instant is exactly at
1256:                    // a transition.
1257:                    instant++;
1258:
1259:                    int standardOffset = iStandardOffset;
1260:                    Recurrence startRecurrence = iStartRecurrence;
1261:                    Recurrence endRecurrence = iEndRecurrence;
1262:
1263:                    long start, end;
1264:
1265:                    try {
1266:                        start = startRecurrence.previous(instant,
1267:                                standardOffset, endRecurrence.getSaveMillis());
1268:                        if (instant < 0 && start > 0) {
1269:                            // Overflowed.
1270:                            start = instant;
1271:                        }
1272:                    } catch (IllegalArgumentException e) {
1273:                        // Overflowed.
1274:                        start = instant;
1275:                    } catch (ArithmeticException e) {
1276:                        // Overflowed.
1277:                        start = instant;
1278:                    }
1279:
1280:                    try {
1281:                        end = endRecurrence.previous(instant, standardOffset,
1282:                                startRecurrence.getSaveMillis());
1283:                        if (instant < 0 && end > 0) {
1284:                            // Overflowed.
1285:                            end = instant;
1286:                        }
1287:                    } catch (IllegalArgumentException e) {
1288:                        // Overflowed.
1289:                        end = instant;
1290:                    } catch (ArithmeticException e) {
1291:                        // Overflowed.
1292:                        end = instant;
1293:                    }
1294:
1295:                    return ((start > end) ? start : end) - 1;
1296:                }
1297:
1298:                public boolean equals(Object obj) {
1299:                    if (this  == obj) {
1300:                        return true;
1301:                    }
1302:                    if (obj instanceof  DSTZone) {
1303:                        DSTZone other = (DSTZone) obj;
1304:                        return getID().equals(other.getID())
1305:                                && iStandardOffset == other.iStandardOffset
1306:                                && iStartRecurrence
1307:                                        .equals(other.iStartRecurrence)
1308:                                && iEndRecurrence.equals(other.iEndRecurrence);
1309:                    }
1310:                    return false;
1311:                }
1312:
1313:                public void writeTo(DataOutput out) throws IOException {
1314:                    writeMillis(out, iStandardOffset);
1315:                    iStartRecurrence.writeTo(out);
1316:                    iEndRecurrence.writeTo(out);
1317:                }
1318:
1319:                private Recurrence findMatchingRecurrence(long instant) {
1320:                    int standardOffset = iStandardOffset;
1321:                    Recurrence startRecurrence = iStartRecurrence;
1322:                    Recurrence endRecurrence = iEndRecurrence;
1323:
1324:                    long start, end;
1325:
1326:                    try {
1327:                        start = startRecurrence.next(instant, standardOffset,
1328:                                endRecurrence.getSaveMillis());
1329:                    } catch (IllegalArgumentException e) {
1330:                        // Overflowed.
1331:                        start = instant;
1332:                    } catch (ArithmeticException e) {
1333:                        // Overflowed.
1334:                        start = instant;
1335:                    }
1336:
1337:                    try {
1338:                        end = endRecurrence.next(instant, standardOffset,
1339:                                startRecurrence.getSaveMillis());
1340:                    } catch (IllegalArgumentException e) {
1341:                        // Overflowed.
1342:                        end = instant;
1343:                    } catch (ArithmeticException e) {
1344:                        // Overflowed.
1345:                        end = instant;
1346:                    }
1347:
1348:                    return (start > end) ? startRecurrence : endRecurrence;
1349:                }
1350:            }
1351:
1352:            private static final class PrecalculatedZone extends DateTimeZone {
1353:                private static final long serialVersionUID = 7811976468055766265L;
1354:
1355:                static PrecalculatedZone readFrom(DataInput in, String id)
1356:                        throws IOException {
1357:                    // Read string pool.
1358:                    int poolSize = in.readUnsignedShort();
1359:                    String[] pool = new String[poolSize];
1360:                    for (int i = 0; i < poolSize; i++) {
1361:                        pool[i] = in.readUTF();
1362:                    }
1363:
1364:                    int size = in.readInt();
1365:                    long[] transitions = new long[size];
1366:                    int[] wallOffsets = new int[size];
1367:                    int[] standardOffsets = new int[size];
1368:                    String[] nameKeys = new String[size];
1369:
1370:                    for (int i = 0; i < size; i++) {
1371:                        transitions[i] = readMillis(in);
1372:                        wallOffsets[i] = (int) readMillis(in);
1373:                        standardOffsets[i] = (int) readMillis(in);
1374:                        try {
1375:                            int index;
1376:                            if (poolSize < 256) {
1377:                                index = in.readUnsignedByte();
1378:                            } else {
1379:                                index = in.readUnsignedShort();
1380:                            }
1381:                            nameKeys[i] = pool[index];
1382:                        } catch (ArrayIndexOutOfBoundsException e) {
1383:                            throw new IOException("Invalid encoding");
1384:                        }
1385:                    }
1386:
1387:                    DSTZone tailZone = null;
1388:                    if (in.readBoolean()) {
1389:                        tailZone = DSTZone.readFrom(in, id);
1390:                    }
1391:
1392:                    return new PrecalculatedZone(id, transitions, wallOffsets,
1393:                            standardOffsets, nameKeys, tailZone);
1394:                }
1395:
1396:                /**
1397:                 * Factory to create instance from builder.
1398:                 * 
1399:                 * @param id  the zone id
1400:                 * @param outputID  true if the zone id should be output
1401:                 * @param transitions  the list of Transition objects
1402:                 * @param tailZone  optional zone for getting info beyond precalculated tables
1403:                 */
1404:                static PrecalculatedZone create(String id, boolean outputID,
1405:                        ArrayList transitions, DSTZone tailZone) {
1406:                    int size = transitions.size();
1407:                    if (size == 0) {
1408:                        throw new IllegalArgumentException();
1409:                    }
1410:
1411:                    long[] trans = new long[size];
1412:                    int[] wallOffsets = new int[size];
1413:                    int[] standardOffsets = new int[size];
1414:                    String[] nameKeys = new String[size];
1415:
1416:                    Transition last = null;
1417:                    for (int i = 0; i < size; i++) {
1418:                        Transition tr = (Transition) transitions.get(i);
1419:
1420:                        if (!tr.isTransitionFrom(last)) {
1421:                            throw new IllegalArgumentException(id);
1422:                        }
1423:
1424:                        trans[i] = tr.getMillis();
1425:                        wallOffsets[i] = tr.getWallOffset();
1426:                        standardOffsets[i] = tr.getStandardOffset();
1427:                        nameKeys[i] = tr.getNameKey();
1428:
1429:                        last = tr;
1430:                    }
1431:
1432:                    // Some timezones (Australia) have the same name key for
1433:                    // summer and winter which messes everything up. Fix it here.
1434:                    String[] zoneNameData = new String[5];
1435:                    String[][] zoneStrings = new DateFormatSymbols(
1436:                            Locale.ENGLISH).getZoneStrings();
1437:                    for (int j = 0; j < zoneStrings.length; j++) {
1438:                        String[] set = zoneStrings[j];
1439:                        if (set != null && set.length == 5 && id.equals(set[0])) {
1440:                            zoneNameData = set;
1441:                        }
1442:                    }
1443:                    for (int i = 0; i < nameKeys.length - 1; i++) {
1444:                        String curNameKey = nameKeys[i];
1445:                        String nextNameKey = nameKeys[i + 1];
1446:                        long curOffset = wallOffsets[i];
1447:                        long nextOffset = wallOffsets[i + 1];
1448:                        long curStdOffset = standardOffsets[i];
1449:                        long nextStdOffset = standardOffsets[i + 1];
1450:                        Period p = new Period(trans[i], trans[i + 1],
1451:                                PeriodType.yearMonthDay());
1452:                        if (curOffset != nextOffset
1453:                                && curStdOffset == nextStdOffset
1454:                                && curNameKey.equals(nextNameKey)
1455:                                && p.getYears() == 0 && p.getMonths() > 4
1456:                                && p.getMonths() < 8
1457:                                && curNameKey.equals(zoneNameData[2])
1458:                                && curNameKey.equals(zoneNameData[4])) {
1459:
1460:                            System.out.println("Fixing duplicate name key - "
1461:                                    + nextNameKey);
1462:                            System.out.println("     - "
1463:                                    + new DateTime(trans[i]) + " - "
1464:                                    + new DateTime(trans[i + 1]));
1465:                            if (curOffset > nextOffset) {
1466:                                nameKeys[i] = (curNameKey + "-Summer").intern();
1467:                            } else if (curOffset < nextOffset) {
1468:                                nameKeys[i + 1] = (nextNameKey + "-Summer")
1469:                                        .intern();
1470:                                i++;
1471:                            }
1472:                        }
1473:                    }
1474:                    if (tailZone != null) {
1475:                        if (tailZone.iStartRecurrence.getNameKey().equals(
1476:                                tailZone.iEndRecurrence.getNameKey())) {
1477:                            System.out
1478:                                    .println("Fixing duplicate recurrent name key - "
1479:                                            + tailZone.iStartRecurrence
1480:                                                    .getNameKey());
1481:                            if (tailZone.iStartRecurrence.getSaveMillis() > 0) {
1482:                                tailZone = new DSTZone(tailZone.getID(),
1483:                                        tailZone.iStandardOffset,
1484:                                        tailZone.iStartRecurrence
1485:                                                .renameAppend("-Summer"),
1486:                                        tailZone.iEndRecurrence);
1487:                            } else {
1488:                                tailZone = new DSTZone(tailZone.getID(),
1489:                                        tailZone.iStandardOffset,
1490:                                        tailZone.iStartRecurrence,
1491:                                        tailZone.iEndRecurrence
1492:                                                .renameAppend("-Summer"));
1493:                            }
1494:                        }
1495:                    }
1496:
1497:                    return new PrecalculatedZone((outputID ? id : ""), trans,
1498:                            wallOffsets, standardOffsets, nameKeys, tailZone);
1499:                }
1500:
1501:                // All array fields have the same length.
1502:
1503:                private final long[] iTransitions;
1504:
1505:                private final int[] iWallOffsets;
1506:                private final int[] iStandardOffsets;
1507:                private final String[] iNameKeys;
1508:
1509:                private final DSTZone iTailZone;
1510:
1511:                /**
1512:                 * Constructor used ONLY for valid input, loaded via static methods.
1513:                 */
1514:                private PrecalculatedZone(String id, long[] transitions,
1515:                        int[] wallOffsets, int[] standardOffsets,
1516:                        String[] nameKeys, DSTZone tailZone) {
1517:                    super (id);
1518:                    iTransitions = transitions;
1519:                    iWallOffsets = wallOffsets;
1520:                    iStandardOffsets = standardOffsets;
1521:                    iNameKeys = nameKeys;
1522:                    iTailZone = tailZone;
1523:                }
1524:
1525:                public String getNameKey(long instant) {
1526:                    long[] transitions = iTransitions;
1527:                    int i = Arrays.binarySearch(transitions, instant);
1528:                    if (i >= 0) {
1529:                        return iNameKeys[i];
1530:                    }
1531:                    i = ~i;
1532:                    if (i < transitions.length) {
1533:                        if (i > 0) {
1534:                            return iNameKeys[i - 1];
1535:                        }
1536:                        return "UTC";
1537:                    }
1538:                    if (iTailZone == null) {
1539:                        return iNameKeys[i - 1];
1540:                    }
1541:                    return iTailZone.getNameKey(instant);
1542:                }
1543:
1544:                public int getOffset(long instant) {
1545:                    long[] transitions = iTransitions;
1546:                    int i = Arrays.binarySearch(transitions, instant);
1547:                    if (i >= 0) {
1548:                        return iWallOffsets[i];
1549:                    }
1550:                    i = ~i;
1551:                    if (i < transitions.length) {
1552:                        if (i > 0) {
1553:                            return iWallOffsets[i - 1];
1554:                        }
1555:                        return 0;
1556:                    }
1557:                    if (iTailZone == null) {
1558:                        return iWallOffsets[i - 1];
1559:                    }
1560:                    return iTailZone.getOffset(instant);
1561:                }
1562:
1563:                public int getStandardOffset(long instant) {
1564:                    long[] transitions = iTransitions;
1565:                    int i = Arrays.binarySearch(transitions, instant);
1566:                    if (i >= 0) {
1567:                        return iStandardOffsets[i];
1568:                    }
1569:                    i = ~i;
1570:                    if (i < transitions.length) {
1571:                        if (i > 0) {
1572:                            return iStandardOffsets[i - 1];
1573:                        }
1574:                        return 0;
1575:                    }
1576:                    if (iTailZone == null) {
1577:                        return iStandardOffsets[i - 1];
1578:                    }
1579:                    return iTailZone.getStandardOffset(instant);
1580:                }
1581:
1582:                public boolean isFixed() {
1583:                    return false;
1584:                }
1585:
1586:                public long nextTransition(long instant) {
1587:                    long[] transitions = iTransitions;
1588:                    int i = Arrays.binarySearch(transitions, instant);
1589:                    i = (i >= 0) ? (i + 1) : ~i;
1590:                    if (i < transitions.length) {
1591:                        return transitions[i];
1592:                    }
1593:                    if (iTailZone == null) {
1594:                        return instant;
1595:                    }
1596:                    long end = transitions[transitions.length - 1];
1597:                    if (instant < end) {
1598:                        instant = end;
1599:                    }
1600:                    return iTailZone.nextTransition(instant);
1601:                }
1602:
1603:                public long previousTransition(long instant) {
1604:                    long[] transitions = iTransitions;
1605:                    int i = Arrays.binarySearch(transitions, instant);
1606:                    if (i >= 0) {
1607:                        if (instant > Long.MIN_VALUE) {
1608:                            return instant - 1;
1609:                        }
1610:                        return instant;
1611:                    }
1612:                    i = ~i;
1613:                    if (i < transitions.length) {
1614:                        if (i > 0) {
1615:                            long prev = transitions[i - 1];
1616:                            if (prev > Long.MIN_VALUE) {
1617:                                return prev - 1;
1618:                            }
1619:                        }
1620:                        return instant;
1621:                    }
1622:                    if (iTailZone != null) {
1623:                        long prev = iTailZone.previousTransition(instant);
1624:                        if (prev < instant) {
1625:                            return prev;
1626:                        }
1627:                    }
1628:                    long prev = transitions[i - 1];
1629:                    if (prev > Long.MIN_VALUE) {
1630:                        return prev - 1;
1631:                    }
1632:                    return instant;
1633:                }
1634:
1635:                public boolean equals(Object obj) {
1636:                    if (this  == obj) {
1637:                        return true;
1638:                    }
1639:                    if (obj instanceof  PrecalculatedZone) {
1640:                        PrecalculatedZone other = (PrecalculatedZone) obj;
1641:                        return getID().equals(other.getID())
1642:                                && Arrays.equals(iTransitions,
1643:                                        other.iTransitions)
1644:                                && Arrays.equals(iNameKeys, other.iNameKeys)
1645:                                && Arrays.equals(iWallOffsets,
1646:                                        other.iWallOffsets)
1647:                                && Arrays.equals(iStandardOffsets,
1648:                                        other.iStandardOffsets)
1649:                                && ((iTailZone == null) ? (null == other.iTailZone)
1650:                                        : (iTailZone.equals(other.iTailZone)));
1651:                    }
1652:                    return false;
1653:                }
1654:
1655:                public void writeTo(DataOutput out) throws IOException {
1656:                    int size = iTransitions.length;
1657:
1658:                    // Create unique string pool.
1659:                    Set poolSet = new HashSet();
1660:                    for (int i = 0; i < size; i++) {
1661:                        poolSet.add(iNameKeys[i]);
1662:                    }
1663:
1664:                    int poolSize = poolSet.size();
1665:                    if (poolSize > 65535) {
1666:                        throw new UnsupportedOperationException(
1667:                                "String pool is too large");
1668:                    }
1669:                    String[] pool = new String[poolSize];
1670:                    Iterator it = poolSet.iterator();
1671:                    for (int i = 0; it.hasNext(); i++) {
1672:                        pool[i] = (String) it.next();
1673:                    }
1674:
1675:                    // Write out the pool.
1676:                    out.writeShort(poolSize);
1677:                    for (int i = 0; i < poolSize; i++) {
1678:                        out.writeUTF(pool[i]);
1679:                    }
1680:
1681:                    out.writeInt(size);
1682:
1683:                    for (int i = 0; i < size; i++) {
1684:                        writeMillis(out, iTransitions[i]);
1685:                        writeMillis(out, iWallOffsets[i]);
1686:                        writeMillis(out, iStandardOffsets[i]);
1687:
1688:                        // Find pool index and write it out.
1689:                        String nameKey = iNameKeys[i];
1690:                        for (int j = 0; j < poolSize; j++) {
1691:                            if (pool[j].equals(nameKey)) {
1692:                                if (poolSize < 256) {
1693:                                    out.writeByte(j);
1694:                                } else {
1695:                                    out.writeShort(j);
1696:                                }
1697:                                break;
1698:                            }
1699:                        }
1700:                    }
1701:
1702:                    out.writeBoolean(iTailZone != null);
1703:                    if (iTailZone != null) {
1704:                        iTailZone.writeTo(out);
1705:                    }
1706:                }
1707:
1708:                public boolean isCachable() {
1709:                    if (iTailZone != null) {
1710:                        return true;
1711:                    }
1712:                    long[] transitions = iTransitions;
1713:                    if (transitions.length <= 1) {
1714:                        return false;
1715:                    }
1716:
1717:                    // Add up all the distances between transitions that are less than
1718:                    // about two years.
1719:                    double distances = 0;
1720:                    int count = 0;
1721:
1722:                    for (int i = 1; i < transitions.length; i++) {
1723:                        long diff = transitions[i] - transitions[i - 1];
1724:                        if (diff < ((366L + 365) * 24 * 60 * 60 * 1000)) {
1725:                            distances += (double) diff;
1726:                            count++;
1727:                        }
1728:                    }
1729:
1730:                    if (count > 0) {
1731:                        double avg = distances / count;
1732:                        avg /= 24 * 60 * 60 * 1000;
1733:                        if (avg >= 25) {
1734:                            // Only bother caching if average distance between
1735:                            // transitions is at least 25 days. Why 25?
1736:                            // CachedDateTimeZone is more efficient if the distance
1737:                            // between transitions is large. With an average of 25, it
1738:                            // will on average perform about 2 tests per cache
1739:                            // hit. (49.7 / 25) is approximately 2.
1740:                            return true;
1741:                        }
1742:                    }
1743:
1744:                    return false;
1745:                }
1746:            }
1747:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.