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.DateTime;
023: import org.joda.time.DateTimeConstants;
024: import org.joda.time.DateTimeField;
025: import org.joda.time.DateTimeZone;
026: import org.joda.time.field.SkipDateTimeField;
027:
028: /**
029: * Implements the Coptic calendar system, which defines every fourth year as
030: * leap, much like the Julian calendar. The year is broken down into 12 months,
031: * each 30 days in length. An extra period at the end of the year is either 5
032: * or 6 days in length. In this implementation, it is considered a 13th month.
033: * <p>
034: * Year 1 in the Coptic calendar began on August 29, 284 CE (Julian), thus
035: * Coptic years do not begin at the same time as Julian years. This chronology
036: * is not proleptic, as it does not allow dates before the first Coptic year.
037: * <p>
038: * This implementation defines a day as midnight to midnight exactly as per
039: * the ISO chronology. Some references indicate that a coptic day starts at
040: * sunset on the previous ISO day, but this has not been confirmed and is not
041: * implemented.
042: * <p>
043: * CopticChronology is thread-safe and immutable.
044: *
045: * @see <a href="http://en.wikipedia.org/wiki/Coptic_calendar">Wikipedia</a>
046: * @see JulianChronology
047: *
048: * @author Brian S O'Neill
049: * @since 1.0
050: */
051: public final class CopticChronology extends BasicFixedMonthChronology {
052:
053: /** Serialization lock */
054: private static final long serialVersionUID = -5972804258688333942L;
055:
056: /**
057: * Constant value for 'Anno Martyrum' or 'Era of the Martyrs', equivalent
058: * to the value returned for AD/CE.
059: */
060: public static final int AM = DateTimeConstants.CE;
061:
062: /** A singleton era field. */
063: private static final DateTimeField ERA_FIELD = new BasicSingleEraDateTimeField(
064: "AM");
065:
066: /** The lowest year that can be fully supported. */
067: private static final int MIN_YEAR = -292269337;
068:
069: /** The highest year that can be fully supported. */
070: private static final int MAX_YEAR = 292272708;
071:
072: /** Cache of zone to chronology arrays */
073: private static final Map cCache = new HashMap();
074:
075: /** Singleton instance of a UTC CopticChronology */
076: private static final CopticChronology INSTANCE_UTC;
077: static {
078: // init after static fields
079: INSTANCE_UTC = getInstance(DateTimeZone.UTC);
080: }
081:
082: //-----------------------------------------------------------------------
083: /**
084: * Gets an instance of the CopticChronology.
085: * The time zone of the returned instance is UTC.
086: *
087: * @return a singleton UTC instance of the chronology
088: */
089: public static CopticChronology getInstanceUTC() {
090: return INSTANCE_UTC;
091: }
092:
093: /**
094: * Gets an instance of the CopticChronology in the default time zone.
095: *
096: * @return a chronology in the default time zone
097: */
098: public static CopticChronology getInstance() {
099: return getInstance(DateTimeZone.getDefault(), 4);
100: }
101:
102: /**
103: * Gets an instance of the CopticChronology in the given time zone.
104: *
105: * @param zone the time zone to get the chronology in, null is default
106: * @return a chronology in the specified time zone
107: */
108: public static CopticChronology getInstance(DateTimeZone zone) {
109: return getInstance(zone, 4);
110: }
111:
112: /**
113: * Gets an instance of the CopticChronology in the given time zone.
114: *
115: * @param zone the time zone to get the chronology in, null is default
116: * @param minDaysInFirstWeek minimum number of days in first week of the year; default is 4
117: * @return a chronology in the specified time zone
118: */
119: public static CopticChronology getInstance(DateTimeZone zone,
120: int minDaysInFirstWeek) {
121: if (zone == null) {
122: zone = DateTimeZone.getDefault();
123: }
124: CopticChronology chrono;
125: synchronized (cCache) {
126: CopticChronology[] chronos = (CopticChronology[]) cCache
127: .get(zone);
128: if (chronos == null) {
129: chronos = new CopticChronology[7];
130: cCache.put(zone, chronos);
131: }
132: try {
133: chrono = chronos[minDaysInFirstWeek - 1];
134: } catch (ArrayIndexOutOfBoundsException e) {
135: throw new IllegalArgumentException(
136: "Invalid min days in first week: "
137: + minDaysInFirstWeek);
138: }
139: if (chrono == null) {
140: if (zone == DateTimeZone.UTC) {
141: // First create without a lower limit.
142: chrono = new CopticChronology(null, null,
143: minDaysInFirstWeek);
144: // Impose lower limit and make another CopticChronology.
145: DateTime lowerLimit = new DateTime(1, 1, 1, 0, 0,
146: 0, 0, chrono);
147: chrono = new CopticChronology(LimitChronology
148: .getInstance(chrono, lowerLimit, null),
149: null, minDaysInFirstWeek);
150: } else {
151: chrono = getInstance(DateTimeZone.UTC,
152: minDaysInFirstWeek);
153: chrono = new CopticChronology(ZonedChronology
154: .getInstance(chrono, zone), null,
155: minDaysInFirstWeek);
156: }
157: chronos[minDaysInFirstWeek - 1] = chrono;
158: }
159: }
160: return chrono;
161: }
162:
163: // Constructors and instance variables
164: //-----------------------------------------------------------------------
165: /**
166: * Restricted constructor.
167: */
168: CopticChronology(Chronology base, Object param,
169: int minDaysInFirstWeek) {
170: super (base, param, minDaysInFirstWeek);
171: }
172:
173: /**
174: * Serialization singleton.
175: */
176: private Object readResolve() {
177: Chronology base = getBase();
178: int minDays = getMinimumDaysInFirstWeek();
179: minDays = (minDays == 0 ? 4 : minDays); // handle rename of BaseGJChronology
180: return base == null ? getInstance(DateTimeZone.UTC, minDays)
181: : getInstance(base.getZone(), minDays);
182: }
183:
184: // Conversion
185: //-----------------------------------------------------------------------
186: /**
187: * Gets the Chronology in the UTC time zone.
188: *
189: * @return the chronology in UTC
190: */
191: public Chronology withUTC() {
192: return INSTANCE_UTC;
193: }
194:
195: /**
196: * Gets the Chronology in a specific time zone.
197: *
198: * @param zone the zone to get the chronology in, null is default
199: * @return the chronology
200: */
201: public Chronology withZone(DateTimeZone zone) {
202: if (zone == null) {
203: zone = DateTimeZone.getDefault();
204: }
205: if (zone == getZone()) {
206: return this ;
207: }
208: return getInstance(zone);
209: }
210:
211: //-----------------------------------------------------------------------
212: long calculateFirstDayOfYearMillis(int year) {
213: // Java epoch is 1970-01-01 Gregorian which is 1686-04-23 Coptic.
214: // Calculate relative to the nearest leap year and account for the
215: // difference later.
216:
217: int relativeYear = year - 1687;
218: int leapYears;
219: if (relativeYear <= 0) {
220: // Add 3 before shifting right since /4 and >>2 behave differently
221: // on negative numbers.
222: leapYears = (relativeYear + 3) >> 2;
223: } else {
224: leapYears = relativeYear >> 2;
225: // For post 1687 an adjustment is needed as jan1st is before leap day
226: if (!isLeapYear(year)) {
227: leapYears++;
228: }
229: }
230:
231: long millis = (relativeYear * 365L + leapYears)
232: * (long) DateTimeConstants.MILLIS_PER_DAY;
233:
234: // Adjust to account for difference between 1687-01-01 and 1686-04-23.
235:
236: return millis + (365L - 112) * DateTimeConstants.MILLIS_PER_DAY;
237: }
238:
239: //-----------------------------------------------------------------------
240: int getMinYear() {
241: return MIN_YEAR;
242: }
243:
244: //-----------------------------------------------------------------------
245: int getMaxYear() {
246: return MAX_YEAR;
247: }
248:
249: //-----------------------------------------------------------------------
250: long getApproxMillisAtEpochDividedByTwo() {
251: return (1686L * MILLIS_PER_YEAR + 112L * DateTimeConstants.MILLIS_PER_DAY) / 2;
252: }
253:
254: //-----------------------------------------------------------------------
255: protected void assemble(Fields fields) {
256: if (getBase() == null) {
257: super .assemble(fields);
258:
259: // Coptic, like Julian, has no year zero.
260: fields.year = new SkipDateTimeField(this , fields.year);
261: fields.weekyear = new SkipDateTimeField(this ,
262: fields.weekyear);
263:
264: fields.era = ERA_FIELD;
265: fields.monthOfYear = new BasicMonthOfYearDateTimeField(
266: this , 13);
267: fields.months = fields.monthOfYear.getDurationField();
268: }
269: }
270:
271: }
|