001    /*
002     *  Copyright 2001-2009 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("AM");
064    
065        /** The lowest year that can be fully supported. */
066        private static final int MIN_YEAR = -292269337;
067    
068        /** The highest year that can be fully supported. */
069        private static final int MAX_YEAR = 292272708;
070    
071        /** Cache of zone to chronology arrays */
072        private static final Map<DateTimeZone, CopticChronology[]> cCache = new HashMap<DateTimeZone, CopticChronology[]>();
073    
074        /** Singleton instance of a UTC CopticChronology */
075        private static final CopticChronology INSTANCE_UTC;
076        static {
077            // init after static fields
078            INSTANCE_UTC = getInstance(DateTimeZone.UTC);
079        }
080    
081        //-----------------------------------------------------------------------
082        /**
083         * Gets an instance of the CopticChronology.
084         * The time zone of the returned instance is UTC.
085         * 
086         * @return a singleton UTC instance of the chronology
087         */
088        public static CopticChronology getInstanceUTC() {
089            return INSTANCE_UTC;
090        }
091    
092        /**
093         * Gets an instance of the CopticChronology in the default time zone.
094         * 
095         * @return a chronology in the default time zone
096         */
097        public static CopticChronology getInstance() {
098            return getInstance(DateTimeZone.getDefault(), 4);
099        }
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    }