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