|
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.chrono; |
|
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.Clock; |
|
72 import java.time.DateTimeException; |
|
73 import java.time.DayOfWeek; |
|
74 import java.time.LocalDate; |
|
75 import java.time.LocalTime; |
|
76 import java.time.Period; |
|
77 import java.time.ZoneId; |
|
78 import java.time.temporal.ChronoField; |
|
79 import java.time.temporal.TemporalQuery; |
|
80 import java.time.temporal.TemporalAccessor; |
|
81 import java.time.temporal.TemporalAdjuster; |
|
82 import java.time.temporal.TemporalAmount; |
|
83 import java.time.temporal.TemporalField; |
|
84 import java.time.temporal.TemporalUnit; |
|
85 import java.time.temporal.ValueRange; |
|
86 import java.util.Objects; |
|
87 |
|
88 /** |
|
89 * A date in the Hijrah calendar system. |
|
90 * <p> |
|
91 * This date operates using the {@linkplain HijrahChronology Hijrah calendar}. |
|
92 * <p> |
|
93 * The Hijrah calendar has a different total of days in a year than |
|
94 * Gregorian calendar, and a month is based on the period of a complete |
|
95 * revolution of the moon around the earth (as between successive new moons). |
|
96 * The calendar cycles becomes longer and unstable, and sometimes a manual |
|
97 * adjustment (for entering deviation) is necessary for correctness |
|
98 * because of the complex algorithm. |
|
99 * <p> |
|
100 * HijrahDate supports the manual adjustment feature by providing a configuration |
|
101 * file. The configuration file contains the adjustment (deviation) data with following format. |
|
102 * <pre> |
|
103 * StartYear/StartMonth(0-based)-EndYear/EndMonth(0-based):Deviation day (1, 2, -1, or -2) |
|
104 * Line separator or ";" is used for the separator of each deviation data.</pre> |
|
105 * Here is the example. |
|
106 * <pre> |
|
107 * 1429/0-1429/1:1 |
|
108 * 1429/2-1429/7:1;1429/6-1429/11:1 |
|
109 * 1429/11-9999/11:1</pre> |
|
110 * The default location of the configuration file is: |
|
111 * <pre> |
|
112 * $CLASSPATH/java/time/i18n</pre> |
|
113 * And the default file name is: |
|
114 * <pre> |
|
115 * hijrah_deviation.cfg</pre> |
|
116 * The default location and file name can be overriden by setting |
|
117 * following two Java's system property. |
|
118 * <pre> |
|
119 * Location: java.time.i18n.HijrahDate.deviationConfigDir |
|
120 * File name: java.time.i18n.HijrahDate.deviationConfigFile</pre> |
|
121 * |
|
122 * <h3>Specification for implementors</h3> |
|
123 * This class is immutable and thread-safe. |
|
124 * |
|
125 * @since 1.8 |
|
126 */ |
|
127 public final class HijrahDate |
|
128 extends ChronoDateImpl<HijrahDate> |
|
129 implements ChronoLocalDate<HijrahDate>, Serializable { |
|
130 |
|
131 /** |
|
132 * Serialization version. |
|
133 */ |
|
134 private static final long serialVersionUID = -5207853542612002020L; |
|
135 |
|
136 /** |
|
137 * The Chronology of this HijrahDate. |
|
138 */ |
|
139 private final HijrahChronology chrono; |
|
140 /** |
|
141 * The era. |
|
142 */ |
|
143 private final transient HijrahEra era; |
|
144 /** |
|
145 * The year. |
|
146 */ |
|
147 private final transient int yearOfEra; |
|
148 /** |
|
149 * The month-of-year. |
|
150 */ |
|
151 private final transient int monthOfYear; |
|
152 /** |
|
153 * The day-of-month. |
|
154 */ |
|
155 private final transient int dayOfMonth; |
|
156 /** |
|
157 * The day-of-year. |
|
158 */ |
|
159 private final transient int dayOfYear; |
|
160 /** |
|
161 * The day-of-week. |
|
162 */ |
|
163 private final transient DayOfWeek dayOfWeek; |
|
164 /** |
|
165 * Gregorian days for this object. Holding number of days since 1970/01/01. |
|
166 * The number of days are calculated with pure Gregorian calendar |
|
167 * based. |
|
168 */ |
|
169 private final long gregorianEpochDay; |
|
170 /** |
|
171 * True if year is leap year. |
|
172 */ |
|
173 private final transient boolean isLeapYear; |
|
174 |
|
175 //------------------------------------------------------------------------- |
|
176 /** |
|
177 * Obtains an instance of {@code HijrahDate} from the Hijrah era year, |
|
178 * month-of-year and day-of-month. This uses the Hijrah era. |
|
179 * |
|
180 * @param prolepticYear the proleptic year to represent in the Hijrah |
|
181 * @param monthOfYear the month-of-year to represent, from 1 to 12 |
|
182 * @param dayOfMonth the day-of-month to represent, from 1 to 30 |
|
183 * @return the Hijrah date, never null |
|
184 * @throws DateTimeException if the value of any field is out of range |
|
185 */ |
|
186 static HijrahDate of(HijrahChronology chrono, int prolepticYear, int monthOfYear, int dayOfMonth) { |
|
187 return (prolepticYear >= 1) ? |
|
188 HijrahDate.of(chrono, HijrahEra.AH, prolepticYear, monthOfYear, dayOfMonth) : |
|
189 HijrahDate.of(chrono, HijrahEra.BEFORE_AH, 1 - prolepticYear, monthOfYear, dayOfMonth); |
|
190 } |
|
191 |
|
192 /** |
|
193 * Obtains an instance of {@code HijrahDate} from the era, year-of-era |
|
194 * month-of-year and day-of-month. |
|
195 * |
|
196 * @param era the era to represent, not null |
|
197 * @param yearOfEra the year-of-era to represent, from 1 to 9999 |
|
198 * @param monthOfYear the month-of-year to represent, from 1 to 12 |
|
199 * @param dayOfMonth the day-of-month to represent, from 1 to 31 |
|
200 * @return the Hijrah date, never null |
|
201 * @throws DateTimeException if the value of any field is out of range |
|
202 */ |
|
203 private static HijrahDate of(HijrahChronology chrono, HijrahEra era, int yearOfEra, int monthOfYear, int dayOfMonth) { |
|
204 Objects.requireNonNull(era, "era"); |
|
205 chrono.checkValidYearOfEra(yearOfEra); |
|
206 chrono.checkValidMonth(monthOfYear); |
|
207 chrono.checkValidDayOfMonth(dayOfMonth); |
|
208 long gregorianDays = chrono.getGregorianEpochDay(era.prolepticYear(yearOfEra), monthOfYear, dayOfMonth); |
|
209 return new HijrahDate(chrono, gregorianDays); |
|
210 } |
|
211 |
|
212 static HijrahDate ofEpochDay(HijrahChronology chrono, long epochDay) { |
|
213 return new HijrahDate(chrono, epochDay); |
|
214 } |
|
215 |
|
216 //----------------------------------------------------------------------- |
|
217 /** |
|
218 * Obtains the current {@code HijrahDate} from the system clock in the default time-zone. |
|
219 * <p> |
|
220 * This will query the {@link Clock#systemDefaultZone() system clock} in the default |
|
221 * time-zone to obtain the current date. |
|
222 * <p> |
|
223 * Using this method will prevent the ability to use an alternate clock for testing |
|
224 * because the clock is hard-coded. |
|
225 * |
|
226 * @return the current date using the system clock and default time-zone, not null |
|
227 */ |
|
228 public static HijrahDate now() { |
|
229 return now(Clock.systemDefaultZone()); |
|
230 } |
|
231 |
|
232 /** |
|
233 * Obtains the current {@code HijrahDate} from the system clock in the specified time-zone. |
|
234 * <p> |
|
235 * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date. |
|
236 * Specifying the time-zone avoids dependence on the default time-zone. |
|
237 * <p> |
|
238 * Using this method will prevent the ability to use an alternate clock for testing |
|
239 * because the clock is hard-coded. |
|
240 * |
|
241 * @param zone the zone ID to use, not null |
|
242 * @return the current date using the system clock, not null |
|
243 */ |
|
244 public static HijrahDate now(ZoneId zone) { |
|
245 return now(Clock.system(zone)); |
|
246 } |
|
247 |
|
248 /** |
|
249 * Obtains the current {@code HijrahDate} from the specified clock. |
|
250 * <p> |
|
251 * This will query the specified clock to obtain the current date - today. |
|
252 * Using this method allows the use of an alternate clock for testing. |
|
253 * The alternate clock may be introduced using {@linkplain Clock dependency injection}. |
|
254 * |
|
255 * @param clock the clock to use, not null |
|
256 * @return the current date, not null |
|
257 * @throws DateTimeException if the current date cannot be obtained |
|
258 */ |
|
259 public static HijrahDate now(Clock clock) { |
|
260 return HijrahChronology.INSTANCE.date(LocalDate.now(clock)); |
|
261 } |
|
262 |
|
263 /** |
|
264 * Obtains a {@code HijrahDate} representing a date in the Hijrah calendar |
|
265 * system from the proleptic-year, month-of-year and day-of-month fields. |
|
266 * <p> |
|
267 * This returns a {@code HijrahDate} with the specified fields. |
|
268 * The day must be valid for the year and month, otherwise an exception will be thrown. |
|
269 * |
|
270 * @param prolepticYear the Hijrah proleptic-year |
|
271 * @param month the Hijrah month-of-year, from 1 to 12 |
|
272 * @param dayOfMonth the Hijrah day-of-month, from 1 to 30 |
|
273 * @return the date in Hijrah calendar system, not null |
|
274 * @throws DateTimeException if the value of any field is out of range, |
|
275 * or if the day-of-month is invalid for the month-year |
|
276 */ |
|
277 public static HijrahDate of(int prolepticYear, int month, int dayOfMonth) { |
|
278 return HijrahChronology.INSTANCE.date(prolepticYear, month, dayOfMonth); |
|
279 } |
|
280 |
|
281 /** |
|
282 * Obtains a {@code HijrahDate} from a temporal object. |
|
283 * <p> |
|
284 * This obtains a date in the Hijrah calendar system based on the specified temporal. |
|
285 * A {@code TemporalAccessor} represents an arbitrary set of date and time information, |
|
286 * which this factory converts to an instance of {@code HijrahDate}. |
|
287 * <p> |
|
288 * The conversion typically uses the {@link ChronoField#EPOCH_DAY EPOCH_DAY} |
|
289 * field, which is standardized across calendar systems. |
|
290 * <p> |
|
291 * This method matches the signature of the functional interface {@link TemporalQuery} |
|
292 * allowing it to be used as a query via method reference, {@code HijrahDate::from}. |
|
293 * |
|
294 * @param temporal the temporal object to convert, not null |
|
295 * @return the date in Hijrah calendar system, not null |
|
296 * @throws DateTimeException if unable to convert to a {@code HijrahDate} |
|
297 */ |
|
298 public static HijrahDate from(TemporalAccessor temporal) { |
|
299 return HijrahChronology.INSTANCE.date(temporal); |
|
300 } |
|
301 |
|
302 //----------------------------------------------------------------------- |
|
303 /** |
|
304 * Constructs an instance with the specified date. |
|
305 * |
|
306 * @param gregorianDay the number of days from 0001/01/01 (Gregorian), caller calculated |
|
307 */ |
|
308 private HijrahDate(HijrahChronology chrono, long gregorianDay) { |
|
309 this.chrono = chrono; |
|
310 int[] dateInfo = chrono.getHijrahDateInfo(gregorianDay); |
|
311 |
|
312 chrono.checkValidYearOfEra(dateInfo[1]); |
|
313 chrono.checkValidMonth(dateInfo[2]); |
|
314 chrono.checkValidDayOfMonth(dateInfo[3]); |
|
315 chrono.checkValidDayOfYear(dateInfo[4]); |
|
316 |
|
317 this.era = HijrahEra.of(dateInfo[0]); |
|
318 this.yearOfEra = dateInfo[1]; |
|
319 this.monthOfYear = dateInfo[2]; |
|
320 this.dayOfMonth = dateInfo[3]; |
|
321 this.dayOfYear = dateInfo[4]; |
|
322 this.dayOfWeek = DayOfWeek.of(dateInfo[5]); |
|
323 this.gregorianEpochDay = gregorianDay; |
|
324 this.isLeapYear = chrono.isLeapYear(this.yearOfEra); |
|
325 } |
|
326 |
|
327 //----------------------------------------------------------------------- |
|
328 @Override |
|
329 public HijrahChronology getChronology() { |
|
330 return chrono; |
|
331 } |
|
332 |
|
333 @Override |
|
334 public ValueRange range(TemporalField field) { |
|
335 if (field instanceof ChronoField) { |
|
336 if (isSupported(field)) { |
|
337 ChronoField f = (ChronoField) field; |
|
338 switch (f) { |
|
339 case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); |
|
340 case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); |
|
341 case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, 5); // TODO |
|
342 case YEAR_OF_ERA: return ValueRange.of(1, 1000); // TODO |
|
343 } |
|
344 return getChronology().range(f); |
|
345 } |
|
346 throw new DateTimeException("Unsupported field: " + field.getName()); |
|
347 } |
|
348 return field.rangeRefinedBy(this); |
|
349 } |
|
350 |
|
351 @Override // Override for javadoc |
|
352 public int get(TemporalField field) { |
|
353 return super.get(field); |
|
354 } |
|
355 |
|
356 @Override |
|
357 public long getLong(TemporalField field) { |
|
358 if (field instanceof ChronoField) { |
|
359 switch ((ChronoField) field) { |
|
360 case DAY_OF_WEEK: return dayOfWeek.getValue(); |
|
361 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((dayOfWeek.getValue() - 1) % 7) + 1; |
|
362 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((dayOfYear - 1) % 7) + 1; |
|
363 case DAY_OF_MONTH: return this.dayOfMonth; |
|
364 case DAY_OF_YEAR: return this.dayOfYear; |
|
365 case EPOCH_DAY: return toEpochDay(); |
|
366 case ALIGNED_WEEK_OF_MONTH: return ((dayOfMonth - 1) / 7) + 1; |
|
367 case ALIGNED_WEEK_OF_YEAR: return ((dayOfYear - 1) / 7) + 1; |
|
368 case MONTH_OF_YEAR: return monthOfYear; |
|
369 case YEAR_OF_ERA: return yearOfEra; |
|
370 case YEAR: return yearOfEra; |
|
371 case ERA: return era.getValue(); |
|
372 } |
|
373 throw new DateTimeException("Unsupported field: " + field.getName()); |
|
374 } |
|
375 return field.getFrom(this); |
|
376 } |
|
377 |
|
378 @Override |
|
379 public HijrahDate with(TemporalField field, long newValue) { |
|
380 if (field instanceof ChronoField) { |
|
381 ChronoField f = (ChronoField) field; |
|
382 f.checkValidValue(newValue); // TODO: validate value |
|
383 int nvalue = (int) newValue; |
|
384 switch (f) { |
|
385 case DAY_OF_WEEK: return plusDays(newValue - dayOfWeek.getValue()); |
|
386 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_MONTH)); |
|
387 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_YEAR)); |
|
388 case DAY_OF_MONTH: return resolvePreviousValid(yearOfEra, monthOfYear, nvalue); |
|
389 case DAY_OF_YEAR: return resolvePreviousValid(yearOfEra, ((nvalue - 1) / 30) + 1, ((nvalue - 1) % 30) + 1); |
|
390 case EPOCH_DAY: return new HijrahDate(chrono, nvalue); |
|
391 case ALIGNED_WEEK_OF_MONTH: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_MONTH)) * 7); |
|
392 case ALIGNED_WEEK_OF_YEAR: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_YEAR)) * 7); |
|
393 case MONTH_OF_YEAR: return resolvePreviousValid(yearOfEra, nvalue, dayOfMonth); |
|
394 case YEAR_OF_ERA: return resolvePreviousValid(yearOfEra >= 1 ? nvalue : 1 - nvalue, monthOfYear, dayOfMonth); |
|
395 case YEAR: return resolvePreviousValid(nvalue, monthOfYear, dayOfMonth); |
|
396 case ERA: return resolvePreviousValid(1 - yearOfEra, monthOfYear, dayOfMonth); |
|
397 } |
|
398 throw new DateTimeException("Unsupported field: " + field.getName()); |
|
399 } |
|
400 return (HijrahDate) ChronoLocalDate.super.with(field, newValue); |
|
401 } |
|
402 |
|
403 private HijrahDate resolvePreviousValid(int yearOfEra, int month, int day) { |
|
404 int monthDays = getMonthDays(month - 1, yearOfEra); |
|
405 if (day > monthDays) { |
|
406 day = monthDays; |
|
407 } |
|
408 return HijrahDate.of(chrono, yearOfEra, month, day); |
|
409 } |
|
410 |
|
411 /** |
|
412 * {@inheritDoc} |
|
413 * @throws DateTimeException {@inheritDoc} |
|
414 * @throws ArithmeticException {@inheritDoc} |
|
415 */ |
|
416 @Override |
|
417 public HijrahDate with(TemporalAdjuster adjuster) { |
|
418 return (HijrahDate)super.with(adjuster); |
|
419 } |
|
420 |
|
421 /** |
|
422 * {@inheritDoc} |
|
423 * @throws DateTimeException {@inheritDoc} |
|
424 * @throws ArithmeticException {@inheritDoc} |
|
425 */ |
|
426 @Override |
|
427 public HijrahDate plus(TemporalAmount amount) { |
|
428 return (HijrahDate)super.plus(amount); |
|
429 } |
|
430 |
|
431 /** |
|
432 * {@inheritDoc} |
|
433 * @throws DateTimeException {@inheritDoc} |
|
434 * @throws ArithmeticException {@inheritDoc} |
|
435 */ |
|
436 @Override |
|
437 public HijrahDate minus(TemporalAmount amount) { |
|
438 return (HijrahDate)super.minus(amount); |
|
439 } |
|
440 |
|
441 @Override |
|
442 public long toEpochDay() { |
|
443 return chrono.getGregorianEpochDay(yearOfEra, monthOfYear, dayOfMonth); |
|
444 } |
|
445 |
|
446 //----------------------------------------------------------------------- |
|
447 /** |
|
448 * Checks if the year is a leap year, according to the Hijrah calendar system rules. |
|
449 * |
|
450 * @return true if this date is in a leap year |
|
451 */ |
|
452 @Override |
|
453 public boolean isLeapYear() { |
|
454 return this.isLeapYear; |
|
455 } |
|
456 |
|
457 //----------------------------------------------------------------------- |
|
458 @Override |
|
459 HijrahDate plusYears(long years) { |
|
460 if (years == 0) { |
|
461 return this; |
|
462 } |
|
463 int newYear = Math.addExact(this.yearOfEra, (int)years); |
|
464 return HijrahDate.of(chrono, this.era, newYear, this.monthOfYear, this.dayOfMonth); |
|
465 } |
|
466 |
|
467 @Override |
|
468 HijrahDate plusMonths(long months) { |
|
469 if (months == 0) { |
|
470 return this; |
|
471 } |
|
472 int newMonth = this.monthOfYear - 1; |
|
473 newMonth = newMonth + (int)months; |
|
474 int years = newMonth / 12; |
|
475 newMonth = newMonth % 12; |
|
476 while (newMonth < 0) { |
|
477 newMonth += 12; |
|
478 years = Math.subtractExact(years, 1); |
|
479 } |
|
480 int newYear = Math.addExact(this.yearOfEra, years); |
|
481 return HijrahDate.of(chrono, this.era, newYear, newMonth + 1, this.dayOfMonth); |
|
482 } |
|
483 |
|
484 @Override |
|
485 HijrahDate plusWeeks(long weeksToAdd) { |
|
486 return (HijrahDate)super.plusWeeks(weeksToAdd); |
|
487 } |
|
488 |
|
489 @Override |
|
490 HijrahDate plusDays(long days) { |
|
491 return new HijrahDate(chrono, this.gregorianEpochDay + days); |
|
492 } |
|
493 |
|
494 @Override |
|
495 public HijrahDate plus(long amountToAdd, TemporalUnit unit) { |
|
496 return (HijrahDate)super.plus(amountToAdd, unit); |
|
497 } |
|
498 |
|
499 @Override |
|
500 public HijrahDate minus(long amountToSubtract, TemporalUnit unit) { |
|
501 return (HijrahDate)super.minus(amountToSubtract, unit); |
|
502 } |
|
503 |
|
504 @Override |
|
505 HijrahDate minusYears(long yearsToSubtract) { |
|
506 return (HijrahDate)super.minusYears(yearsToSubtract); |
|
507 } |
|
508 |
|
509 @Override |
|
510 HijrahDate minusMonths(long monthsToSubtract) { |
|
511 return (HijrahDate)super.minusMonths(monthsToSubtract); |
|
512 } |
|
513 |
|
514 @Override |
|
515 HijrahDate minusWeeks(long weeksToSubtract) { |
|
516 return (HijrahDate)super.minusWeeks(weeksToSubtract); |
|
517 } |
|
518 |
|
519 @Override |
|
520 HijrahDate minusDays(long daysToSubtract) { |
|
521 return (HijrahDate)super.minusDays(daysToSubtract); |
|
522 } |
|
523 |
|
524 /** |
|
525 * Returns month days from the beginning of year. |
|
526 * |
|
527 * @param month month (0-based) |
|
528 * @parma year year |
|
529 * @return month days from the beginning of year |
|
530 */ |
|
531 private int getMonthDays(int month, int year) { |
|
532 int[] newMonths = chrono.getAdjustedMonthDays(year); |
|
533 return newMonths[month]; |
|
534 } |
|
535 |
|
536 /** |
|
537 * Returns month length. |
|
538 * |
|
539 * @param month month (0-based) |
|
540 * @param year year |
|
541 * @return month length |
|
542 */ |
|
543 private int getMonthLength(int month, int year) { |
|
544 int[] newMonths = chrono.getAdjustedMonthLength(year); |
|
545 return newMonths[month]; |
|
546 } |
|
547 |
|
548 @Override |
|
549 public int lengthOfMonth() { |
|
550 return getMonthLength(monthOfYear - 1, yearOfEra); |
|
551 } |
|
552 |
|
553 @Override |
|
554 public int lengthOfYear() { |
|
555 return chrono.getYearLength(yearOfEra); // TODO: proleptic year |
|
556 } |
|
557 |
|
558 @Override // for javadoc and covariant return type |
|
559 public final ChronoLocalDateTime<HijrahDate> atTime(LocalTime localTime) { |
|
560 return (ChronoLocalDateTime<HijrahDate>)super.atTime(localTime); |
|
561 } |
|
562 |
|
563 @Override |
|
564 public Period periodUntil(ChronoLocalDate<?> endDate) { |
|
565 // TODO: untested |
|
566 HijrahDate end = (HijrahDate) getChronology().date(endDate); |
|
567 long totalMonths = (end.yearOfEra - this.yearOfEra) * 12 + (end.monthOfYear - this.monthOfYear); // safe |
|
568 int days = end.dayOfMonth - this.dayOfMonth; |
|
569 if (totalMonths > 0 && days < 0) { |
|
570 totalMonths--; |
|
571 HijrahDate calcDate = this.plusMonths(totalMonths); |
|
572 days = (int) (end.toEpochDay() - calcDate.toEpochDay()); // safe |
|
573 } else if (totalMonths < 0 && days > 0) { |
|
574 totalMonths++; |
|
575 days -= end.lengthOfMonth(); |
|
576 } |
|
577 long years = totalMonths / 12; // safe |
|
578 int months = (int) (totalMonths % 12); // safe |
|
579 return Period.of(Math.toIntExact(years), months, days); |
|
580 } |
|
581 |
|
582 //----------------------------------------------------------------------- |
|
583 private Object writeReplace() { |
|
584 return new Ser(Ser.HIJRAH_DATE_TYPE, this); |
|
585 } |
|
586 |
|
587 void writeExternal(ObjectOutput out) throws IOException { |
|
588 // HijrahChronology is implicit in the Hijrah_DATE_TYPE |
|
589 out.writeObject(chrono); |
|
590 out.writeInt(get(YEAR)); |
|
591 out.writeByte(get(MONTH_OF_YEAR)); |
|
592 out.writeByte(get(DAY_OF_MONTH)); |
|
593 } |
|
594 |
|
595 /** |
|
596 * Replaces the date instance from the stream with a valid one. |
|
597 * ReadExternal has already read the fields and created a new instance |
|
598 * from the data. |
|
599 * |
|
600 * @return the resolved date, never null |
|
601 */ |
|
602 private Object readResolve() { |
|
603 return this; |
|
604 } |
|
605 |
|
606 static ChronoLocalDate<HijrahDate> readExternal(ObjectInput in) throws IOException, ClassNotFoundException { |
|
607 HijrahChronology chrono = (HijrahChronology)in.readObject(); |
|
608 int year = in.readInt(); |
|
609 int month = in.readByte(); |
|
610 int dayOfMonth = in.readByte(); |
|
611 return chrono.date(year, month, dayOfMonth); |
|
612 } |
|
613 |
|
614 } |