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