001    /*
002     *  Copyright 2001-2013 Stephen Colebourne
003     *
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     *  Unless required by applicable law or agreed to in writing, software
011     *  distributed under the License is distributed on an "AS IS" BASIS,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package org.joda.time;
017    
018    import java.io.IOException;
019    import java.io.ObjectInputStream;
020    import java.io.ObjectOutputStream;
021    import java.io.ObjectStreamException;
022    import java.io.Serializable;
023    import java.lang.ref.Reference;
024    import java.lang.ref.SoftReference;
025    import java.util.HashMap;
026    import java.util.Locale;
027    import java.util.Map;
028    import java.util.Set;
029    import java.util.TimeZone;
030    
031    import org.joda.convert.FromString;
032    import org.joda.convert.ToString;
033    import org.joda.time.chrono.BaseChronology;
034    import org.joda.time.field.FieldUtils;
035    import org.joda.time.format.DateTimeFormat;
036    import org.joda.time.format.DateTimeFormatter;
037    import org.joda.time.format.DateTimeFormatterBuilder;
038    import org.joda.time.format.FormatUtils;
039    import org.joda.time.tz.DefaultNameProvider;
040    import org.joda.time.tz.FixedDateTimeZone;
041    import org.joda.time.tz.NameProvider;
042    import org.joda.time.tz.Provider;
043    import org.joda.time.tz.UTCProvider;
044    import org.joda.time.tz.ZoneInfoProvider;
045    
046    /**
047     * DateTimeZone represents a time zone.
048     * <p>
049     * A time zone is a system of rules to convert time from one geographic 
050     * location to another. For example, Paris, France is one hour ahead of
051     * London, England. Thus when it is 10:00 in London, it is 11:00 in Paris.
052     * <p>
053     * All time zone rules are expressed, for historical reasons, relative to
054     * Greenwich, London. Local time in Greenwich is referred to as Greenwich Mean
055     * Time (GMT).  This is similar, but not precisely identical, to Universal 
056     * Coordinated Time, or UTC. This library only uses the term UTC.
057     * <p>
058     * Using this system, America/Los_Angeles is expressed as UTC-08:00, or UTC-07:00
059     * in the summer. The offset -08:00 indicates that America/Los_Angeles time is
060     * obtained from UTC by adding -08:00, that is, by subtracting 8 hours.
061     * <p>
062     * The offset differs in the summer because of daylight saving time, or DST.
063     * The following definitions of time are generally used:
064     * <ul>
065     * <li>UTC - The reference time.
066     * <li>Standard Time - The local time without a daylight saving time offset.
067     * For example, in Paris, standard time is UTC+01:00.
068     * <li>Daylight Saving Time - The local time with a daylight saving time 
069     * offset. This offset is typically one hour, but not always. It is typically
070     * used in most countries away from the equator.  In Paris, daylight saving 
071     * time is UTC+02:00.
072     * <li>Wall Time - This is what a local clock on the wall reads. This will be
073     * either Standard Time or Daylight Saving Time depending on the time of year
074     * and whether the location uses Daylight Saving Time.
075     * </ul>
076     * <p>
077     * Unlike the Java TimeZone class, DateTimeZone is immutable. It also only
078     * supports long format time zone ids. Thus EST and ECT are not accepted.
079     * However, the factory that accepts a TimeZone will attempt to convert from
080     * the old short id to a suitable long id.
081     * <p>
082     * DateTimeZone is thread-safe and immutable, and all subclasses must be as
083     * well.
084     * 
085     * @author Brian S O'Neill
086     * @author Stephen Colebourne
087     * @since 1.0
088     */
089    public abstract class DateTimeZone implements Serializable {
090        
091        /** Serialization version. */
092        private static final long serialVersionUID = 5546345482340108586L;
093    
094        /** The time zone for Universal Coordinated Time */
095        public static final DateTimeZone UTC = new FixedDateTimeZone("UTC", "UTC", 0, 0);
096    
097        /** The instance that is providing time zones. */
098        private static Provider cProvider;
099        /** The instance that is providing time zone names. */
100        private static NameProvider cNameProvider;
101        /** The set of ID strings. */
102        private static Set<String> cAvailableIDs;
103        /** The default time zone. */
104        private static volatile DateTimeZone cDefault;
105        /** A formatter for printing and parsing zones. */
106        private static DateTimeFormatter cOffsetFormatter;
107    
108        /** Cache that maps fixed offset strings to softly referenced DateTimeZones */
109        private static Map<String, SoftReference<DateTimeZone>> iFixedOffsetCache;
110    
111        /** Cache of old zone IDs to new zone IDs */
112        private static Map<String, String> cZoneIdConversion;
113    
114        static {
115            setProvider0(null);
116            setNameProvider0(null);
117        }
118    
119        //-----------------------------------------------------------------------
120        /**
121         * Gets the default time zone.
122         * <p>
123         * The default time zone is derived from the system property {@code user.timezone}.
124         * If that is {@code null} or is not a valid identifier, then the value of the
125         * JDK {@code TimeZone} default is converted. If that fails, {@code UTC} is used.
126         * <p>
127         * NOTE: If the {@code java.util.TimeZone} default is updated <i>after</i> calling this
128         * method, then the change will not be picked up here.
129         * 
130         * @return the default datetime zone object
131         */
132        public static DateTimeZone getDefault() {
133            DateTimeZone zone = cDefault;
134            if (zone == null) {
135                synchronized(DateTimeZone.class) {
136                    zone = cDefault;
137                    if (zone == null) {
138                        DateTimeZone temp = null;
139                        try {
140                            try {
141                                String id = System.getProperty("user.timezone");
142                                if (id != null) {  // null check avoids stack overflow
143                                    temp = forID(id);
144                                }
145                            } catch (RuntimeException ex) {
146                                // ignored
147                            }
148                            if (temp == null) {
149                                temp = forTimeZone(TimeZone.getDefault());
150                            }
151                        } catch (IllegalArgumentException ex) {
152                            // ignored
153                        }
154                        if (temp == null) {
155                            temp = UTC;
156                        }
157                        cDefault = zone = temp;
158                    }
159                }
160            }
161            return zone;
162        }
163    
164        /**
165         * Sets the default time zone.
166         * <p>
167         * NOTE: Calling this method does <i>not</i> set the {@code java.util.TimeZone} default.
168         * 
169         * @param zone  the default datetime zone object, must not be null
170         * @throws IllegalArgumentException if the zone is null
171         * @throws SecurityException if the application has insufficient security rights
172         */
173        public static void setDefault(DateTimeZone zone) throws SecurityException {
174            SecurityManager sm = System.getSecurityManager();
175            if (sm != null) {
176                sm.checkPermission(new JodaTimePermission("DateTimeZone.setDefault"));
177            }
178            if (zone == null) {
179                throw new IllegalArgumentException("The datetime zone must not be null");
180            }
181            synchronized(DateTimeZone.class) {
182                cDefault = zone;
183            }
184        }
185    
186        //-----------------------------------------------------------------------
187        /**
188         * Gets a time zone instance for the specified time zone id.
189         * <p>
190         * The time zone id may be one of those returned by getAvailableIDs.
191         * Short ids, as accepted by {@link java.util.TimeZone}, are not accepted.
192         * All IDs must be specified in the long format.
193         * The exception is UTC, which is an acceptable id.
194         * <p>
195         * Alternatively a locale independent, fixed offset, datetime zone can
196         * be specified. The form <code>[+-]hh:mm</code> can be used.
197         * 
198         * @param id  the ID of the datetime zone, null means default
199         * @return the DateTimeZone object for the ID
200         * @throws IllegalArgumentException if the ID is not recognised
201         */
202        @FromString
203        public static DateTimeZone forID(String id) {
204            if (id == null) {
205                return getDefault();
206            }
207            if (id.equals("UTC")) {
208                return DateTimeZone.UTC;
209            }
210            DateTimeZone zone = cProvider.getZone(id);
211            if (zone != null) {
212                return zone;
213            }
214            if (id.startsWith("+") || id.startsWith("-")) {
215                int offset = parseOffset(id);
216                if (offset == 0L) {
217                    return DateTimeZone.UTC;
218                } else {
219                    id = printOffset(offset);
220                    return fixedOffsetZone(id, offset);
221                }
222            }
223            throw new IllegalArgumentException("The datetime zone id '" + id + "' is not recognised");
224        }
225    
226        /**
227         * Gets a time zone instance for the specified offset to UTC in hours.
228         * This method assumes standard length hours.
229         * <p>
230         * This factory is a convenient way of constructing zones with a fixed offset.
231         * 
232         * @param hoursOffset  the offset in hours from UTC
233         * @return the DateTimeZone object for the offset
234         * @throws IllegalArgumentException if the offset is too large or too small
235         */
236        public static DateTimeZone forOffsetHours(int hoursOffset) throws IllegalArgumentException {
237            return forOffsetHoursMinutes(hoursOffset, 0);
238        }
239    
240        /**
241         * Gets a time zone instance for the specified offset to UTC in hours and minutes.
242         * This method assumes 60 minutes in an hour, and standard length minutes.
243         * <p>
244         * This factory is a convenient way of constructing zones with a fixed offset.
245         * The minutes value is always positive and in the range 0 to 59.
246         * If constructed with the values (-2, 30), the resulting zone is '-02:30'.
247         * 
248         * @param hoursOffset  the offset in hours from UTC
249         * @param minutesOffset  the offset in minutes from UTC, must be between 0 and 59 inclusive
250         * @return the DateTimeZone object for the offset
251         * @throws IllegalArgumentException if the offset or minute is too large or too small
252         */
253        public static DateTimeZone forOffsetHoursMinutes(int hoursOffset, int minutesOffset) throws IllegalArgumentException {
254            if (hoursOffset == 0 && minutesOffset == 0) {
255                return DateTimeZone.UTC;
256            }
257            if (minutesOffset < 0 || minutesOffset > 59) {
258                throw new IllegalArgumentException("Minutes out of range: " + minutesOffset);
259            }
260            int offset = 0;
261            try {
262                int hoursInMinutes = FieldUtils.safeMultiply(hoursOffset, 60);
263                if (hoursInMinutes < 0) {
264                    minutesOffset = FieldUtils.safeAdd(hoursInMinutes, -minutesOffset);
265                } else {
266                    minutesOffset = FieldUtils.safeAdd(hoursInMinutes, minutesOffset);
267                }
268                offset = FieldUtils.safeMultiply(minutesOffset, DateTimeConstants.MILLIS_PER_MINUTE);
269            } catch (ArithmeticException ex) {
270                throw new IllegalArgumentException("Offset is too large");
271            }
272            return forOffsetMillis(offset);
273        }
274    
275        /**
276         * Gets a time zone instance for the specified offset to UTC in milliseconds.
277         *
278         * @param millisOffset  the offset in millis from UTC
279         * @return the DateTimeZone object for the offset
280         */
281        public static DateTimeZone forOffsetMillis(int millisOffset) {
282            String id = printOffset(millisOffset);
283            return fixedOffsetZone(id, millisOffset);
284        }
285    
286        /**
287         * Gets a time zone instance for a JDK TimeZone.
288         * <p>
289         * DateTimeZone only accepts a subset of the IDs from TimeZone. The
290         * excluded IDs are the short three letter form (except UTC). This 
291         * method will attempt to convert between time zones created using the
292         * short IDs and the full version.
293         * <p>
294         * This method is not designed to parse time zones with rules created by
295         * applications using <code>SimpleTimeZone</code> directly.
296         * 
297         * @param zone  the zone to convert, null means default
298         * @return the DateTimeZone object for the zone
299         * @throws IllegalArgumentException if the zone is not recognised
300         */
301        public static DateTimeZone forTimeZone(TimeZone zone) {
302            if (zone == null) {
303                return getDefault();
304            }
305            final String id = zone.getID();
306            if (id.equals("UTC")) {
307                return DateTimeZone.UTC;
308            }
309    
310            // Convert from old alias before consulting provider since they may differ.
311            DateTimeZone dtz = null;
312            String convId = getConvertedId(id);
313            if (convId != null) {
314                dtz = cProvider.getZone(convId);
315            }
316            if (dtz == null) {
317                dtz = cProvider.getZone(id);
318            }
319            if (dtz != null) {
320                return dtz;
321            }
322    
323            // Support GMT+/-hh:mm formats
324            if (convId == null) {
325                convId = zone.getID();
326                if (convId.startsWith("GMT+") || convId.startsWith("GMT-")) {
327                    convId = convId.substring(3);
328                    int offset = parseOffset(convId);
329                    if (offset == 0L) {
330                        return DateTimeZone.UTC;
331                    } else {
332                        convId = printOffset(offset);
333                        return fixedOffsetZone(convId, offset);
334                    }
335                }
336            }
337            throw new IllegalArgumentException("The datetime zone id '" + id + "' is not recognised");
338        }
339    
340        //-----------------------------------------------------------------------
341        /**
342         * Gets the zone using a fixed offset amount.
343         * 
344         * @param id  the zone id
345         * @param offset  the offset in millis
346         * @return the zone
347         */
348        private static synchronized DateTimeZone fixedOffsetZone(String id, int offset) {
349            if (offset == 0) {
350                return DateTimeZone.UTC;
351            }
352            if (iFixedOffsetCache == null) {
353                iFixedOffsetCache = new HashMap<String, SoftReference<DateTimeZone>>();
354            }
355            DateTimeZone zone;
356            Reference<DateTimeZone> ref = iFixedOffsetCache.get(id);
357            if (ref != null) {
358                zone = ref.get();
359                if (zone != null) {
360                    return zone;
361                }
362            }
363            zone = new FixedDateTimeZone(id, null, offset, offset);
364            iFixedOffsetCache.put(id, new SoftReference<DateTimeZone>(zone));
365            return zone;
366        }
367    
368        /**
369         * Gets all the available IDs supported.
370         * 
371         * @return an unmodifiable Set of String IDs
372         */
373        public static Set<String> getAvailableIDs() {
374            return cAvailableIDs;
375        }
376    
377        //-----------------------------------------------------------------------
378        /**
379         * Gets the zone provider factory.
380         * <p>
381         * The zone provider is a pluggable instance factory that supplies the
382         * actual instances of DateTimeZone.
383         * 
384         * @return the provider
385         */
386        public static Provider getProvider() {
387            return cProvider;
388        }
389    
390        /**
391         * Sets the zone provider factory.
392         * <p>
393         * The zone provider is a pluggable instance factory that supplies the
394         * actual instances of DateTimeZone.
395         * 
396         * @param provider  provider to use, or null for default
397         * @throws SecurityException if you do not have the permission DateTimeZone.setProvider
398         * @throws IllegalArgumentException if the provider is invalid
399         */
400        public static void setProvider(Provider provider) throws SecurityException {
401            SecurityManager sm = System.getSecurityManager();
402            if (sm != null) {
403                sm.checkPermission(new JodaTimePermission("DateTimeZone.setProvider"));
404            }
405            setProvider0(provider);
406        }
407    
408        /**
409         * Sets the zone provider factory without performing the security check.
410         * 
411         * @param provider  provider to use, or null for default
412         * @throws IllegalArgumentException if the provider is invalid
413         */
414        private static void setProvider0(Provider provider) {
415            if (provider == null) {
416                provider = getDefaultProvider();
417            }
418            Set<String> ids = provider.getAvailableIDs();
419            if (ids == null || ids.size() == 0) {
420                throw new IllegalArgumentException
421                    ("The provider doesn't have any available ids");
422            }
423            if (!ids.contains("UTC")) {
424                throw new IllegalArgumentException("The provider doesn't support UTC");
425            }
426            if (!UTC.equals(provider.getZone("UTC"))) {
427                throw new IllegalArgumentException("Invalid UTC zone provided");
428            }
429            cProvider = provider;
430            cAvailableIDs = ids;
431        }
432    
433        /**
434         * Gets the default zone provider.
435         * <p>
436         * Tries the system property <code>org.joda.time.DateTimeZone.Provider</code>.
437         * Then tries a <code>ZoneInfoProvider</code> using the data in <code>org/joda/time/tz/data</code>.
438         * Then uses <code>UTCProvider</code>.
439         * 
440         * @return the default name provider
441         */
442        private static Provider getDefaultProvider() {
443            Provider provider = null;
444    
445            try {
446                String providerClass =
447                    System.getProperty("org.joda.time.DateTimeZone.Provider");
448                if (providerClass != null) {
449                    try {
450                        provider = (Provider) Class.forName(providerClass).newInstance();
451                    } catch (Exception ex) {
452                        Thread thread = Thread.currentThread();
453                        thread.getThreadGroup().uncaughtException(thread, ex);
454                    }
455                }
456            } catch (SecurityException ex) {
457                // ignored
458            }
459    
460            if (provider == null) {
461                try {
462                    provider = new ZoneInfoProvider("org/joda/time/tz/data");
463                } catch (Exception ex) {
464                    Thread thread = Thread.currentThread();
465                    thread.getThreadGroup().uncaughtException(thread, ex);
466                }
467            }
468    
469            if (provider == null) {
470                provider = new UTCProvider();
471            }
472    
473            return provider;
474        }
475    
476        //-----------------------------------------------------------------------
477        /**
478         * Gets the name provider factory.
479         * <p>
480         * The name provider is a pluggable instance factory that supplies the
481         * names of each DateTimeZone.
482         * 
483         * @return the provider
484         */
485        public static NameProvider getNameProvider() {
486            return cNameProvider;
487        }
488    
489        /**
490         * Sets the name provider factory.
491         * <p>
492         * The name provider is a pluggable instance factory that supplies the
493         * names of each DateTimeZone.
494         * 
495         * @param nameProvider  provider to use, or null for default
496         * @throws SecurityException if you do not have the permission DateTimeZone.setNameProvider
497         * @throws IllegalArgumentException if the provider is invalid
498         */
499        public static void setNameProvider(NameProvider nameProvider) throws SecurityException {
500            SecurityManager sm = System.getSecurityManager();
501            if (sm != null) {
502                sm.checkPermission(new JodaTimePermission("DateTimeZone.setNameProvider"));
503            }
504            setNameProvider0(nameProvider);
505        }
506    
507        /**
508         * Sets the name provider factory without performing the security check.
509         * 
510         * @param nameProvider  provider to use, or null for default
511         * @throws IllegalArgumentException if the provider is invalid
512         */
513        private static void setNameProvider0(NameProvider nameProvider) {
514            if (nameProvider == null) {
515                nameProvider = getDefaultNameProvider();
516            }
517            cNameProvider = nameProvider;
518        }
519    
520        /**
521         * Gets the default name provider.
522         * <p>
523         * Tries the system property <code>org.joda.time.DateTimeZone.NameProvider</code>.
524         * Then uses <code>DefaultNameProvider</code>.
525         * 
526         * @return the default name provider
527         */
528        private static NameProvider getDefaultNameProvider() {
529            NameProvider nameProvider = null;
530            try {
531                String providerClass = System.getProperty("org.joda.time.DateTimeZone.NameProvider");
532                if (providerClass != null) {
533                    try {
534                        nameProvider = (NameProvider) Class.forName(providerClass).newInstance();
535                    } catch (Exception ex) {
536                        Thread thread = Thread.currentThread();
537                        thread.getThreadGroup().uncaughtException(thread, ex);
538                    }
539                }
540            } catch (SecurityException ex) {
541                // ignore
542            }
543    
544            if (nameProvider == null) {
545                nameProvider = new DefaultNameProvider();
546            }
547    
548            return nameProvider;
549        }
550    
551        //-----------------------------------------------------------------------
552        /**
553         * Converts an old style id to a new style id.
554         * 
555         * @param id  the old style id
556         * @return the new style id, null if not found
557         */
558        private static synchronized String getConvertedId(String id) {
559            Map<String, String> map = cZoneIdConversion;
560            if (map == null) {
561                // Backwards compatibility with TimeZone.
562                map = new HashMap<String, String>();
563                map.put("GMT", "UTC");
564                map.put("WET", "WET");
565                map.put("CET", "CET");
566                map.put("MET", "CET");
567                map.put("ECT", "CET");
568                map.put("EET", "EET");
569                map.put("MIT", "Pacific/Apia");
570                map.put("HST", "Pacific/Honolulu");  // JDK 1.1 compatible
571                map.put("AST", "America/Anchorage");
572                map.put("PST", "America/Los_Angeles");
573                map.put("MST", "America/Denver");  // JDK 1.1 compatible
574                map.put("PNT", "America/Phoenix");
575                map.put("CST", "America/Chicago");
576                map.put("EST", "America/New_York");  // JDK 1.1 compatible
577                map.put("IET", "America/Indiana/Indianapolis");
578                map.put("PRT", "America/Puerto_Rico");
579                map.put("CNT", "America/St_Johns");
580                map.put("AGT", "America/Argentina/Buenos_Aires");
581                map.put("BET", "America/Sao_Paulo");
582                map.put("ART", "Africa/Cairo");
583                map.put("CAT", "Africa/Harare");
584                map.put("EAT", "Africa/Addis_Ababa");
585                map.put("NET", "Asia/Yerevan");
586                map.put("PLT", "Asia/Karachi");
587                map.put("IST", "Asia/Kolkata");
588                map.put("BST", "Asia/Dhaka");
589                map.put("VST", "Asia/Ho_Chi_Minh");
590                map.put("CTT", "Asia/Shanghai");
591                map.put("JST", "Asia/Tokyo");
592                map.put("ACT", "Australia/Darwin");
593                map.put("AET", "Australia/Sydney");
594                map.put("SST", "Pacific/Guadalcanal");
595                map.put("NST", "Pacific/Auckland");
596                cZoneIdConversion = map;
597            }
598            return map.get(id);
599        }
600    
601        private static int parseOffset(String str) {
602            // Can't use a real chronology if called during class
603            // initialization. Offset parser doesn't need it anyhow.
604            Chronology chrono = new BaseChronology() {
605                public DateTimeZone getZone() {
606                    return null;
607                }
608                public Chronology withUTC() {
609                    return this;
610                }
611                public Chronology withZone(DateTimeZone zone) {
612                    return this;
613                }
614                public String toString() {
615                    return getClass().getName();
616                }
617            };
618            return -(int) offsetFormatter().withChronology(chrono).parseMillis(str);
619        }
620    
621        /**
622         * Formats a timezone offset string.
623         * <p>
624         * This method is kept separate from the formatting classes to speed and
625         * simplify startup and classloading.
626         * 
627         * @param offset  the offset in milliseconds
628         * @return the time zone string
629         */
630        private static String printOffset(int offset) {
631            StringBuffer buf = new StringBuffer();
632            if (offset >= 0) {
633                buf.append('+');
634            } else {
635                buf.append('-');
636                offset = -offset;
637            }
638    
639            int hours = offset / DateTimeConstants.MILLIS_PER_HOUR;
640            FormatUtils.appendPaddedInteger(buf, hours, 2);
641            offset -= hours * (int) DateTimeConstants.MILLIS_PER_HOUR;
642    
643            int minutes = offset / DateTimeConstants.MILLIS_PER_MINUTE;
644            buf.append(':');
645            FormatUtils.appendPaddedInteger(buf, minutes, 2);
646            offset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
647            if (offset == 0) {
648                return buf.toString();
649            }
650    
651            int seconds = offset / DateTimeConstants.MILLIS_PER_SECOND;
652            buf.append(':');
653            FormatUtils.appendPaddedInteger(buf, seconds, 2);
654            offset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
655            if (offset == 0) {
656                return buf.toString();
657            }
658    
659            buf.append('.');
660            FormatUtils.appendPaddedInteger(buf, offset, 3);
661            return buf.toString();
662        }
663    
664        /**
665         * Gets a printer/parser for managing the offset id formatting.
666         * 
667         * @return the formatter
668         */
669        private static synchronized DateTimeFormatter offsetFormatter() {
670            if (cOffsetFormatter == null) {
671                cOffsetFormatter = new DateTimeFormatterBuilder()
672                    .appendTimeZoneOffset(null, true, 2, 4)
673                    .toFormatter();
674            }
675            return cOffsetFormatter;
676        }
677    
678        // Instance fields and methods
679        //--------------------------------------------------------------------
680    
681        private final String iID;
682    
683        /**
684         * Constructor.
685         * 
686         * @param id  the id to use
687         * @throws IllegalArgumentException if the id is null
688         */
689        protected DateTimeZone(String id) {
690            if (id == null) {
691                throw new IllegalArgumentException("Id must not be null");
692            }
693            iID = id;
694        }
695    
696        // Principal methods
697        //--------------------------------------------------------------------
698    
699        /**
700         * Gets the ID of this datetime zone.
701         * 
702         * @return the ID of this datetime zone
703         */
704        @ToString
705        public final String getID() {
706            return iID;
707        }
708    
709        /**
710         * Returns a non-localized name that is unique to this time zone. It can be
711         * combined with id to form a unique key for fetching localized names.
712         *
713         * @param instant  milliseconds from 1970-01-01T00:00:00Z to get the name for
714         * @return name key or null if id should be used for names
715         */
716        public abstract String getNameKey(long instant);
717    
718        /**
719         * Gets the short name of this datetime zone suitable for display using
720         * the default locale.
721         * <p>
722         * If the name is not available for the locale, then this method returns a
723         * string in the format <code>[+-]hh:mm</code>.
724         * 
725         * @param instant  milliseconds from 1970-01-01T00:00:00Z to get the name for
726         * @return the human-readable short name in the default locale
727         */
728        public final String getShortName(long instant) {
729            return getShortName(instant, null);
730        }
731    
732        /**
733         * Gets the short name of this datetime zone suitable for display using
734         * the specified locale.
735         * <p>
736         * If the name is not available for the locale, then this method returns a
737         * string in the format <code>[+-]hh:mm</code>.
738         * 
739         * @param instant  milliseconds from 1970-01-01T00:00:00Z to get the name for
740         * @param locale  the locale to get the name for
741         * @return the human-readable short name in the specified locale
742         */
743        public String getShortName(long instant, Locale locale) {
744            if (locale == null) {
745                locale = Locale.getDefault();
746            }
747            String nameKey = getNameKey(instant);
748            if (nameKey == null) {
749                return iID;
750            }
751            String name = cNameProvider.getShortName(locale, iID, nameKey);
752            if (name != null) {
753                return name;
754            }
755            return printOffset(getOffset(instant));
756        }
757    
758        /**
759         * Gets the long name of this datetime zone suitable for display using
760         * the default locale.
761         * <p>
762         * If the name is not available for the locale, then this method returns a
763         * string in the format <code>[+-]hh:mm</code>.
764         * 
765         * @param instant  milliseconds from 1970-01-01T00:00:00Z to get the name for
766         * @return the human-readable long name in the default locale
767         */
768        public final String getName(long instant) {
769            return getName(instant, null);
770        }
771    
772        /**
773         * Gets the long name of this datetime zone suitable for display using
774         * the specified locale.
775         * <p>
776         * If the name is not available for the locale, then this method returns a
777         * string in the format <code>[+-]hh:mm</code>.
778         * 
779         * @param instant  milliseconds from 1970-01-01T00:00:00Z to get the name for
780         * @param locale  the locale to get the name for
781         * @return the human-readable long name in the specified locale
782         */
783        public String getName(long instant, Locale locale) {
784            if (locale == null) {
785                locale = Locale.getDefault();
786            }
787            String nameKey = getNameKey(instant);
788            if (nameKey == null) {
789                return iID;
790            }
791            String name = cNameProvider.getName(locale, iID, nameKey);
792            if (name != null) {
793                return name;
794            }
795            return printOffset(getOffset(instant));
796        }
797    
798        /**
799         * Gets the millisecond offset to add to UTC to get local time.
800         * 
801         * @param instant  milliseconds from 1970-01-01T00:00:00Z to get the offset for
802         * @return the millisecond offset to add to UTC to get local time
803         */
804        public abstract int getOffset(long instant);
805    
806        /**
807         * Gets the millisecond offset to add to UTC to get local time.
808         * 
809         * @param instant  instant to get the offset for, null means now
810         * @return the millisecond offset to add to UTC to get local time
811         */
812        public final int getOffset(ReadableInstant instant) {
813            if (instant == null) {
814                return getOffset(DateTimeUtils.currentTimeMillis());
815            }
816            return getOffset(instant.getMillis());
817        }
818    
819        /**
820         * Gets the standard millisecond offset to add to UTC to get local time,
821         * when standard time is in effect.
822         * 
823         * @param instant  milliseconds from 1970-01-01T00:00:00Z to get the offset for
824         * @return the millisecond offset to add to UTC to get local time
825         */
826        public abstract int getStandardOffset(long instant);
827    
828        /**
829         * Checks whether, at a particular instant, the offset is standard or not.
830         * <p>
831         * This method can be used to determine whether Summer Time (DST) applies.
832         * As a general rule, if the offset at the specified instant is standard,
833         * then either Winter time applies, or there is no Summer Time. If the
834         * instant is not standard, then Summer Time applies.
835         * <p>
836         * The implementation of the method is simply whether {@link #getOffset(long)}
837         * equals {@link #getStandardOffset(long)} at the specified instant.
838         * 
839         * @param instant  milliseconds from 1970-01-01T00:00:00Z to get the offset for
840         * @return true if the offset at the given instant is the standard offset
841         * @since 1.5
842         */
843        public boolean isStandardOffset(long instant) {
844            return getOffset(instant) == getStandardOffset(instant);
845        }
846    
847        /**
848         * Gets the millisecond offset to subtract from local time to get UTC time.
849         * This offset can be used to undo adding the offset obtained by getOffset.
850         *
851         * <pre>
852         * millisLocal == millisUTC   + getOffset(millisUTC)
853         * millisUTC   == millisLocal - getOffsetFromLocal(millisLocal)
854         * </pre>
855         *
856         * NOTE: After calculating millisLocal, some error may be introduced. At
857         * offset transitions (due to DST or other historical changes), ranges of
858         * local times may map to different UTC times.
859         * <p>
860         * This method will return an offset suitable for calculating an instant
861         * after any DST gap. For example, consider a zone with a cutover
862         * from 01:00 to 01:59:<br />
863         * Input: 00:00  Output: 00:00<br />
864         * Input: 00:30  Output: 00:30<br />
865         * Input: 01:00  Output: 02:00<br />
866         * Input: 01:30  Output: 02:30<br />
867         * Input: 02:00  Output: 02:00<br />
868         * Input: 02:30  Output: 02:30<br />
869         * <p>
870         * During a DST overlap (where the local time is ambiguous) this method will return
871         * the earlier instant. The combination of these two rules is to always favour
872         * daylight (summer) time over standard (winter) time.
873         * <p>
874         * NOTE: Prior to v2.0, the DST overlap behaviour was not defined and varied by hemisphere.
875         * Prior to v1.5, the DST gap behaviour was also not defined.
876         *
877         * @param instantLocal  the millisecond instant, relative to this time zone, to get the offset for
878         * @return the millisecond offset to subtract from local time to get UTC time
879         */
880        public int getOffsetFromLocal(long instantLocal) {
881            // get the offset at instantLocal (first estimate)
882            final int offsetLocal = getOffset(instantLocal);
883            // adjust instantLocal using the estimate and recalc the offset
884            final long instantAdjusted = instantLocal - offsetLocal;
885            final int offsetAdjusted = getOffset(instantAdjusted);
886            // if the offsets differ, we must be near a DST boundary
887            if (offsetLocal != offsetAdjusted) {
888                // we need to ensure that time is always after the DST gap
889                // this happens naturally for positive offsets, but not for negative
890                if ((offsetLocal - offsetAdjusted) < 0) {
891                    // if we just return offsetAdjusted then the time is pushed
892                    // back before the transition, whereas it should be
893                    // on or after the transition
894                    long nextLocal = nextTransition(instantAdjusted);
895                    long nextAdjusted = nextTransition(instantLocal - offsetAdjusted);
896                    if (nextLocal != nextAdjusted) {
897                        return offsetLocal;
898                    }
899                }
900            } else if (offsetLocal >= 0) {
901                long prev = previousTransition(instantAdjusted);
902                if (prev < instantAdjusted) {
903                    int offsetPrev = getOffset(prev);
904                    int diff = offsetPrev - offsetLocal;
905                    if (instantAdjusted - prev <= diff) {
906                        return offsetPrev;
907                    }
908                }
909            }
910            return offsetAdjusted;
911        }
912    
913        /**
914         * Converts a standard UTC instant to a local instant with the same
915         * local time. This conversion is used before performing a calculation
916         * so that the calculation can be done using a simple local zone.
917         *
918         * @param instantUTC  the UTC instant to convert to local
919         * @return the local instant with the same local time
920         * @throws ArithmeticException if the result overflows a long
921         * @since 1.5
922         */
923        public long convertUTCToLocal(long instantUTC) {
924            int offset = getOffset(instantUTC);
925            long instantLocal = instantUTC + offset;
926            // If there is a sign change, but the two values have the same sign...
927            if ((instantUTC ^ instantLocal) < 0 && (instantUTC ^ offset) >= 0) {
928                throw new ArithmeticException("Adding time zone offset caused overflow");
929            }
930            return instantLocal;
931        }
932    
933        /**
934         * Converts a local instant to a standard UTC instant with the same
935         * local time attempting to use the same offset as the original.
936         * <p>
937         * This conversion is used after performing a calculation
938         * where the calculation was done using a simple local zone.
939         * Whenever possible, the same offset as the original offset will be used.
940         * This is most significant during a daylight savings overlap.
941         *
942         * @param instantLocal  the local instant to convert to UTC
943         * @param strict  whether the conversion should reject non-existent local times
944         * @param originalInstantUTC  the original instant that the calculation is based on
945         * @return the UTC instant with the same local time, 
946         * @throws ArithmeticException if the result overflows a long
947         * @throws IllegalArgumentException if the zone has no equivalent local time
948         * @since 2.0
949         */
950        public long convertLocalToUTC(long instantLocal, boolean strict, long originalInstantUTC) {
951            int offsetOriginal = getOffset(originalInstantUTC);
952            long instantUTC = instantLocal - offsetOriginal;
953            int offsetLocalFromOriginal = getOffset(instantUTC);
954            if (offsetLocalFromOriginal == offsetOriginal) {
955                return instantUTC;
956            }
957            return convertLocalToUTC(instantLocal, strict);
958        }
959    
960        /**
961         * Converts a local instant to a standard UTC instant with the same
962         * local time. This conversion is used after performing a calculation
963         * where the calculation was done using a simple local zone.
964         *
965         * @param instantLocal  the local instant to convert to UTC
966         * @param strict  whether the conversion should reject non-existent local times
967         * @return the UTC instant with the same local time, 
968         * @throws ArithmeticException if the result overflows a long
969         * @throws IllegalInstantException if the zone has no equivalent local time
970         * @since 1.5
971         */
972        public long convertLocalToUTC(long instantLocal, boolean strict) {
973            // get the offset at instantLocal (first estimate)
974            int offsetLocal = getOffset(instantLocal);
975            // adjust instantLocal using the estimate and recalc the offset
976            int offset = getOffset(instantLocal - offsetLocal);
977            // if the offsets differ, we must be near a DST boundary
978            if (offsetLocal != offset) {
979                // if strict then always check if in DST gap
980                // otherwise only check if zone in Western hemisphere (as the
981                // value of offset is already correct for Eastern hemisphere)
982                if (strict || offsetLocal < 0) {
983                    // determine if we are in the DST gap
984                    long nextLocal = nextTransition(instantLocal - offsetLocal);
985                    if (nextLocal == (instantLocal - offsetLocal)) {
986                        nextLocal = Long.MAX_VALUE;
987                    }
988                    long nextAdjusted = nextTransition(instantLocal - offset);
989                    if (nextAdjusted == (instantLocal - offset)) {
990                        nextAdjusted = Long.MAX_VALUE;
991                    }
992                    if (nextLocal != nextAdjusted) {
993                        // yes we are in the DST gap
994                        if (strict) {
995                            // DST gap is not acceptable
996                            throw new IllegalInstantException(instantLocal, getID());
997                        } else {
998                            // DST gap is acceptable, but for the Western hemisphere
999                            // the offset is wrong and will result in local times
1000                            // before the cutover so use the offsetLocal instead
1001                            offset = offsetLocal;
1002                        }
1003                    }
1004                }
1005            }
1006            // check for overflow
1007            long instantUTC = instantLocal - offset;
1008            // If there is a sign change, but the two values have different signs...
1009            if ((instantLocal ^ instantUTC) < 0 && (instantLocal ^ offset) < 0) {
1010                throw new ArithmeticException("Subtracting time zone offset caused overflow");
1011            }
1012            return instantUTC;
1013        }
1014    
1015        /**
1016         * Gets the millisecond instant in another zone keeping the same local time.
1017         * <p>
1018         * The conversion is performed by converting the specified UTC millis to local
1019         * millis in this zone, then converting back to UTC millis in the new zone.
1020         *
1021         * @param newZone  the new zone, null means default
1022         * @param oldInstant  the UTC millisecond instant to convert
1023         * @return the UTC millisecond instant with the same local time in the new zone
1024         */
1025        public long getMillisKeepLocal(DateTimeZone newZone, long oldInstant) {
1026            if (newZone == null) {
1027                newZone = DateTimeZone.getDefault();
1028            }
1029            if (newZone == this) {
1030                return oldInstant;
1031            }
1032            long instantLocal = convertUTCToLocal(oldInstant);
1033            return newZone.convertLocalToUTC(instantLocal, false, oldInstant);
1034        }
1035    
1036    //    //-----------------------------------------------------------------------
1037    //    /**
1038    //     * Checks if the given {@link LocalDateTime} is within an overlap.
1039    //     * <p>
1040    //     * When switching from Daylight Savings Time to standard time there is
1041    //     * typically an overlap where the same clock hour occurs twice. This
1042    //     * method identifies whether the local datetime refers to such an overlap.
1043    //     * 
1044    //     * @param localDateTime  the time to check, not null
1045    //     * @return true if the given datetime refers to an overlap
1046    //     */
1047    //    public boolean isLocalDateTimeOverlap(LocalDateTime localDateTime) {
1048    //        if (isFixed()) {
1049    //            return false;
1050    //        }
1051    //        long instantLocal = localDateTime.toDateTime(DateTimeZone.UTC).getMillis();
1052    //        // get the offset at instantLocal (first estimate)
1053    //        int offsetLocal = getOffset(instantLocal);
1054    //        // adjust instantLocal using the estimate and recalc the offset
1055    //        int offset = getOffset(instantLocal - offsetLocal);
1056    //        // if the offsets differ, we must be near a DST boundary
1057    //        if (offsetLocal != offset) {
1058    //            long nextLocal = nextTransition(instantLocal - offsetLocal);
1059    //            long nextAdjusted = nextTransition(instantLocal - offset);
1060    //            if (nextLocal != nextAdjusted) {
1061    //                // in DST gap
1062    //                return false;
1063    //            }
1064    //            long diff = Math.abs(offset - offsetLocal);
1065    //            DateTime dateTime = localDateTime.toDateTime(this);
1066    //            DateTime adjusted = dateTime.plus(diff);
1067    //            if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
1068    //                    dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
1069    //                    dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
1070    //                return true;
1071    //            }
1072    //            adjusted = dateTime.minus(diff);
1073    //            if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
1074    //                    dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
1075    //                    dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
1076    //                return true;
1077    //            }
1078    //            return false;
1079    //        }
1080    //        return false;
1081    //    }
1082    //        
1083    //        
1084    //        DateTime dateTime = null;
1085    //        try {
1086    //            dateTime = localDateTime.toDateTime(this);
1087    //        } catch (IllegalArgumentException ex) {
1088    //            return false;  // it is a gap, not an overlap
1089    //        }
1090    //        long offset1 = Math.abs(getOffset(dateTime.getMillis() + 1) - getStandardOffset(dateTime.getMillis() + 1));
1091    //        long offset2 = Math.abs(getOffset(dateTime.getMillis() - 1) - getStandardOffset(dateTime.getMillis() - 1));
1092    //        long offset = Math.max(offset1, offset2);
1093    //        if (offset == 0) {
1094    //            return false;
1095    //        }
1096    //        DateTime adjusted = dateTime.plus(offset);
1097    //        if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
1098    //                dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
1099    //                dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
1100    //            return true;
1101    //        }
1102    //        adjusted = dateTime.minus(offset);
1103    //        if (dateTime.getHourOfDay() == adjusted.getHourOfDay() &&
1104    //                dateTime.getMinuteOfHour() == adjusted.getMinuteOfHour() &&
1105    //                dateTime.getSecondOfMinute() == adjusted.getSecondOfMinute()) {
1106    //            return true;
1107    //        }
1108    //        return false;
1109            
1110    //        long millis = dateTime.getMillis();
1111    //        long nextTransition = nextTransition(millis);
1112    //        long previousTransition = previousTransition(millis);
1113    //        long deltaToPreviousTransition = millis - previousTransition;
1114    //        long deltaToNextTransition = nextTransition - millis;
1115    //        if (deltaToNextTransition < deltaToPreviousTransition) {
1116    //            int offset = getOffset(nextTransition);
1117    //            int standardOffset = getStandardOffset(nextTransition);
1118    //            if (Math.abs(offset - standardOffset) >= deltaToNextTransition) {
1119    //                return true;
1120    //            }
1121    //        } else  {
1122    //            int offset = getOffset(previousTransition);
1123    //            int standardOffset = getStandardOffset(previousTransition);
1124    //            if (Math.abs(offset - standardOffset) >= deltaToPreviousTransition) {
1125    //                return true;
1126    //            }
1127    //        }
1128    //        return false;
1129    //    }
1130    
1131        /**
1132         * Checks if the given {@link LocalDateTime} is within a gap.
1133         * <p>
1134         * When switching from standard time to Daylight Savings Time there is
1135         * typically a gap where a clock hour is missing. This method identifies
1136         * whether the local datetime refers to such a gap.
1137         * 
1138         * @param localDateTime  the time to check, not null
1139         * @return true if the given datetime refers to a gap
1140         * @since 1.6
1141         */
1142        public boolean isLocalDateTimeGap(LocalDateTime localDateTime) {
1143            if (isFixed()) {
1144                return false;
1145            }
1146            try {
1147                localDateTime.toDateTime(this);
1148                return false;
1149            } catch (IllegalInstantException ex) {
1150                return true;
1151            }
1152        }
1153    
1154        /**
1155         * Adjusts the offset to be the earlier or later one during an overlap.
1156         * 
1157         * @param instant  the instant to adjust
1158         * @param earlierOrLater  false for earlier, true for later
1159         * @return the adjusted instant millis
1160         */
1161        public long adjustOffset(long instant, boolean earlierOrLater) {
1162            // a bit messy, but will work in all non-pathological cases
1163            
1164            // evaluate 3 hours before and after to work out if anything is happening
1165            long instantBefore = instant - 3 * DateTimeConstants.MILLIS_PER_HOUR;
1166            long instantAfter = instant + 3 * DateTimeConstants.MILLIS_PER_HOUR;
1167            long offsetBefore = getOffset(instantBefore);
1168            long offsetAfter = getOffset(instantAfter);
1169            if (offsetBefore <= offsetAfter) {
1170                return instant;  // not an overlap (less than is a gap, equal is normal case)
1171            }
1172            
1173            // work out range of instants that have duplicate local times
1174            long diff = offsetBefore - offsetAfter;
1175            long transition = nextTransition(instantBefore);
1176            long overlapStart = transition - diff;
1177            long overlapEnd = transition + diff;
1178            if (instant < overlapStart || instant >= overlapEnd) {
1179              return instant;  // not an overlap
1180            }
1181            
1182            // calculate result
1183            long afterStart = instant - overlapStart;
1184            if (afterStart >= diff) {
1185              // currently in later offset
1186              return earlierOrLater ? instant : instant - diff;
1187            } else {
1188              // currently in earlier offset
1189              return earlierOrLater ? instant + diff : instant;
1190            }
1191        }
1192    //    System.out.println(new DateTime(transitionStart, DateTimeZone.UTC) + " " + new DateTime(transitionStart, this));
1193    
1194        //-----------------------------------------------------------------------
1195        /**
1196         * Returns true if this time zone has no transitions.
1197         *
1198         * @return true if no transitions
1199         */
1200        public abstract boolean isFixed();
1201    
1202        /**
1203         * Advances the given instant to where the time zone offset or name changes.
1204         * If the instant returned is exactly the same as passed in, then
1205         * no changes occur after the given instant.
1206         *
1207         * @param instant  milliseconds from 1970-01-01T00:00:00Z
1208         * @return milliseconds from 1970-01-01T00:00:00Z
1209         */
1210        public abstract long nextTransition(long instant);
1211    
1212        /**
1213         * Retreats the given instant to where the time zone offset or name changes.
1214         * If the instant returned is exactly the same as passed in, then
1215         * no changes occur before the given instant.
1216         *
1217         * @param instant  milliseconds from 1970-01-01T00:00:00Z
1218         * @return milliseconds from 1970-01-01T00:00:00Z
1219         */
1220        public abstract long previousTransition(long instant);
1221    
1222        // Basic methods
1223        //--------------------------------------------------------------------
1224    
1225        /**
1226         * Get the datetime zone as a {@link java.util.TimeZone}.
1227         * 
1228         * @return the closest matching TimeZone object
1229         */
1230        public java.util.TimeZone toTimeZone() {
1231            return java.util.TimeZone.getTimeZone(iID);
1232        }
1233    
1234        /**
1235         * Compare this datetime zone with another.
1236         * 
1237         * @param object the object to compare with
1238         * @return true if equal, based on the ID and all internal rules
1239         */
1240        public abstract boolean equals(Object object);
1241    
1242        /**
1243         * Gets a hash code compatable with equals.
1244         * 
1245         * @return suitable hashcode
1246         */
1247        public int hashCode() {
1248            return 57 + getID().hashCode();
1249        }
1250    
1251        /**
1252         * Gets the datetime zone as a string, which is simply its ID.
1253         * @return the id of the zone
1254         */
1255        public String toString() {
1256            return getID();
1257        }
1258    
1259        /**
1260         * By default, when DateTimeZones are serialized, only a "stub" object
1261         * referring to the id is written out. When the stub is read in, it
1262         * replaces itself with a DateTimeZone object.
1263         * @return a stub object to go in the stream
1264         */
1265        protected Object writeReplace() throws ObjectStreamException {
1266            return new Stub(iID);
1267        }
1268    
1269        /**
1270         * Used to serialize DateTimeZones by id.
1271         */
1272        private static final class Stub implements Serializable {
1273            /** Serialization lock. */
1274            private static final long serialVersionUID = -6471952376487863581L;
1275            /** The ID of the zone. */
1276            private transient String iID;
1277    
1278            /**
1279             * Constructor.
1280             * @param id  the id of the zone
1281             */
1282            Stub(String id) {
1283                iID = id;
1284            }
1285    
1286            private void writeObject(ObjectOutputStream out) throws IOException {
1287                out.writeUTF(iID);
1288            }
1289    
1290            private void readObject(ObjectInputStream in) throws IOException {
1291                iID = in.readUTF();
1292            }
1293    
1294            private Object readResolve() throws ObjectStreamException {
1295                return forID(iID);
1296            }
1297        }
1298    
1299    }