1 /*
2  *  Copyright 2001-2014 Stephen Colebourne
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */

16 package org.joda.time.chrono;
17
18 import java.io.IOException;
19 import java.io.ObjectInputStream;
20 import java.io.ObjectOutputStream;
21 import java.io.Serializable;
22 import java.util.concurrent.ConcurrentHashMap;
23
24 import org.joda.time.Chronology;
25 import org.joda.time.DateTimeFieldType;
26 import org.joda.time.DateTimeZone;
27 import org.joda.time.field.DividedDateTimeField;
28 import org.joda.time.field.RemainderDateTimeField;
29
30 /**
31  * Implements a chronology that follows the rules of the ISO8601 standard,
32  * which is compatible with Gregorian for all modern dates.
33  * When ISO does not define a field, but it can be determined (such as AM/PM)
34  * it is included.
35  * <p>
36  * With the exception of century related fields, ISOChronology is exactly the
37  * same as {@link GregorianChronology}. In this chronology, centuries and year
38  * of century are zero based. For all years, the century is determined by
39  * dropping the last two digits of the year, ignoring sign. The year of century
40  * is the value of the last two year digits.
41  * <p>
42  * ISOChronology is thread-safe and immutable.
43  *
44  * @author Stephen Colebourne
45  * @author Brian S O'Neill
46  * @since 1.0
47  */

48 public final class ISOChronology extends AssembledChronology {
49
50     /** Serialization lock */
51     private static final long serialVersionUID = -6212696554273812441L;
52
53     /** Singleton instance of a UTC ISOChronology */
54     private static final ISOChronology INSTANCE_UTC;
55
56     /** Cache of zone to chronology */
57     private static final ConcurrentHashMap<DateTimeZone, ISOChronology> cCache = new ConcurrentHashMap<DateTimeZone, ISOChronology>();
58     static {
59         INSTANCE_UTC = new ISOChronology(GregorianChronology.getInstanceUTC());
60         cCache.put(DateTimeZone.UTC, INSTANCE_UTC);
61     }
62
63     /**
64      * Gets an instance of the ISOChronology.
65      * The time zone of the returned instance is UTC.
66      * 
67      * @return a singleton UTC instance of the chronology
68      */

69     public static ISOChronology getInstanceUTC() {
70         return INSTANCE_UTC;
71     }
72
73     /**
74      * Gets an instance of the ISOChronology in the default time zone.
75      * 
76      * @return a chronology in the default time zone
77      */

78     public static ISOChronology getInstance() {
79         return getInstance(DateTimeZone.getDefault());
80     }
81
82     /**
83      * Gets an instance of the ISOChronology in the given time zone.
84      * 
85      * @param zone  the time zone to get the chronology in, null is default
86      * @return a chronology in the specified time zone
87      */

88     public static ISOChronology getInstance(DateTimeZone zone) {
89         if (zone == null) {
90             zone = DateTimeZone.getDefault();
91         }
92         ISOChronology chrono = cCache.get(zone);
93         if (chrono == null) {
94             chrono = new ISOChronology(ZonedChronology.getInstance(INSTANCE_UTC, zone));
95             ISOChronology oldChrono = cCache.putIfAbsent(zone, chrono);
96             if (oldChrono != null) {
97                 chrono = oldChrono;
98             }
99         }
100         return chrono;
101     }
102
103     // Constructors and instance variables
104     //-----------------------------------------------------------------------
105
106     /**
107      * Restricted constructor
108      */

109     private ISOChronology(Chronology base) {
110         super(base, null);
111     }
112
113     // Conversion
114     //-----------------------------------------------------------------------
115     /**
116      * Gets the Chronology in the UTC time zone.
117      * 
118      * @return the chronology in UTC
119      */

120     public Chronology withUTC() {
121         return INSTANCE_UTC;
122     }
123
124     /**
125      * Gets the Chronology in a specific time zone.
126      * 
127      * @param zone  the zone to get the chronology in, null is default
128      * @return the chronology
129      */

130     public Chronology withZone(DateTimeZone zone) {
131         if (zone == null) {
132             zone = DateTimeZone.getDefault();
133         }
134         if (zone == getZone()) {
135             return this;
136         }
137         return getInstance(zone);
138     }
139
140     // Output
141     //-----------------------------------------------------------------------
142     /**
143      * Gets a debugging toString.
144      * 
145      * @return a debugging string
146      */

147     public String toString() {
148         String str = "ISOChronology";
149         DateTimeZone zone = getZone();
150         if (zone != null) {
151             str = str + '[' + zone.getID() + ']';
152         }
153         return str;
154     }
155
156     protected void assemble(Fields fields) {
157         if (getBase().getZone() == DateTimeZone.UTC) {
158             // Use zero based century and year of century.
159             fields.centuryOfEra = new DividedDateTimeField(
160                 ISOYearOfEraDateTimeField.INSTANCE, DateTimeFieldType.centuryOfEra(), 100);
161             fields.centuries = fields.centuryOfEra.getDurationField();
162             
163             fields.yearOfCentury = new RemainderDateTimeField(
164                 (DividedDateTimeField) fields.centuryOfEra, DateTimeFieldType.yearOfCentury());
165             fields.weekyearOfCentury = new RemainderDateTimeField(
166                 (DividedDateTimeField) fields.centuryOfEra, fields.weekyears, DateTimeFieldType.weekyearOfCentury());
167         }
168     }
169
170     //-----------------------------------------------------------------------
171     /**
172      * Checks if this chronology instance equals another.
173      * 
174      * @param obj  the object to compare to
175      * @return true if equal
176      * @since 1.6
177      */

178     public boolean equals(Object obj) {
179         if (this == obj) {
180             return true;
181         }
182         if (obj instanceof ISOChronology) {
183             ISOChronology chrono = (ISOChronology) obj;
184             return getZone().equals(chrono.getZone());
185         }
186         return false;
187     }
188
189     /**
190      * A suitable hash code for the chronology.
191      * 
192      * @return the hash code
193      * @since 1.6
194      */

195     public int hashCode() {
196         return "ISO".hashCode() * 11 + getZone().hashCode();
197     }
198
199     //-----------------------------------------------------------------------
200     /**
201      * Serialize ISOChronology instances using a small stub. This reduces the
202      * serialized size, and deserialized instances come from the cache.
203      */

204     private Object writeReplace() {
205         return new Stub(getZone());
206     }
207
208     private static final class Stub implements Serializable {
209         private static final long serialVersionUID = -6212696554273812441L;
210
211         private transient DateTimeZone iZone;
212
213         Stub(DateTimeZone zone) {
214             iZone = zone;
215         }
216
217         private Object readResolve() {
218             return ISOChronology.getInstance(iZone);
219         }
220
221         private void writeObject(ObjectOutputStream out) throws IOException {
222             out.writeObject(iZone);
223         }
224
225         private void readObject(ObjectInputStream in)
226             throws IOException, ClassNotFoundException
227         {
228             iZone = (DateTimeZone)in.readObject();
229         }
230     }
231
232 }
233