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 }