001: /*
002: * Copyright 2001-2005 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.util.HashMap;
019: import java.util.Map;
020:
021: import org.joda.time.Chronology;
022: import org.joda.time.DateTimeConstants;
023: import org.joda.time.DateTimeZone;
024:
025: /**
026: * Implements a pure proleptic Gregorian calendar system, which defines every
027: * fourth year as leap, unless the year is divisible by 100 and not by 400.
028: * This improves upon the Julian calendar leap year rule.
029: * <p>
030: * Although the Gregorian calendar did not exist before 1582 CE, this
031: * chronology assumes it did, thus it is proleptic. This implementation also
032: * fixes the start of the year at January 1, and defines the year zero.
033: * <p>
034: * GregorianChronology is thread-safe and immutable.
035: *
036: * @see <a href="http://en.wikipedia.org/wiki/Gregorian_calendar">Wikipedia</a>
037: * @see JulianChronology
038: * @see GJChronology
039: *
040: * @author Guy Allard
041: * @author Stephen Colebourne
042: * @author Brian S O'Neill
043: * @since 1.0
044: */
045: public final class GregorianChronology extends BasicGJChronology {
046:
047: /** Serialization lock */
048: private static final long serialVersionUID = -861407383323710522L;
049:
050: private static final long MILLIS_PER_YEAR = (long) (365.2425 * DateTimeConstants.MILLIS_PER_DAY);
051:
052: private static final long MILLIS_PER_MONTH = (long) (365.2425 * DateTimeConstants.MILLIS_PER_DAY / 12);
053:
054: private static final int DAYS_0000_TO_1970 = 719527;
055:
056: /** The lowest year that can be fully supported. */
057: private static final int MIN_YEAR = -292275054;
058:
059: /** The highest year that can be fully supported. */
060: private static final int MAX_YEAR = 292278993;
061:
062: /** Singleton instance of a UTC GregorianChronology */
063: private static final GregorianChronology INSTANCE_UTC;
064:
065: /** Cache of zone to chronology arrays */
066: private static final Map cCache = new HashMap();
067:
068: static {
069: INSTANCE_UTC = getInstance(DateTimeZone.UTC);
070: }
071:
072: /**
073: * Gets an instance of the GregorianChronology.
074: * The time zone of the returned instance is UTC.
075: *
076: * @return a singleton UTC instance of the chronology
077: */
078: public static GregorianChronology getInstanceUTC() {
079: return INSTANCE_UTC;
080: }
081:
082: /**
083: * Gets an instance of the GregorianChronology in the default time zone.
084: *
085: * @return a chronology in the default time zone
086: */
087: public static GregorianChronology getInstance() {
088: return getInstance(DateTimeZone.getDefault(), 4);
089: }
090:
091: /**
092: * Gets an instance of the GregorianChronology in the given time zone.
093: *
094: * @param zone the time zone to get the chronology in, null is default
095: * @return a chronology in the specified time zone
096: */
097: public static GregorianChronology getInstance(DateTimeZone zone) {
098: return getInstance(zone, 4);
099: }
100:
101: /**
102: * Gets an instance of the GregorianChronology in the given time zone.
103: *
104: * @param zone the time zone to get the chronology in, null is default
105: * @param minDaysInFirstWeek minimum number of days in first week of the year; default is 4
106: * @return a chronology in the specified time zone
107: */
108: public static GregorianChronology getInstance(DateTimeZone zone,
109: int minDaysInFirstWeek) {
110: if (zone == null) {
111: zone = DateTimeZone.getDefault();
112: }
113: GregorianChronology chrono;
114: synchronized (cCache) {
115: GregorianChronology[] chronos = (GregorianChronology[]) cCache
116: .get(zone);
117: if (chronos == null) {
118: chronos = new GregorianChronology[7];
119: cCache.put(zone, chronos);
120: }
121: try {
122: chrono = chronos[minDaysInFirstWeek - 1];
123: } catch (ArrayIndexOutOfBoundsException e) {
124: throw new IllegalArgumentException(
125: "Invalid min days in first week: "
126: + minDaysInFirstWeek);
127: }
128: if (chrono == null) {
129: if (zone == DateTimeZone.UTC) {
130: chrono = new GregorianChronology(null, null,
131: minDaysInFirstWeek);
132: } else {
133: chrono = getInstance(DateTimeZone.UTC,
134: minDaysInFirstWeek);
135: chrono = new GregorianChronology(ZonedChronology
136: .getInstance(chrono, zone), null,
137: minDaysInFirstWeek);
138: }
139: chronos[minDaysInFirstWeek - 1] = chrono;
140: }
141: }
142: return chrono;
143: }
144:
145: // Constructors and instance variables
146: //-----------------------------------------------------------------------
147:
148: /**
149: * Restricted constructor
150: */
151: private GregorianChronology(Chronology base, Object param,
152: int minDaysInFirstWeek) {
153: super (base, param, minDaysInFirstWeek);
154: }
155:
156: /**
157: * Serialization singleton
158: */
159: private Object readResolve() {
160: Chronology base = getBase();
161: int minDays = getMinimumDaysInFirstWeek();
162: minDays = (minDays == 0 ? 4 : minDays); // handle rename of BaseGJChronology
163: return base == null ? getInstance(DateTimeZone.UTC, minDays)
164: : getInstance(base.getZone(), minDays);
165: }
166:
167: // Conversion
168: //-----------------------------------------------------------------------
169: /**
170: * Gets the Chronology in the UTC time zone.
171: *
172: * @return the chronology in UTC
173: */
174: public Chronology withUTC() {
175: return INSTANCE_UTC;
176: }
177:
178: /**
179: * Gets the Chronology in a specific time zone.
180: *
181: * @param zone the zone to get the chronology in, null is default
182: * @return the chronology
183: */
184: public Chronology withZone(DateTimeZone zone) {
185: if (zone == null) {
186: zone = DateTimeZone.getDefault();
187: }
188: if (zone == getZone()) {
189: return this ;
190: }
191: return getInstance(zone);
192: }
193:
194: protected void assemble(Fields fields) {
195: if (getBase() == null) {
196: super .assemble(fields);
197: }
198: }
199:
200: boolean isLeapYear(int year) {
201: return ((year & 3) == 0)
202: && ((year % 100) != 0 || (year % 400) == 0);
203: }
204:
205: long calculateFirstDayOfYearMillis(int year) {
206: // Initial value is just temporary.
207: int leapYears = year / 100;
208: if (year < 0) {
209: // Add 3 before shifting right since /4 and >>2 behave differently
210: // on negative numbers. When the expression is written as
211: // (year / 4) - (year / 100) + (year / 400),
212: // it works for both positive and negative values, except this optimization
213: // eliminates two divisions.
214: leapYears = ((year + 3) >> 2) - leapYears
215: + ((leapYears + 3) >> 2) - 1;
216: } else {
217: leapYears = (year >> 2) - leapYears + (leapYears >> 2);
218: if (isLeapYear(year)) {
219: leapYears--;
220: }
221: }
222:
223: return (year * 365L + (leapYears - DAYS_0000_TO_1970))
224: * DateTimeConstants.MILLIS_PER_DAY;
225: }
226:
227: int getMinYear() {
228: return MIN_YEAR;
229: }
230:
231: int getMaxYear() {
232: return MAX_YEAR;
233: }
234:
235: long getAverageMillisPerYear() {
236: return MILLIS_PER_YEAR;
237: }
238:
239: long getAverageMillisPerYearDividedByTwo() {
240: return MILLIS_PER_YEAR / 2;
241: }
242:
243: long getAverageMillisPerMonth() {
244: return MILLIS_PER_MONTH;
245: }
246:
247: long getApproxMillisAtEpochDividedByTwo() {
248: return (1970L * MILLIS_PER_YEAR) / 2;
249: }
250:
251: }
|