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 }