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.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 =
056            (long) (365.25 * DateTimeConstants.MILLIS_PER_DAY);
057    
058        private static final long MILLIS_PER_MONTH =
059            (long) (365.25 * DateTimeConstants.MILLIS_PER_DAY / 12);
060    
061        /** The lowest year that can be fully supported. */
062        private static final int MIN_YEAR = -292269054;
063    
064        /** The highest year that can be fully supported. */
065        private static final int MAX_YEAR = 292272992;
066    
067        /** Singleton instance of a UTC JulianChronology */
068        private static final JulianChronology INSTANCE_UTC;
069    
070        /** Cache of zone to chronology arrays */
071        private static final Map<DateTimeZone, JulianChronology[]> cCache = new HashMap<DateTimeZone, JulianChronology[]>();
072    
073        static {
074            INSTANCE_UTC = getInstance(DateTimeZone.UTC);
075        }
076    
077        static int adjustYearForSet(int year) {
078            if (year <= 0) {
079                if (year == 0) {
080                    throw new IllegalFieldValueException
081                        (DateTimeFieldType.year(), Integer.valueOf(year), null, null);
082                }
083                year++;
084            }
085            return year;
086        }
087    
088        /**
089         * Gets an instance of the JulianChronology.
090         * The time zone of the returned instance is UTC.
091         * 
092         * @return a singleton UTC instance of the chronology
093         */
094        public static JulianChronology getInstanceUTC() {
095            return INSTANCE_UTC;
096        }
097    
098        /**
099         * Gets an instance of the JulianChronology in the default time zone.
100         * 
101         * @return a chronology in the default time zone
102         */
103        public static JulianChronology getInstance() {
104            return getInstance(DateTimeZone.getDefault(), 4);
105        }
106    
107        /**
108         * Gets an instance of the JulianChronology in the given time zone.
109         * 
110         * @param zone  the time zone to get the chronology in, null is default
111         * @return a chronology in the specified time zone
112         */
113        public static JulianChronology getInstance(DateTimeZone zone) {
114            return getInstance(zone, 4);
115        }
116    
117        /**
118         * Gets an instance of the JulianChronology in the given time zone.
119         * 
120         * @param zone  the time zone to get the chronology in, null is default
121         * @param minDaysInFirstWeek  minimum number of days in first week of the year; default is 4
122         * @return a chronology in the specified time zone
123         */
124        public static JulianChronology getInstance(DateTimeZone zone, int minDaysInFirstWeek) {
125            if (zone == null) {
126                zone = DateTimeZone.getDefault();
127            }
128            JulianChronology chrono;
129            synchronized (cCache) {
130                JulianChronology[] chronos = cCache.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: " + minDaysInFirstWeek);
140                }
141                if (chrono == null) {
142                    if (zone == DateTimeZone.UTC) {
143                        chrono = new JulianChronology(null, null, minDaysInFirstWeek);
144                    } else {
145                        chrono = getInstance(DateTimeZone.UTC, minDaysInFirstWeek);
146                        chrono = new JulianChronology
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        /**
159         * Restricted constructor
160         */
161        JulianChronology(Chronology base, Object param, int minDaysInFirstWeek) {
162            super(base, param, minDaysInFirstWeek);
163        }
164    
165        /**
166         * Serialization singleton
167         */
168        private Object readResolve() {
169            Chronology base = getBase();
170            int minDays = getMinimumDaysInFirstWeek();
171            minDays = (minDays == 0 ? 4 : minDays);  // handle rename of BaseGJChronology
172            return base == null ?
173                    getInstance(DateTimeZone.UTC, minDays) :
174                        getInstance(base.getZone(), minDays);
175        }
176    
177        // Conversion
178        //-----------------------------------------------------------------------
179        /**
180         * Gets the Chronology in the UTC time zone.
181         * 
182         * @return the chronology in UTC
183         */
184        public Chronology withUTC() {
185            return INSTANCE_UTC;
186        }
187    
188        /**
189         * Gets the Chronology in a specific time zone.
190         * 
191         * @param zone  the zone to get the chronology in, null is default
192         * @return the chronology
193         */
194        public Chronology withZone(DateTimeZone zone) {
195            if (zone == null) {
196                zone = DateTimeZone.getDefault();
197            }
198            if (zone == getZone()) {
199                return this;
200            }
201            return getInstance(zone);
202        }
203    
204        long getDateMidnightMillis(int year, int monthOfYear, int dayOfMonth)
205            throws IllegalArgumentException
206        {
207            return super.getDateMidnightMillis(adjustYearForSet(year), monthOfYear, dayOfMonth);
208        }
209    
210        boolean isLeapYear(int year) {
211            return (year & 3) == 0;
212        }
213    
214        long calculateFirstDayOfYearMillis(int year) {
215            // Java epoch is 1970-01-01 Gregorian which is 1969-12-19 Julian.
216            // Calculate relative to the nearest leap year and account for the
217            // difference later.
218    
219            int relativeYear = year - 1968;
220            int leapYears;
221            if (relativeYear <= 0) {
222                // Add 3 before shifting right since /4 and >>2 behave differently
223                // on negative numbers.
224                leapYears = (relativeYear + 3) >> 2;
225            } else {
226                leapYears = relativeYear >> 2;
227                // For post 1968 an adjustment is needed as jan1st is before leap day
228                if (!isLeapYear(year)) {
229                    leapYears++;
230                }
231            }
232            
233            long millis = (relativeYear * 365L + leapYears) * (long)DateTimeConstants.MILLIS_PER_DAY;
234    
235            // Adjust to account for difference between 1968-01-01 and 1969-12-19.
236    
237            return millis - (366L + 352) * DateTimeConstants.MILLIS_PER_DAY;
238        }
239    
240        int getMinYear() {
241            return MIN_YEAR;
242        }
243    
244        int getMaxYear() {
245            return MAX_YEAR;
246        }
247    
248        long getAverageMillisPerYear() {
249            return MILLIS_PER_YEAR;
250        }
251    
252        long getAverageMillisPerYearDividedByTwo() {
253            return MILLIS_PER_YEAR / 2;
254        }
255    
256        long getAverageMillisPerMonth() {
257            return MILLIS_PER_MONTH;
258        }
259    
260        long getApproxMillisAtEpochDividedByTwo() {
261            return (1969L * MILLIS_PER_YEAR + 352L * DateTimeConstants.MILLIS_PER_DAY) / 2;
262        }
263    
264        protected void assemble(Fields fields) {
265            if (getBase() == null) {
266                super.assemble(fields);
267                // Julian chronology has no year zero.
268                fields.year = new SkipDateTimeField(this, fields.year);
269                fields.weekyear = new SkipDateTimeField(this, fields.weekyear);
270            }
271        }
272    
273    }