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 }