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.DateTimeConstants;
23  import org.joda.time.DateTimeFieldType;
24  import org.joda.time.DateTimeZone;
25  import org.joda.time.IllegalFieldValueException;
26  import org.joda.time.field.SkipDateTimeField;
27  
28  /**
29   * Implements a pure proleptic Julian calendar system, which defines every
30   * fourth year as leap. This implementation follows the leap year rule
31   * strictly, even for dates before 8 CE, where leap years were actually
32   * irregular. In the Julian calendar, year zero does not exist: 1 BCE is
33   * followed by 1 CE.
34   * <p>
35   * Although the Julian calendar did not exist before 45 BCE, this chronology
36   * assumes it did, thus it is proleptic. This implementation also fixes the
37   * start of the year at January 1.
38   * <p>
39   * JulianChronology is thread-safe and immutable.
40   *
41   * @see <a href="http://en.wikipedia.org/wiki/Julian_calendar">Wikipedia</a>
42   * @see GregorianChronology
43   * @see GJChronology
44   *
45   * @author Guy Allard
46   * @author Brian S O'Neill
47   * @author Stephen Colebourne
48   * @since 1.0
49   */
50  public final class JulianChronology extends BasicGJChronology {
51  
52      /** Serialization lock */
53      private static final long serialVersionUID = -8731039522547897247L;
54  
55      private static final long MILLIS_PER_YEAR =
56          (long) (365.25 * DateTimeConstants.MILLIS_PER_DAY);
57  
58      private static final long MILLIS_PER_MONTH =
59          (long) (365.25 * DateTimeConstants.MILLIS_PER_DAY / 12);
60  
61      /** The lowest year that can be fully supported. */
62      private static final int MIN_YEAR = -292269054;
63  
64      /** The highest year that can be fully supported. */
65      private static final int MAX_YEAR = 292272992;
66  
67      /** Singleton instance of a UTC JulianChronology */
68      private static final JulianChronology INSTANCE_UTC;
69  
70      /** Cache of zone to chronology arrays */
71      private static final Map<DateTimeZone, JulianChronology[]> cCache = new HashMap<DateTimeZone, JulianChronology[]>();
72  
73      static {
74          INSTANCE_UTC = getInstance(DateTimeZone.UTC);
75      }
76  
77      static int adjustYearForSet(int year) {
78          if (year <= 0) {
79              if (year == 0) {
80                  throw new IllegalFieldValueException
81                      (DateTimeFieldType.year(), Integer.valueOf(year), null, null);
82              }
83              year++;
84          }
85          return year;
86      }
87  
88      /**
89       * Gets an instance of the JulianChronology.
90       * The time zone of the returned instance is UTC.
91       * 
92       * @return a singleton UTC instance of the chronology
93       */
94      public static JulianChronology getInstanceUTC() {
95          return INSTANCE_UTC;
96      }
97  
98      /**
99       * 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 }