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.DateTimeFieldType;
024: import org.joda.time.DateTimeZone;
025: import org.joda.time.IllegalFieldValueException;
026: import org.joda.time.field.SkipDateTimeField;
027:
028: /**
029: * Implements a pure proleptic Julian calendar system, which defines every
030: * fourth year as leap. This implementation follows the leap year rule
031: * strictly, even for dates before 8 CE, where leap years were actually
032: * irregular. In the Julian calendar, year zero does not exist: 1 BCE is
033: * followed by 1 CE.
034: * <p>
035: * Although the Julian calendar did not exist before 45 BCE, this chronology
036: * assumes it did, thus it is proleptic. This implementation also fixes the
037: * start of the year at January 1.
038: * <p>
039: * JulianChronology is thread-safe and immutable.
040: *
041: * @see <a href="http://en.wikipedia.org/wiki/Julian_calendar">Wikipedia</a>
042: * @see GregorianChronology
043: * @see GJChronology
044: *
045: * @author Guy Allard
046: * @author Brian S O'Neill
047: * @author Stephen Colebourne
048: * @since 1.0
049: */
050: public final class JulianChronology extends BasicGJChronology {
051:
052: /** Serialization lock */
053: private static final long serialVersionUID = -8731039522547897247L;
054:
055: private static final long MILLIS_PER_YEAR = (long) (365.25 * DateTimeConstants.MILLIS_PER_DAY);
056:
057: private static final long MILLIS_PER_MONTH = (long) (365.25 * DateTimeConstants.MILLIS_PER_DAY / 12);
058:
059: /** The lowest year that can be fully supported. */
060: private static final int MIN_YEAR = -292269054;
061:
062: /** The highest year that can be fully supported. */
063: private static final int MAX_YEAR = 292272992;
064:
065: /** Singleton instance of a UTC JulianChronology */
066: private static final JulianChronology INSTANCE_UTC;
067:
068: /** Cache of zone to chronology arrays */
069: private static final Map cCache = new HashMap();
070:
071: static {
072: INSTANCE_UTC = getInstance(DateTimeZone.UTC);
073: }
074:
075: static int adjustYearForSet(int year) {
076: if (year <= 0) {
077: if (year == 0) {
078: throw new IllegalFieldValueException(DateTimeFieldType
079: .year(), new Integer(year), null, null);
080: }
081: year++;
082: }
083: return year;
084: }
085:
086: /**
087: * Gets an instance of the JulianChronology.
088: * The time zone of the returned instance is UTC.
089: *
090: * @return a singleton UTC instance of the chronology
091: */
092: public static JulianChronology getInstanceUTC() {
093: return INSTANCE_UTC;
094: }
095:
096: /**
097: * Gets an instance of the JulianChronology in the default time zone.
098: *
099: * @return a chronology in the default time zone
100: */
101: public static JulianChronology getInstance() {
102: return getInstance(DateTimeZone.getDefault(), 4);
103: }
104:
105: /**
106: * Gets an instance of the JulianChronology in the given time zone.
107: *
108: * @param zone the time zone to get the chronology in, null is default
109: * @return a chronology in the specified time zone
110: */
111: public static JulianChronology getInstance(DateTimeZone zone) {
112: return getInstance(zone, 4);
113: }
114:
115: /**
116: * Gets an instance of the JulianChronology in the given time zone.
117: *
118: * @param zone the time zone to get the chronology in, null is default
119: * @param minDaysInFirstWeek minimum number of days in first week of the year; default is 4
120: * @return a chronology in the specified time zone
121: */
122: public static JulianChronology getInstance(DateTimeZone zone,
123: int minDaysInFirstWeek) {
124: if (zone == null) {
125: zone = DateTimeZone.getDefault();
126: }
127: JulianChronology chrono;
128: synchronized (cCache) {
129: JulianChronology[] chronos = (JulianChronology[]) cCache
130: .get(zone);
131: if (chronos == null) {
132: chronos = new JulianChronology[7];
133: cCache.put(zone, chronos);
134: }
135: try {
136: chrono = chronos[minDaysInFirstWeek - 1];
137: } catch (ArrayIndexOutOfBoundsException e) {
138: throw new IllegalArgumentException(
139: "Invalid min days in first week: "
140: + minDaysInFirstWeek);
141: }
142: if (chrono == null) {
143: if (zone == DateTimeZone.UTC) {
144: chrono = new JulianChronology(null, null,
145: minDaysInFirstWeek);
146: } else {
147: chrono = getInstance(DateTimeZone.UTC,
148: minDaysInFirstWeek);
149: chrono = new JulianChronology(ZonedChronology
150: .getInstance(chrono, zone), null,
151: minDaysInFirstWeek);
152: }
153: chronos[minDaysInFirstWeek - 1] = chrono;
154: }
155: }
156: return chrono;
157: }
158:
159: // Constructors and instance variables
160: //-----------------------------------------------------------------------
161:
162: /**
163: * Restricted constructor
164: */
165: JulianChronology(Chronology base, Object param,
166: int minDaysInFirstWeek) {
167: super (base, param, minDaysInFirstWeek);
168: }
169:
170: /**
171: * Serialization singleton
172: */
173: private Object readResolve() {
174: Chronology base = getBase();
175: int minDays = getMinimumDaysInFirstWeek();
176: minDays = (minDays == 0 ? 4 : minDays); // handle rename of BaseGJChronology
177: return base == null ? getInstance(DateTimeZone.UTC, minDays)
178: : getInstance(base.getZone(), minDays);
179: }
180:
181: // Conversion
182: //-----------------------------------------------------------------------
183: /**
184: * Gets the Chronology in the UTC time zone.
185: *
186: * @return the chronology in UTC
187: */
188: public Chronology withUTC() {
189: return INSTANCE_UTC;
190: }
191:
192: /**
193: * Gets the Chronology in a specific time zone.
194: *
195: * @param zone the zone to get the chronology in, null is default
196: * @return the chronology
197: */
198: public Chronology withZone(DateTimeZone zone) {
199: if (zone == null) {
200: zone = DateTimeZone.getDefault();
201: }
202: if (zone == getZone()) {
203: return this ;
204: }
205: return getInstance(zone);
206: }
207:
208: long getDateMidnightMillis(int year, int monthOfYear, int dayOfMonth)
209: throws IllegalArgumentException {
210: return super .getDateMidnightMillis(adjustYearForSet(year),
211: monthOfYear, dayOfMonth);
212: }
213:
214: boolean isLeapYear(int year) {
215: return (year & 3) == 0;
216: }
217:
218: long calculateFirstDayOfYearMillis(int year) {
219: // Java epoch is 1970-01-01 Gregorian which is 1969-12-19 Julian.
220: // Calculate relative to the nearest leap year and account for the
221: // difference later.
222:
223: int relativeYear = year - 1968;
224: int leapYears;
225: if (relativeYear <= 0) {
226: // Add 3 before shifting right since /4 and >>2 behave differently
227: // on negative numbers.
228: leapYears = (relativeYear + 3) >> 2;
229: } else {
230: leapYears = relativeYear >> 2;
231: // For post 1968 an adjustment is needed as jan1st is before leap day
232: if (!isLeapYear(year)) {
233: leapYears++;
234: }
235: }
236:
237: long millis = (relativeYear * 365L + leapYears)
238: * (long) DateTimeConstants.MILLIS_PER_DAY;
239:
240: // Adjust to account for difference between 1968-01-01 and 1969-12-19.
241:
242: return millis - (366L + 352) * DateTimeConstants.MILLIS_PER_DAY;
243: }
244:
245: int getMinYear() {
246: return MIN_YEAR;
247: }
248:
249: int getMaxYear() {
250: return MAX_YEAR;
251: }
252:
253: long getAverageMillisPerYear() {
254: return MILLIS_PER_YEAR;
255: }
256:
257: long getAverageMillisPerYearDividedByTwo() {
258: return MILLIS_PER_YEAR / 2;
259: }
260:
261: long getAverageMillisPerMonth() {
262: return MILLIS_PER_MONTH;
263: }
264:
265: long getApproxMillisAtEpochDividedByTwo() {
266: return (1969L * MILLIS_PER_YEAR + 352L * DateTimeConstants.MILLIS_PER_DAY) / 2;
267: }
268:
269: protected void assemble(Fields fields) {
270: if (getBase() == null) {
271: super .assemble(fields);
272: // Julian chronology has no year zero.
273: fields.year = new SkipDateTimeField(this , fields.year);
274: fields.weekyear = new SkipDateTimeField(this,
275: fields.weekyear);
276: }
277: }
278:
279: }
|