001    /*
002     *  Copyright 2001-2009 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.chrono;
017    
018    import java.io.IOException;
019    import java.io.ObjectInputStream;
020    import java.io.ObjectOutputStream;
021    import java.io.Serializable;
022    import java.util.HashMap;
023    import java.util.Map;
024    
025    import org.joda.time.Chronology;
026    import org.joda.time.DateTimeFieldType;
027    import org.joda.time.DateTimeZone;
028    import org.joda.time.field.DividedDateTimeField;
029    import org.joda.time.field.RemainderDateTimeField;
030    
031    /**
032     * Implements a chronology that follows the rules of the ISO8601 standard,
033     * which is compatible with Gregorian for all modern dates.
034     * When ISO does not define a field, but it can be determined (such as AM/PM)
035     * it is included.
036     * <p>
037     * With the exception of century related fields, ISOChronology is exactly the
038     * same as {@link GregorianChronology}. In this chronology, centuries and year
039     * of century are zero based. For all years, the century is determined by
040     * dropping the last two digits of the year, ignoring sign. The year of century
041     * is the value of the last two year digits.
042     * <p>
043     * ISOChronology is thread-safe and immutable.
044     *
045     * @author Stephen Colebourne
046     * @author Brian S O'Neill
047     * @since 1.0
048     */
049    public final class ISOChronology extends AssembledChronology {
050        
051        /** Serialization lock */
052        private static final long serialVersionUID = -6212696554273812441L;
053    
054        /** Singleton instance of a UTC ISOChronology */
055        private static final ISOChronology INSTANCE_UTC;
056            
057        private static final int FAST_CACHE_SIZE = 64;
058    
059        /** Fast cache of zone to chronology */
060        private static final ISOChronology[] cFastCache;
061    
062        /** Cache of zone to chronology */
063        private static final Map<DateTimeZone, ISOChronology> cCache = new HashMap<DateTimeZone, ISOChronology>();
064        static {
065            cFastCache = new ISOChronology[FAST_CACHE_SIZE];
066            INSTANCE_UTC = new ISOChronology(GregorianChronology.getInstanceUTC());
067            cCache.put(DateTimeZone.UTC, INSTANCE_UTC);
068        }
069    
070        /**
071         * Gets an instance of the ISOChronology.
072         * The time zone of the returned instance is UTC.
073         * 
074         * @return a singleton UTC instance of the chronology
075         */
076        public static ISOChronology getInstanceUTC() {
077            return INSTANCE_UTC;
078        }
079    
080        /**
081         * Gets an instance of the ISOChronology in the default time zone.
082         * 
083         * @return a chronology in the default time zone
084         */
085        public static ISOChronology getInstance() {
086            return getInstance(DateTimeZone.getDefault());
087        }
088    
089        /**
090         * Gets an instance of the ISOChronology in the given time zone.
091         * 
092         * @param zone  the time zone to get the chronology in, null is default
093         * @return a chronology in the specified time zone
094         */
095        public static ISOChronology getInstance(DateTimeZone zone) {
096            if (zone == null) {
097                zone = DateTimeZone.getDefault();
098            }
099            int index = System.identityHashCode(zone) & (FAST_CACHE_SIZE - 1);
100            ISOChronology chrono = cFastCache[index];
101            if (chrono != null && chrono.getZone() == zone) {
102                return chrono;
103            }
104            synchronized (cCache) {
105                chrono = cCache.get(zone);
106                if (chrono == null) {
107                    chrono = new ISOChronology(ZonedChronology.getInstance(INSTANCE_UTC, zone));
108                    cCache.put(zone, chrono);
109                }
110            }
111            cFastCache[index] = chrono;
112            return chrono;
113        }
114    
115        // Constructors and instance variables
116        //-----------------------------------------------------------------------
117    
118        /**
119         * Restricted constructor
120         */
121        private ISOChronology(Chronology base) {
122            super(base, null);
123        }
124    
125        // Conversion
126        //-----------------------------------------------------------------------
127        /**
128         * Gets the Chronology in the UTC time zone.
129         * 
130         * @return the chronology in UTC
131         */
132        public Chronology withUTC() {
133            return INSTANCE_UTC;
134        }
135    
136        /**
137         * Gets the Chronology in a specific time zone.
138         * 
139         * @param zone  the zone to get the chronology in, null is default
140         * @return the chronology
141         */
142        public Chronology withZone(DateTimeZone zone) {
143            if (zone == null) {
144                zone = DateTimeZone.getDefault();
145            }
146            if (zone == getZone()) {
147                return this;
148            }
149            return getInstance(zone);
150        }
151    
152        // Output
153        //-----------------------------------------------------------------------
154        /**
155         * Gets a debugging toString.
156         * 
157         * @return a debugging string
158         */
159        public String toString() {
160            String str = "ISOChronology";
161            DateTimeZone zone = getZone();
162            if (zone != null) {
163                str = str + '[' + zone.getID() + ']';
164            }
165            return str;
166        }
167    
168        protected void assemble(Fields fields) {
169            if (getBase().getZone() == DateTimeZone.UTC) {
170                // Use zero based century and year of century.
171                fields.centuryOfEra = new DividedDateTimeField(
172                    ISOYearOfEraDateTimeField.INSTANCE, DateTimeFieldType.centuryOfEra(), 100);
173                fields.yearOfCentury = new RemainderDateTimeField(
174                    (DividedDateTimeField) fields.centuryOfEra, DateTimeFieldType.yearOfCentury());
175                fields.weekyearOfCentury = new RemainderDateTimeField(
176                    (DividedDateTimeField) fields.centuryOfEra, DateTimeFieldType.weekyearOfCentury());
177    
178                fields.centuries = fields.centuryOfEra.getDurationField();
179            }
180        }
181    
182        /**
183         * Checks if this chronology instance equals another.
184         * 
185         * @param obj  the object to compare to
186         * @return true if equal
187         * @since 1.6
188         */
189        public boolean equals(Object obj) {
190            return super.equals(obj);
191        }
192    
193        /**
194         * A suitable hash code for the chronology.
195         * 
196         * @return the hash code
197         * @since 1.6
198         */
199        public int hashCode() {
200            return "ISO".hashCode() * 11 + getZone().hashCode();
201        }
202    
203        /**
204         * Serialize ISOChronology instances using a small stub. This reduces the
205         * serialized size, and deserialized instances come from the cache.
206         */
207        private Object writeReplace() {
208            return new Stub(getZone());
209        }
210    
211        private static final class Stub implements Serializable {
212            private static final long serialVersionUID = -6212696554273812441L;
213    
214            private transient DateTimeZone iZone;
215    
216            Stub(DateTimeZone zone) {
217                iZone = zone;
218            }
219    
220            private Object readResolve() {
221                return ISOChronology.getInstance(iZone);
222            }
223    
224            private void writeObject(ObjectOutputStream out) throws IOException {
225                out.writeObject(iZone);
226            }
227    
228            private void readObject(ObjectInputStream in)
229                throws IOException, ClassNotFoundException
230            {
231                iZone = (DateTimeZone)in.readObject();
232            }
233        }
234    
235    }