View Javadoc

1   /*
2    *  Copyright 2001-2009 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.util.HashMap;
19  import java.util.Map;
20  
21  import org.joda.time.Chronology;
22  import org.joda.time.DateTime;
23  import org.joda.time.DateTimeConstants;
24  import org.joda.time.DateTimeField;
25  import org.joda.time.DateTimeZone;
26  import org.joda.time.field.SkipDateTimeField;
27  
28  /**
29   * Implements the Coptic calendar system, which defines every fourth year as
30   * leap, much like the Julian calendar. The year is broken down into 12 months,
31   * each 30 days in length. An extra period at the end of the year is either 5
32   * or 6 days in length. In this implementation, it is considered a 13th month.
33   * <p>
34   * Year 1 in the Coptic calendar began on August 29, 284 CE (Julian), thus
35   * Coptic years do not begin at the same time as Julian years. This chronology
36   * is not proleptic, as it does not allow dates before the first Coptic year.
37   * <p>
38   * This implementation defines a day as midnight to midnight exactly as per
39   * the ISO chronology. Some references indicate that a coptic day starts at
40   * sunset on the previous ISO day, but this has not been confirmed and is not
41   * implemented.
42   * <p>
43   * CopticChronology is thread-safe and immutable.
44   *
45   * @see <a href="http://en.wikipedia.org/wiki/Coptic_calendar">Wikipedia</a>
46   * @see JulianChronology
47   *
48   * @author Brian S O'Neill
49   * @since 1.0
50   */
51  public final class CopticChronology extends BasicFixedMonthChronology {
52  
53      /** Serialization lock */
54      private static final long serialVersionUID = -5972804258688333942L;
55  
56      /**
57       * Constant value for 'Anno Martyrum' or 'Era of the Martyrs', equivalent
58       * to the value returned for AD/CE.
59       */
60      public static final int AM = DateTimeConstants.CE;
61  
62      /** A singleton era field. */
63      private static final DateTimeField ERA_FIELD = new BasicSingleEraDateTimeField("AM");
64  
65      /** The lowest year that can be fully supported. */
66      private static final int MIN_YEAR = -292269337;
67  
68      /** The highest year that can be fully supported. */
69      private static final int MAX_YEAR = 292272708;
70  
71      /** Cache of zone to chronology arrays */
72      private static final Map<DateTimeZone, CopticChronology[]> cCache = new HashMap<DateTimeZone, CopticChronology[]>();
73  
74      /** Singleton instance of a UTC CopticChronology */
75      private static final CopticChronology INSTANCE_UTC;
76      static {
77          // init after static fields
78          INSTANCE_UTC = getInstance(DateTimeZone.UTC);
79      }
80  
81      //-----------------------------------------------------------------------
82      /**
83       * Gets an instance of the CopticChronology.
84       * The time zone of the returned instance is UTC.
85       * 
86       * @return a singleton UTC instance of the chronology
87       */
88      public static CopticChronology getInstanceUTC() {
89          return INSTANCE_UTC;
90      }
91  
92      /**
93       * Gets an instance of the CopticChronology in the default time zone.
94       * 
95       * @return a chronology in the default time zone
96       */
97      public static CopticChronology getInstance() {
98          return getInstance(DateTimeZone.getDefault(), 4);
99      }
100 
101     /**
102      * Gets an instance of the CopticChronology in the given time zone.
103      * 
104      * @param zone  the time zone to get the chronology in, null is default
105      * @return a chronology in the specified time zone
106      */
107     public static CopticChronology getInstance(DateTimeZone zone) {
108         return getInstance(zone, 4);
109     }
110 
111     /**
112      * Gets an instance of the CopticChronology in the given time zone.
113      * 
114      * @param zone  the time zone to get the chronology in, null is default
115      * @param minDaysInFirstWeek  minimum number of days in first week of the year; default is 4
116      * @return a chronology in the specified time zone
117      */
118     public static CopticChronology getInstance(DateTimeZone zone, int minDaysInFirstWeek) {
119         if (zone == null) {
120             zone = DateTimeZone.getDefault();
121         }
122         CopticChronology chrono;
123         synchronized (cCache) {
124             CopticChronology[] chronos = cCache.get(zone);
125             if (chronos == null) {
126                 chronos = new CopticChronology[7];
127                 cCache.put(zone, chronos);
128             }
129             try {
130                 chrono = chronos[minDaysInFirstWeek - 1];
131             } catch (ArrayIndexOutOfBoundsException e) {
132                 throw new IllegalArgumentException
133                     ("Invalid min days in first week: " + minDaysInFirstWeek);
134             }
135             if (chrono == null) {
136                 if (zone == DateTimeZone.UTC) {
137                     // First create without a lower limit.
138                     chrono = new CopticChronology(null, null, minDaysInFirstWeek);
139                     // Impose lower limit and make another CopticChronology.
140                     DateTime lowerLimit = new DateTime(1, 1, 1, 0, 0, 0, 0, chrono);
141                     chrono = new CopticChronology
142                         (LimitChronology.getInstance(chrono, lowerLimit, null),
143                          null, minDaysInFirstWeek);
144                 } else {
145                     chrono = getInstance(DateTimeZone.UTC, minDaysInFirstWeek);
146                     chrono = new CopticChronology
147                         (ZonedChronology.getInstance(chrono, zone), null, minDaysInFirstWeek);
148                 }
149                 chronos[minDaysInFirstWeek - 1] = chrono;
150             }
151         }
152         return chrono;
153     }
154 
155     // Constructors and instance variables
156     //-----------------------------------------------------------------------
157     /**
158      * Restricted constructor.
159      */
160     CopticChronology(Chronology base, Object param, int minDaysInFirstWeek) {
161         super(base, param, minDaysInFirstWeek);
162     }
163 
164     /**
165      * Serialization singleton.
166      */
167     private Object readResolve() {
168         Chronology base = getBase();
169         int minDays = getMinimumDaysInFirstWeek();
170         minDays = (minDays == 0 ? 4 : minDays);  // handle rename of BaseGJChronology
171         return base == null ?
172                 getInstance(DateTimeZone.UTC, minDays) :
173                     getInstance(base.getZone(), minDays);
174     }
175 
176     // Conversion
177     //-----------------------------------------------------------------------
178     /**
179      * Gets the Chronology in the UTC time zone.
180      * 
181      * @return the chronology in UTC
182      */
183     public Chronology withUTC() {
184         return INSTANCE_UTC;
185     }
186 
187     /**
188      * Gets the Chronology in a specific time zone.
189      * 
190      * @param zone  the zone to get the chronology in, null is default
191      * @return the chronology
192      */
193     public Chronology withZone(DateTimeZone zone) {
194         if (zone == null) {
195             zone = DateTimeZone.getDefault();
196         }
197         if (zone == getZone()) {
198             return this;
199         }
200         return getInstance(zone);
201     }
202 
203     //-----------------------------------------------------------------------
204     long calculateFirstDayOfYearMillis(int year) {
205         // Java epoch is 1970-01-01 Gregorian which is 1686-04-23 Coptic.
206         // Calculate relative to the nearest leap year and account for the
207         // difference later.
208 
209         int relativeYear = year - 1687;
210         int leapYears;
211         if (relativeYear <= 0) {
212             // Add 3 before shifting right since /4 and >>2 behave differently
213             // on negative numbers.
214             leapYears = (relativeYear + 3) >> 2;
215         } else {
216             leapYears = relativeYear >> 2;
217             // For post 1687 an adjustment is needed as jan1st is before leap day
218             if (!isLeapYear(year)) {
219                 leapYears++;
220             }
221         }
222         
223         long millis = (relativeYear * 365L + leapYears)
224             * (long)DateTimeConstants.MILLIS_PER_DAY;
225 
226         // Adjust to account for difference between 1687-01-01 and 1686-04-23.
227 
228         return millis + (365L - 112) * DateTimeConstants.MILLIS_PER_DAY;
229     }
230 
231     //-----------------------------------------------------------------------
232     int getMinYear() {
233         return MIN_YEAR;
234     }
235 
236     //-----------------------------------------------------------------------
237     int getMaxYear() {
238         return MAX_YEAR;
239     }
240 
241     //-----------------------------------------------------------------------
242     long getApproxMillisAtEpochDividedByTwo() {
243         return (1686L * MILLIS_PER_YEAR + 112L * DateTimeConstants.MILLIS_PER_DAY) / 2;
244     }
245 
246     //-----------------------------------------------------------------------
247     protected void assemble(Fields fields) {
248         if (getBase() == null) {
249             super.assemble(fields);
250 
251             // Coptic, like Julian, has no year zero.
252             fields.year = new SkipDateTimeField(this, fields.year);
253             fields.weekyear = new SkipDateTimeField(this, fields.weekyear);
254             
255             fields.era = ERA_FIELD;
256             fields.monthOfYear = new BasicMonthOfYearDateTimeField(this, 13);
257             fields.months = fields.monthOfYear.getDurationField();
258         }
259     }
260 
261 }