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 */
016package org.joda.time;
017
018import java.io.IOException;
019import java.io.ObjectInputStream;
020import java.io.ObjectOutputStream;
021import java.io.ObjectStreamException;
022import java.io.Serializable;
023import java.lang.ref.Reference;
024import java.lang.ref.SoftReference;
025import java.util.HashMap;
026import java.util.Locale;
027import java.util.Map;
028import java.util.Set;
029import java.util.TimeZone;
030
031import org.joda.convert.FromString;
032import org.joda.convert.ToString;
033import org.joda.time.chrono.BaseChronology;
034import org.joda.time.field.FieldUtils;
035import org.joda.time.format.DateTimeFormat;
036import org.joda.time.format.DateTimeFormatter;
037import org.joda.time.format.DateTimeFormatterBuilder;
038import org.joda.time.format.FormatUtils;
039import org.joda.time.tz.DefaultNameProvider;
040import org.joda.time.tz.FixedDateTimeZone;
041import org.joda.time.tz.NameProvider;
042import org.joda.time.tz.Provider;
043import org.joda.time.tz.UTCProvider;
044import 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 */
089public 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}