1 /* |
|
2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 /* |
|
27 * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos |
|
28 * |
|
29 * All rights reserved. |
|
30 * |
|
31 * Redistribution and use in source and binary forms, with or without |
|
32 * modification, are permitted provided that the following conditions are met: |
|
33 * |
|
34 * * Redistributions of source code must retain the above copyright notice, |
|
35 * this list of conditions and the following disclaimer. |
|
36 * |
|
37 * * Redistributions in binary form must reproduce the above copyright notice, |
|
38 * this list of conditions and the following disclaimer in the documentation |
|
39 * and/or other materials provided with the distribution. |
|
40 * |
|
41 * * Neither the name of JSR-310 nor the names of its contributors |
|
42 * may be used to endorse or promote products derived from this software |
|
43 * without specific prior written permission. |
|
44 * |
|
45 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
46 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
47 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
48 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
49 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
|
50 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
|
51 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
52 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
53 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
54 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
55 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
56 */ |
|
57 package java.time.calendar; |
|
58 |
|
59 import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; |
|
60 import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; |
|
61 import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; |
|
62 import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; |
|
63 import static java.time.temporal.ChronoField.DAY_OF_MONTH; |
|
64 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; |
|
65 import static java.time.temporal.ChronoField.YEAR; |
|
66 |
|
67 import java.io.IOException; |
|
68 import java.io.ObjectInput; |
|
69 import java.io.ObjectOutput; |
|
70 import java.io.Serializable; |
|
71 import java.time.DateTimeException; |
|
72 import java.time.DayOfWeek; |
|
73 import java.time.LocalDate; |
|
74 import java.time.temporal.ChronoField; |
|
75 import java.time.temporal.ChronoLocalDate; |
|
76 import java.time.temporal.TemporalField; |
|
77 import java.time.temporal.ValueRange; |
|
78 import java.util.Objects; |
|
79 |
|
80 /** |
|
81 * A date in the Hijrah calendar system. |
|
82 * <p> |
|
83 * This implements {@code ChronoLocalDate} for the {@link HijrahChrono Hijrah calendar}. |
|
84 * <p> |
|
85 * The Hijrah calendar has a different total of days in a year than |
|
86 * Gregorian calendar, and a month is based on the period of a complete |
|
87 * revolution of the moon around the earth (as between successive new moons). |
|
88 * The calendar cycles becomes longer and unstable, and sometimes a manual |
|
89 * adjustment (for entering deviation) is necessary for correctness |
|
90 * because of the complex algorithm. |
|
91 * <p> |
|
92 * HijrahDate supports the manual adjustment feature by providing a configuration |
|
93 * file. The configuration file contains the adjustment (deviation) data with following format. |
|
94 * <pre> |
|
95 * StartYear/StartMonth(0-based)-EndYear/EndMonth(0-based):Deviation day (1, 2, -1, or -2) |
|
96 * Line separator or ";" is used for the separator of each deviation data.</pre> |
|
97 * Here is the example. |
|
98 * <pre> |
|
99 * 1429/0-1429/1:1 |
|
100 * 1429/2-1429/7:1;1429/6-1429/11:1 |
|
101 * 1429/11-9999/11:1</pre> |
|
102 * The default location of the configuration file is: |
|
103 * <pre> |
|
104 * $CLASSPATH/java/time/i18n</pre> |
|
105 * And the default file name is: |
|
106 * <pre> |
|
107 * hijrah_deviation.cfg</pre> |
|
108 * The default location and file name can be overriden by setting |
|
109 * following two Java's system property. |
|
110 * <pre> |
|
111 * Location: java.time.i18n.HijrahDate.deviationConfigDir |
|
112 * File name: java.time.i18n.HijrahDate.deviationConfigFile</pre> |
|
113 * |
|
114 * <h3>Specification for implementors</h3> |
|
115 * This class is immutable and thread-safe. |
|
116 * |
|
117 * @since 1.8 |
|
118 */ |
|
119 final class HijrahDate |
|
120 extends ChronoDateImpl<HijrahChrono> |
|
121 implements ChronoLocalDate<HijrahChrono>, Serializable { |
|
122 // this class is package-scoped so that future conversion to public |
|
123 // would not change serialization |
|
124 |
|
125 /** |
|
126 * Serialization version. |
|
127 */ |
|
128 private static final long serialVersionUID = -5207853542612002020L; |
|
129 |
|
130 /** |
|
131 * The Chronology of this HijrahDate. |
|
132 */ |
|
133 private final HijrahChrono chrono; |
|
134 /** |
|
135 * The era. |
|
136 */ |
|
137 private final transient HijrahEra era; |
|
138 /** |
|
139 * The year. |
|
140 */ |
|
141 private final transient int yearOfEra; |
|
142 /** |
|
143 * The month-of-year. |
|
144 */ |
|
145 private final transient int monthOfYear; |
|
146 /** |
|
147 * The day-of-month. |
|
148 */ |
|
149 private final transient int dayOfMonth; |
|
150 /** |
|
151 * The day-of-year. |
|
152 */ |
|
153 private final transient int dayOfYear; |
|
154 /** |
|
155 * The day-of-week. |
|
156 */ |
|
157 private final transient DayOfWeek dayOfWeek; |
|
158 /** |
|
159 * Gregorian days for this object. Holding number of days since 1970/01/01. |
|
160 * The number of days are calculated with pure Gregorian calendar |
|
161 * based. |
|
162 */ |
|
163 private final long gregorianEpochDay; |
|
164 /** |
|
165 * True if year is leap year. |
|
166 */ |
|
167 private final transient boolean isLeapYear; |
|
168 |
|
169 //------------------------------------------------------------------------- |
|
170 /** |
|
171 * Obtains an instance of {@code HijrahDate} from the Hijrah era year, |
|
172 * month-of-year and day-of-month. This uses the Hijrah era. |
|
173 * |
|
174 * @param prolepticYear the proleptic year to represent in the Hijrah |
|
175 * @param monthOfYear the month-of-year to represent, from 1 to 12 |
|
176 * @param dayOfMonth the day-of-month to represent, from 1 to 30 |
|
177 * @return the Hijrah date, never null |
|
178 * @throws DateTimeException if the value of any field is out of range |
|
179 */ |
|
180 static HijrahDate of(HijrahChrono chrono, int prolepticYear, int monthOfYear, int dayOfMonth) { |
|
181 return (prolepticYear >= 1) ? |
|
182 HijrahDate.of(chrono, HijrahEra.AH, prolepticYear, monthOfYear, dayOfMonth) : |
|
183 HijrahDate.of(chrono, HijrahEra.BEFORE_AH, 1 - prolepticYear, monthOfYear, dayOfMonth); |
|
184 } |
|
185 |
|
186 /** |
|
187 * Obtains an instance of {@code HijrahDate} from the era, year-of-era |
|
188 * month-of-year and day-of-month. |
|
189 * |
|
190 * @param era the era to represent, not null |
|
191 * @param yearOfEra the year-of-era to represent, from 1 to 9999 |
|
192 * @param monthOfYear the month-of-year to represent, from 1 to 12 |
|
193 * @param dayOfMonth the day-of-month to represent, from 1 to 31 |
|
194 * @return the Hijrah date, never null |
|
195 * @throws DateTimeException if the value of any field is out of range |
|
196 */ |
|
197 private static HijrahDate of(HijrahChrono chrono, HijrahEra era, int yearOfEra, int monthOfYear, int dayOfMonth) { |
|
198 Objects.requireNonNull(era, "era"); |
|
199 chrono.checkValidYearOfEra(yearOfEra); |
|
200 chrono.checkValidMonth(monthOfYear); |
|
201 chrono.checkValidDayOfMonth(dayOfMonth); |
|
202 long gregorianDays = chrono.getGregorianEpochDay(era.prolepticYear(yearOfEra), monthOfYear, dayOfMonth); |
|
203 return new HijrahDate(chrono, gregorianDays); |
|
204 } |
|
205 |
|
206 /** |
|
207 * Obtains an instance of {@code HijrahDate} from a date. |
|
208 * |
|
209 * @param date the date to use, not null |
|
210 * @return the Hijrah date, never null |
|
211 * @throws DateTimeException if the year is invalid |
|
212 */ |
|
213 private static HijrahDate of(HijrahChrono chrono, LocalDate date) { |
|
214 long gregorianDays = date.toEpochDay(); |
|
215 return new HijrahDate(chrono, gregorianDays); |
|
216 } |
|
217 |
|
218 static HijrahDate ofEpochDay(HijrahChrono chrono, long epochDay) { |
|
219 return new HijrahDate(chrono, epochDay); |
|
220 } |
|
221 |
|
222 /** |
|
223 * Constructs an instance with the specified date. |
|
224 * |
|
225 * @param gregorianDay the number of days from 0001/01/01 (Gregorian), caller calculated |
|
226 */ |
|
227 private HijrahDate(HijrahChrono chrono, long gregorianDay) { |
|
228 this.chrono = chrono; |
|
229 int[] dateInfo = chrono.getHijrahDateInfo(gregorianDay); |
|
230 |
|
231 chrono.checkValidYearOfEra(dateInfo[1]); |
|
232 chrono.checkValidMonth(dateInfo[2]); |
|
233 chrono.checkValidDayOfMonth(dateInfo[3]); |
|
234 chrono.checkValidDayOfYear(dateInfo[4]); |
|
235 |
|
236 this.era = HijrahEra.of(dateInfo[0]); |
|
237 this.yearOfEra = dateInfo[1]; |
|
238 this.monthOfYear = dateInfo[2]; |
|
239 this.dayOfMonth = dateInfo[3]; |
|
240 this.dayOfYear = dateInfo[4]; |
|
241 this.dayOfWeek = DayOfWeek.of(dateInfo[5]); |
|
242 this.gregorianEpochDay = gregorianDay; |
|
243 this.isLeapYear = chrono.isLeapYear(this.yearOfEra); |
|
244 } |
|
245 |
|
246 //----------------------------------------------------------------------- |
|
247 @Override |
|
248 public HijrahChrono getChrono() { |
|
249 return chrono; |
|
250 } |
|
251 |
|
252 @Override |
|
253 public ValueRange range(TemporalField field) { |
|
254 if (field instanceof ChronoField) { |
|
255 if (isSupported(field)) { |
|
256 ChronoField f = (ChronoField) field; |
|
257 switch (f) { |
|
258 case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); |
|
259 case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); |
|
260 case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, 5); // TODO |
|
261 case YEAR_OF_ERA: return ValueRange.of(1, 1000); // TODO |
|
262 } |
|
263 return getChrono().range(f); |
|
264 } |
|
265 throw new DateTimeException("Unsupported field: " + field.getName()); |
|
266 } |
|
267 return field.doRange(this); |
|
268 } |
|
269 |
|
270 @Override |
|
271 public long getLong(TemporalField field) { |
|
272 if (field instanceof ChronoField) { |
|
273 switch ((ChronoField) field) { |
|
274 case DAY_OF_WEEK: return dayOfWeek.getValue(); |
|
275 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((dayOfWeek.getValue() - 1) % 7) + 1; |
|
276 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((dayOfYear - 1) % 7) + 1; |
|
277 case DAY_OF_MONTH: return this.dayOfMonth; |
|
278 case DAY_OF_YEAR: return this.dayOfYear; |
|
279 case EPOCH_DAY: return toEpochDay(); |
|
280 case ALIGNED_WEEK_OF_MONTH: return ((dayOfMonth - 1) / 7) + 1; |
|
281 case ALIGNED_WEEK_OF_YEAR: return ((dayOfYear - 1) / 7) + 1; |
|
282 case MONTH_OF_YEAR: return monthOfYear; |
|
283 case YEAR_OF_ERA: return yearOfEra; |
|
284 case YEAR: return yearOfEra; |
|
285 case ERA: return era.getValue(); |
|
286 } |
|
287 throw new DateTimeException("Unsupported field: " + field.getName()); |
|
288 } |
|
289 return field.doGet(this); |
|
290 } |
|
291 |
|
292 @Override |
|
293 public HijrahDate with(TemporalField field, long newValue) { |
|
294 if (field instanceof ChronoField) { |
|
295 ChronoField f = (ChronoField) field; |
|
296 f.checkValidValue(newValue); // TODO: validate value |
|
297 int nvalue = (int) newValue; |
|
298 switch (f) { |
|
299 case DAY_OF_WEEK: return plusDays(newValue - dayOfWeek.getValue()); |
|
300 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_MONTH)); |
|
301 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_YEAR)); |
|
302 case DAY_OF_MONTH: return resolvePreviousValid(yearOfEra, monthOfYear, nvalue); |
|
303 case DAY_OF_YEAR: return resolvePreviousValid(yearOfEra, ((nvalue - 1) / 30) + 1, ((nvalue - 1) % 30) + 1); |
|
304 case EPOCH_DAY: return new HijrahDate(chrono, nvalue); |
|
305 case ALIGNED_WEEK_OF_MONTH: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_MONTH)) * 7); |
|
306 case ALIGNED_WEEK_OF_YEAR: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_YEAR)) * 7); |
|
307 case MONTH_OF_YEAR: return resolvePreviousValid(yearOfEra, nvalue, dayOfMonth); |
|
308 case YEAR_OF_ERA: return resolvePreviousValid(yearOfEra >= 1 ? nvalue : 1 - nvalue, monthOfYear, dayOfMonth); |
|
309 case YEAR: return resolvePreviousValid(nvalue, monthOfYear, dayOfMonth); |
|
310 case ERA: return resolvePreviousValid(1 - yearOfEra, monthOfYear, dayOfMonth); |
|
311 } |
|
312 throw new DateTimeException("Unsupported field: " + field.getName()); |
|
313 } |
|
314 return (HijrahDate) ChronoLocalDate.super.with(field, newValue); |
|
315 } |
|
316 |
|
317 private HijrahDate resolvePreviousValid(int yearOfEra, int month, int day) { |
|
318 int monthDays = getMonthDays(month - 1, yearOfEra); |
|
319 if (day > monthDays) { |
|
320 day = monthDays; |
|
321 } |
|
322 return HijrahDate.of(chrono, yearOfEra, month, day); |
|
323 } |
|
324 |
|
325 @Override |
|
326 public long toEpochDay() { |
|
327 return chrono.getGregorianEpochDay(yearOfEra, monthOfYear, dayOfMonth); |
|
328 } |
|
329 |
|
330 //----------------------------------------------------------------------- |
|
331 @Override |
|
332 public HijrahEra getEra() { |
|
333 return this.era; |
|
334 } |
|
335 |
|
336 //----------------------------------------------------------------------- |
|
337 /** |
|
338 * Checks if the year is a leap year, according to the Hijrah calendar system rules. |
|
339 * |
|
340 * @return true if this date is in a leap year |
|
341 */ |
|
342 @Override |
|
343 public boolean isLeapYear() { |
|
344 return this.isLeapYear; |
|
345 } |
|
346 |
|
347 //----------------------------------------------------------------------- |
|
348 @Override |
|
349 public HijrahDate plusYears(long years) { |
|
350 if (years == 0) { |
|
351 return this; |
|
352 } |
|
353 int newYear = Math.addExact(this.yearOfEra, (int)years); |
|
354 return HijrahDate.of(chrono, this.era, newYear, this.monthOfYear, this.dayOfMonth); |
|
355 } |
|
356 |
|
357 @Override |
|
358 public HijrahDate plusMonths(long months) { |
|
359 if (months == 0) { |
|
360 return this; |
|
361 } |
|
362 int newMonth = this.monthOfYear - 1; |
|
363 newMonth = newMonth + (int)months; |
|
364 int years = newMonth / 12; |
|
365 newMonth = newMonth % 12; |
|
366 while (newMonth < 0) { |
|
367 newMonth += 12; |
|
368 years = Math.subtractExact(years, 1); |
|
369 } |
|
370 int newYear = Math.addExact(this.yearOfEra, years); |
|
371 return HijrahDate.of(chrono, this.era, newYear, newMonth + 1, this.dayOfMonth); |
|
372 } |
|
373 |
|
374 @Override |
|
375 public HijrahDate plusDays(long days) { |
|
376 return new HijrahDate(chrono, this.gregorianEpochDay + days); |
|
377 } |
|
378 |
|
379 /** |
|
380 * Returns month days from the beginning of year. |
|
381 * |
|
382 * @param month month (0-based) |
|
383 * @parma year year |
|
384 * @return month days from the beginning of year |
|
385 */ |
|
386 private int getMonthDays(int month, int year) { |
|
387 int[] newMonths = chrono.getAdjustedMonthDays(year); |
|
388 return newMonths[month]; |
|
389 } |
|
390 |
|
391 /** |
|
392 * Returns month length. |
|
393 * |
|
394 * @param month month (0-based) |
|
395 * @param year year |
|
396 * @return month length |
|
397 */ |
|
398 private int getMonthLength(int month, int year) { |
|
399 int[] newMonths = chrono.getAdjustedMonthLength(year); |
|
400 return newMonths[month]; |
|
401 } |
|
402 |
|
403 @Override |
|
404 public int lengthOfMonth() { |
|
405 return getMonthLength(monthOfYear - 1, yearOfEra); |
|
406 } |
|
407 |
|
408 @Override |
|
409 public int lengthOfYear() { |
|
410 return chrono.getYearLength(yearOfEra); // TODO: proleptic year |
|
411 } |
|
412 |
|
413 //----------------------------------------------------------------------- |
|
414 private Object writeReplace() { |
|
415 return new Ser(Ser.HIJRAH_DATE_TYPE, this); |
|
416 } |
|
417 |
|
418 void writeExternal(ObjectOutput out) throws IOException { |
|
419 // HijrahChrono is implicit in the Hijrah_DATE_TYPE |
|
420 out.writeObject(chrono); |
|
421 out.writeInt(get(YEAR)); |
|
422 out.writeByte(get(MONTH_OF_YEAR)); |
|
423 out.writeByte(get(DAY_OF_MONTH)); |
|
424 } |
|
425 |
|
426 /** |
|
427 * Replaces the date instance from the stream with a valid one. |
|
428 * ReadExternal has already read the fields and created a new instance |
|
429 * from the data. |
|
430 * |
|
431 * @return the resolved date, never null |
|
432 */ |
|
433 private Object readResolve() { |
|
434 return this; |
|
435 } |
|
436 |
|
437 static ChronoLocalDate<HijrahChrono> readExternal(ObjectInput in) throws IOException, ClassNotFoundException { |
|
438 HijrahChrono chrono = (HijrahChrono)in.readObject(); |
|
439 int year = in.readInt(); |
|
440 int month = in.readByte(); |
|
441 int dayOfMonth = in.readByte(); |
|
442 return chrono.date(year, month, dayOfMonth); |
|
443 } |
|
444 |
|
445 } |
|