1 /*
2 * Copyright 2001-2014 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.concurrent.ConcurrentHashMap;
19
20 import org.joda.time.Chronology;
21 import org.joda.time.DateTimeConstants;
22 import org.joda.time.DateTimeZone;
23
24 /**
25 * Implements a pure proleptic Gregorian calendar system, which defines every
26 * fourth year as leap, unless the year is divisible by 100 and not by 400.
27 * This improves upon the Julian calendar leap year rule.
28 * <p>
29 * Although the Gregorian calendar did not exist before 1582 CE, this
30 * chronology assumes it did, thus it is proleptic. This implementation also
31 * fixes the start of the year at January 1, and defines the year zero.
32 * <p>
33 * GregorianChronology is thread-safe and immutable.
34 *
35 * @see <a href="http://en.wikipedia.org/wiki/Gregorian_calendar">Wikipedia</a>
36 * @see JulianChronology
37 * @see GJChronology
38 *
39 * @author Guy Allard
40 * @author Stephen Colebourne
41 * @author Brian S O'Neill
42 * @since 1.0
43 */
44 public final class GregorianChronology extends BasicGJChronology {
45
46 /** Serialization lock */
47 private static final long serialVersionUID = -861407383323710522L;
48
49 private static final long MILLIS_PER_YEAR =
50 (long) (365.2425 * DateTimeConstants.MILLIS_PER_DAY);
51
52 private static final long MILLIS_PER_MONTH =
53 (long) (365.2425 * DateTimeConstants.MILLIS_PER_DAY / 12);
54
55 private static final int DAYS_0000_TO_1970 = 719527;
56
57 /** The lowest year that can be fully supported. */
58 private static final int MIN_YEAR = -292275054;
59
60 /** The highest year that can be fully supported. */
61 private static final int MAX_YEAR = 292278993;
62
63 /** Singleton instance of a UTC GregorianChronology */
64 private static final GregorianChronology INSTANCE_UTC;
65
66 /** Cache of zone to chronology arrays */
67 private static final ConcurrentHashMap<DateTimeZone, GregorianChronology[]> cCache = new ConcurrentHashMap<DateTimeZone, GregorianChronology[]>();
68
69 static {
70 INSTANCE_UTC = getInstance(DateTimeZone.UTC);
71 }
72
73 /**
74 * Gets an instance of the GregorianChronology.
75 * The time zone of the returned instance is UTC.
76 *
77 * @return a singleton UTC instance of the chronology
78 */
79 public static GregorianChronology getInstanceUTC() {
80 return INSTANCE_UTC;
81 }
82
83 /**
84 * Gets an instance of the GregorianChronology in the default time zone.
85 *
86 * @return a chronology in the default time zone
87 */
88 public static GregorianChronology getInstance() {
89 return getInstance(DateTimeZone.getDefault(), 4);
90 }
91
92 /**
93 * Gets an instance of the GregorianChronology in the given time zone.
94 *
95 * @param zone the time zone to get the chronology in, null is default
96 * @return a chronology in the specified time zone
97 */
98 public static GregorianChronology getInstance(DateTimeZone zone) {
99 return getInstance(zone, 4);
100 }
101
102 /**
103 * Gets an instance of the GregorianChronology in the given time zone.
104 *
105 * @param zone the time zone to get the chronology in, null is default
106 * @param minDaysInFirstWeek minimum number of days in first week of the year; default is 4
107 * @return a chronology in the specified time zone
108 */
109 public static GregorianChronology getInstance(DateTimeZone zone, int minDaysInFirstWeek) {
110 if (zone == null) {
111 zone = DateTimeZone.getDefault();
112 }
113 GregorianChronology chrono;
114 GregorianChronology[] chronos = cCache.get(zone);
115 if (chronos == null) {
116 chronos = new GregorianChronology[7];
117 GregorianChronology[] oldChronos = cCache.putIfAbsent(zone, chronos);
118 if (oldChronos != null) {
119 chronos = oldChronos;
120 }
121 }
122 try {
123 chrono = chronos[minDaysInFirstWeek - 1];
124 } catch (ArrayIndexOutOfBoundsException e) {
125 throw new IllegalArgumentException
126 ("Invalid min days in first week: " + minDaysInFirstWeek);
127 }
128 if (chrono == null) {
129 synchronized (chronos) {
130 chrono = chronos[minDaysInFirstWeek - 1];
131 if (chrono == null) {
132 if (zone == DateTimeZone.UTC) {
133 chrono = new GregorianChronology(null, null, minDaysInFirstWeek);
134 } else {
135 chrono = getInstance(DateTimeZone.UTC, minDaysInFirstWeek);
136 chrono = new GregorianChronology
137 (ZonedChronology.getInstance(chrono, zone), null, minDaysInFirstWeek);
138 }
139 chronos[minDaysInFirstWeek - 1] = chrono;
140 }
141 }
142 }
143 return chrono;
144 }
145
146 // Constructors and instance variables
147 //-----------------------------------------------------------------------
148
149 /**
150 * Restricted constructor
151 */
152 private GregorianChronology(Chronology base, Object param, int minDaysInFirstWeek) {
153 super(base, param, minDaysInFirstWeek);
154 }
155
156 /**
157 * Serialization singleton
158 */
159 private Object readResolve() {
160 Chronology base = getBase();
161 int minDays = getMinimumDaysInFirstWeek();
162 minDays = (minDays == 0 ? 4 : minDays); // handle rename of BaseGJChronology
163 return base == null ?
164 getInstance(DateTimeZone.UTC, minDays) :
165 getInstance(base.getZone(), minDays);
166 }
167
168 // Conversion
169 //-----------------------------------------------------------------------
170 /**
171 * Gets the Chronology in the UTC time zone.
172 *
173 * @return the chronology in UTC
174 */
175 public Chronology withUTC() {
176 return INSTANCE_UTC;
177 }
178
179 /**
180 * Gets the Chronology in a specific time zone.
181 *
182 * @param zone the zone to get the chronology in, null is default
183 * @return the chronology
184 */
185 public Chronology withZone(DateTimeZone zone) {
186 if (zone == null) {
187 zone = DateTimeZone.getDefault();
188 }
189 if (zone == getZone()) {
190 return this;
191 }
192 return getInstance(zone);
193 }
194
195 protected void assemble(Fields fields) {
196 if (getBase() == null) {
197 super.assemble(fields);
198 }
199 }
200
201 boolean isLeapYear(int year) {
202 return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
203 }
204
205 long calculateFirstDayOfYearMillis(int year) {
206 // Initial value is just temporary.
207 int leapYears = year / 100;
208 if (year < 0) {
209 // Add 3 before shifting right since /4 and >>2 behave differently
210 // on negative numbers. When the expression is written as
211 // (year / 4) - (year / 100) + (year / 400),
212 // it works for both positive and negative values, except this optimization
213 // eliminates two divisions.
214 leapYears = ((year + 3) >> 2) - leapYears + ((leapYears + 3) >> 2) - 1;
215 } else {
216 leapYears = (year >> 2) - leapYears + (leapYears >> 2);
217 if (isLeapYear(year)) {
218 leapYears--;
219 }
220 }
221
222 return (year * 365L + (leapYears - DAYS_0000_TO_1970)) * DateTimeConstants.MILLIS_PER_DAY;
223 }
224
225 int getMinYear() {
226 return MIN_YEAR;
227 }
228
229 int getMaxYear() {
230 return MAX_YEAR;
231 }
232
233 long getAverageMillisPerYear() {
234 return MILLIS_PER_YEAR;
235 }
236
237 long getAverageMillisPerYearDividedByTwo() {
238 return MILLIS_PER_YEAR / 2;
239 }
240
241 long getAverageMillisPerMonth() {
242 return MILLIS_PER_MONTH;
243 }
244
245 long getApproxMillisAtEpochDividedByTwo() {
246 return (1970L * MILLIS_PER_YEAR) / 2;
247 }
248
249 }
250