1 /* 2 * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * * Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * * Neither the name of JSR-310 nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package org.threeten.bp.chrono; 33 34 import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; 35 import static org.threeten.bp.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; 36 import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; 37 import static org.threeten.bp.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; 38 import static org.threeten.bp.temporal.ChronoField.DAY_OF_MONTH; 39 import static org.threeten.bp.temporal.ChronoField.MONTH_OF_YEAR; 40 import static org.threeten.bp.temporal.ChronoField.YEAR; 41 42 import java.io.BufferedReader; 43 import java.io.DataInput; 44 import java.io.DataOutput; 45 import java.io.File; 46 import java.io.FileInputStream; 47 import java.io.IOException; 48 import java.io.InputStream; 49 import java.io.InputStreamReader; 50 import java.io.Serializable; 51 import java.text.ParseException; 52 import java.util.HashMap; 53 import java.util.StringTokenizer; 54 import java.util.zip.ZipEntry; 55 import java.util.zip.ZipFile; 56 57 import org.threeten.bp.Clock; 58 import org.threeten.bp.DateTimeException; 59 import org.threeten.bp.DayOfWeek; 60 import org.threeten.bp.LocalDate; 61 import org.threeten.bp.LocalTime; 62 import org.threeten.bp.ZoneId; 63 import org.threeten.bp.jdk8.Jdk8Methods; 64 import org.threeten.bp.temporal.ChronoField; 65 import org.threeten.bp.temporal.TemporalAccessor; 66 import org.threeten.bp.temporal.TemporalAdjuster; 67 import org.threeten.bp.temporal.TemporalAmount; 68 import org.threeten.bp.temporal.TemporalField; 69 import org.threeten.bp.temporal.TemporalQuery; 70 import org.threeten.bp.temporal.TemporalUnit; 71 import org.threeten.bp.temporal.UnsupportedTemporalTypeException; 72 import org.threeten.bp.temporal.ValueRange; 73 74 /** 75 * A date in the Hijrah calendar system. 76 * <p> 77 * This implements {@code ChronoLocalDate} for the {@link HijrahChronology Hijrah calendar}. 78 * <p> 79 * The Hijrah calendar has a different total of days in a year than 80 * Gregorian calendar, and a month is based on the period of a complete 81 * revolution of the moon around the earth (as between successive new moons). 82 * The calendar cycles becomes longer and unstable, and sometimes a manual 83 * adjustment (for entering deviation) is necessary for correctness 84 * because of the complex algorithm. 85 * <p> 86 * HijrahDate supports the manual adjustment feature by providing a configuration 87 * file. The configuration file contains the adjustment (deviation) data with following format. 88 * <pre> 89 * StartYear/StartMonth(0-based)-EndYear/EndMonth(0-based):Deviation day (1, 2, -1, or -2) 90 * Line separator or ";" is used for the separator of each deviation data.</pre> 91 * Here is the example. 92 * <pre> 93 * 1429/0-1429/1:1 94 * 1429/2-1429/7:1;1429/6-1429/11:1 95 * 1429/11-9999/11:1</pre> 96 * The default location of the configuration file is: 97 * <pre> 98 * $CLASSPATH/org/threeten/bp/chrono</pre> 99 * And the default file name is: 100 * <pre> 101 * hijrah_deviation.cfg</pre> 102 * The default location and file name can be overriden by setting 103 * following two Java's system property. 104 * <pre> 105 * Location: org.threeten.bp.i18n.HijrahDate.deviationConfigDir 106 * File name: org.threeten.bp.i18n.HijrahDate.deviationConfigFile</pre> 107 * 108 * <h3>Specification for implementors</h3> 109 * This class is immutable and thread-safe. 110 */ 111 public final class HijrahDate 112 extends ChronoDateImpl<HijrahDate> 113 implements Serializable { 114 // this class is package-scoped so that future conversion to public 115 // would not change serialization 116 117 /** 118 * Serialization version. 119 */ 120 private static final long serialVersionUID = -5207853542612002020L; 121 122 /** 123 * The minimum valid year-of-era. 124 */ 125 public static final int MIN_VALUE_OF_ERA = 1; 126 /** 127 * The maximum valid year-of-era. 128 * This is currently set to 9999 but may be changed to increase the valid range 129 * in a future version of the specification. 130 */ 131 public static final int MAX_VALUE_OF_ERA = 9999; 132 /** 133 * 0-based, for number of day-of-year in the beginning of month in normal 134 * year. 135 */ 136 private static final int NUM_DAYS[] = 137 {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325}; 138 /** 139 * 0-based, for number of day-of-year in the beginning of month in leap year. 140 */ 141 private static final int LEAP_NUM_DAYS[] = 142 {0, 30, 59, 89, 118, 148, 177, 207, 236, 266, 295, 325}; 143 /** 144 * 0-based, for day-of-month in normal year. 145 */ 146 private static final int MONTH_LENGTH[] = 147 {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29}; 148 /** 149 * 0-based, for day-of-month in leap year. 150 */ 151 private static final int LEAP_MONTH_LENGTH[] = 152 {30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30}; 153 154 /** 155 * <pre> 156 * Greatest Least 157 * Field name Minimum Minimum Maximum Maximum 158 * ---------- ------- ------- ------- ------- 159 * ERA 0 0 1 1 160 * YEAR_OF_ERA 1 1 9999 9999 161 * MONTH_OF_YEAR 1 1 12 12 162 * DAY_OF_MONTH 1 1 29 30 163 * DAY_OF_YEAR 1 1 354 355 164 * </pre> 165 * 166 * Minimum values. 167 */ 168 private static final int MIN_VALUES[] = 169 { 170 0, 171 MIN_VALUE_OF_ERA, 172 0, 173 1, 174 0, 175 1, 176 1 177 }; 178 179 /** 180 * Least maximum values. 181 */ 182 private static final int LEAST_MAX_VALUES[] = 183 { 184 1, 185 MAX_VALUE_OF_ERA, 186 11, 187 51, 188 5, 189 29, 190 354 191 }; 192 193 /** 194 * Maximum values. 195 */ 196 private static final int MAX_VALUES[] = 197 { 198 1, 199 MAX_VALUE_OF_ERA, 200 11, 201 52, 202 6, 203 30, 204 355 205 }; 206 207 /** 208 * Position of day-of-month. This value is used to get the min/max value 209 * from an array. 210 */ 211 private static final int POSITION_DAY_OF_MONTH = 5; 212 /** 213 * Position of day-of-year. This value is used to get the min/max value from 214 * an array. 215 */ 216 private static final int POSITION_DAY_OF_YEAR = 6; 217 /** 218 * Zero-based start date of cycle year. 219 */ 220 private static final int CYCLEYEAR_START_DATE[] = 221 { 222 0, 223 354, 224 709, 225 1063, 226 1417, 227 1772, 228 2126, 229 2481, 230 2835, 231 3189, 232 3544, 233 3898, 234 4252, 235 4607, 236 4961, 237 5315, 238 5670, 239 6024, 240 6379, 241 6733, 242 7087, 243 7442, 244 7796, 245 8150, 246 8505, 247 8859, 248 9214, 249 9568, 250 9922, 251 10277 252 }; 253 254 /** 255 * File separator. 256 */ 257 private static final char FILE_SEP = File.separatorChar; 258 /** 259 * Path separator. 260 */ 261 private static final String PATH_SEP = File.pathSeparator; 262 /** 263 * Default config file name. 264 */ 265 private static final String DEFAULT_CONFIG_FILENAME = "hijrah_deviation.cfg"; 266 /** 267 * Default path to the config file. 268 */ 269 private static final String DEFAULT_CONFIG_PATH = "org" + FILE_SEP + "threeten" + FILE_SEP + "bp" + FILE_SEP + "chrono"; 270 /** 271 * Holding the adjusted month days in year. The key is a year (Integer) and 272 * the value is the all the month days in year (Integer[]). 273 */ 274 private static final HashMap<Integer, Integer[]> ADJUSTED_MONTH_DAYS = new HashMap<Integer, Integer[]>(); 275 /** 276 * Holding the adjusted month length in year. The key is a year (Integer) 277 * and the value is the all the month length in year (Integer[]). 278 */ 279 private static final HashMap<Integer, Integer[]> ADJUSTED_MONTH_LENGTHS = new HashMap<Integer, Integer[]>(); 280 /** 281 * Holding the adjusted days in the 30 year cycle. The key is a cycle number 282 * (Integer) and the value is the all the starting days of the year in the 283 * cycle (Integer[]). 284 */ 285 private static final HashMap<Integer, Integer[]> ADJUSTED_CYCLE_YEARS = new HashMap<Integer, Integer[]>(); 286 /** 287 * Holding the adjusted cycle in the 1 - 30000 year. The key is the cycle 288 * number (Integer) and the value is the starting days in the cycle in the 289 * term. 290 */ 291 private static final Long[] ADJUSTED_CYCLES; 292 /** 293 * Holding the adjusted min values. 294 */ 295 private static final Integer[] ADJUSTED_MIN_VALUES; 296 /** 297 * Holding the adjusted max least max values. 298 */ 299 private static final Integer[] ADJUSTED_LEAST_MAX_VALUES; 300 /** 301 * Holding adjusted max values. 302 */ 303 private static final Integer[] ADJUSTED_MAX_VALUES; 304 /** 305 * Holding the non-adjusted month days in year for non leap year. 306 */ 307 private static final Integer[] DEFAULT_MONTH_DAYS; 308 /** 309 * Holding the non-adjusted month days in year for leap year. 310 */ 311 private static final Integer[] DEFAULT_LEAP_MONTH_DAYS; 312 /** 313 * Holding the non-adjusted month length for non leap year. 314 */ 315 private static final Integer[] DEFAULT_MONTH_LENGTHS; 316 /** 317 * Holding the non-adjusted month length for leap year. 318 */ 319 private static final Integer[] DEFAULT_LEAP_MONTH_LENGTHS; 320 /** 321 * Holding the non-adjusted 30 year cycle starting day. 322 */ 323 private static final Integer[] DEFAULT_CYCLE_YEARS; 324 /** 325 * number of 30-year cycles to hold the deviation data. 326 */ 327 private static final int MAX_ADJUSTED_CYCLE = 334; // to support year 9999 328 329 static { // Initialize the static integer array; 330 331 DEFAULT_MONTH_DAYS = new Integer[NUM_DAYS.length]; 332 for (int i = 0; i < NUM_DAYS.length; i++) { 333 DEFAULT_MONTH_DAYS[i] = Integer.valueOf(NUM_DAYS[i]); 334 } 335 336 DEFAULT_LEAP_MONTH_DAYS = new Integer[LEAP_NUM_DAYS.length]; 337 for (int i = 0; i < LEAP_NUM_DAYS.length; i++) { 338 DEFAULT_LEAP_MONTH_DAYS[i] = Integer.valueOf(LEAP_NUM_DAYS[i]); 339 } 340 341 DEFAULT_MONTH_LENGTHS = new Integer[MONTH_LENGTH.length]; 342 for (int i = 0; i < MONTH_LENGTH.length; i++) { 343 DEFAULT_MONTH_LENGTHS[i] = Integer.valueOf(MONTH_LENGTH[i]); 344 } 345 346 DEFAULT_LEAP_MONTH_LENGTHS = new Integer[LEAP_MONTH_LENGTH.length]; 347 for (int i = 0; i < LEAP_MONTH_LENGTH.length; i++) { 348 DEFAULT_LEAP_MONTH_LENGTHS[i] = Integer.valueOf(LEAP_MONTH_LENGTH[i]); 349 } 350 351 DEFAULT_CYCLE_YEARS = new Integer[CYCLEYEAR_START_DATE.length]; 352 for (int i = 0; i < CYCLEYEAR_START_DATE.length; i++) { 353 DEFAULT_CYCLE_YEARS[i] = Integer.valueOf(CYCLEYEAR_START_DATE[i]); 354 } 355 356 ADJUSTED_CYCLES = new Long[MAX_ADJUSTED_CYCLE]; 357 for (int i = 0; i < ADJUSTED_CYCLES.length; i++) { 358 ADJUSTED_CYCLES[i] = Long.valueOf(10631 * i); 359 } 360 // Initialize min values, least max values and max values. 361 ADJUSTED_MIN_VALUES = new Integer[MIN_VALUES.length]; 362 for (int i = 0; i < MIN_VALUES.length; i++) { 363 ADJUSTED_MIN_VALUES[i] = Integer.valueOf(MIN_VALUES[i]); 364 } 365 ADJUSTED_LEAST_MAX_VALUES = new Integer[LEAST_MAX_VALUES.length]; 366 for (int i = 0; i < LEAST_MAX_VALUES.length; i++) { 367 ADJUSTED_LEAST_MAX_VALUES[i] = Integer.valueOf(LEAST_MAX_VALUES[i]); 368 } 369 ADJUSTED_MAX_VALUES = new Integer[MAX_VALUES.length]; 370 for (int i = 0; i < MAX_VALUES.length; i++) { 371 ADJUSTED_MAX_VALUES[i] = Integer.valueOf(MAX_VALUES[i]); 372 } 373 try { readDeviationConfig()374 readDeviationConfig(); 375 } catch (IOException e) { 376 // do nothing. Ignore deviation config. 377 // e.printStackTrace(); 378 } catch (ParseException e) { 379 // do nothing. Ignore deviation config. 380 // e.printStackTrace(); 381 } 382 } 383 /** 384 * Number of Gregorian day of July 19, year 622 (Gregorian), which is epoch day 385 * of Hijrah calendar. 386 */ 387 private static final int HIJRAH_JAN_1_1_GREGORIAN_DAY = -492148; 388 389 /** 390 * The era. 391 */ 392 private final transient HijrahEra era; 393 /** 394 * The year. 395 */ 396 private final transient int yearOfEra; 397 /** 398 * The month-of-year. 399 */ 400 private final transient int monthOfYear; 401 /** 402 * The day-of-month. 403 */ 404 private final transient int dayOfMonth; 405 /** 406 * The day-of-year. 407 */ 408 private final transient int dayOfYear; 409 /** 410 * The day-of-week. 411 */ 412 private final transient DayOfWeek dayOfWeek; 413 /** 414 * Gregorian days for this object. Holding number of days since 1970/01/01. 415 * The number of days are calculated with pure Gregorian calendar 416 * based. 417 */ 418 private final long gregorianEpochDay; 419 /** 420 * True if year is leap year. 421 */ 422 private final transient boolean isLeapYear; 423 424 //----------------------------------------------------------------------- 425 /** 426 * Obtains the current {@code HijrahDate} of the Islamic Umm Al-Qura calendar 427 * in the default time-zone. 428 * <p> 429 * This will query the {@link Clock#systemDefaultZone() system clock} in the default 430 * time-zone to obtain the current date. 431 * <p> 432 * Using this method will prevent the ability to use an alternate clock for testing 433 * because the clock is hard-coded. 434 * 435 * @return the current date using the system clock and default time-zone, not null 436 */ now()437 public static HijrahDate now() { 438 return now(Clock.systemDefaultZone()); 439 } 440 441 /** 442 * Obtains the current {@code HijrahDate} of the Islamic Umm Al-Qura calendar 443 * in the specified time-zone. 444 * <p> 445 * This will query the {@link Clock#system(ZoneId) system clock} to obtain the current date. 446 * Specifying the time-zone avoids dependence on the default time-zone. 447 * <p> 448 * Using this method will prevent the ability to use an alternate clock for testing 449 * because the clock is hard-coded. 450 * 451 * @param zone the zone ID to use, not null 452 * @return the current date using the system clock, not null 453 */ now(ZoneId zone)454 public static HijrahDate now(ZoneId zone) { 455 return now(Clock.system(zone)); 456 } 457 458 /** 459 * Obtains the current {@code HijrahDate} of the Islamic Umm Al-Qura calendar 460 * from the specified clock. 461 * <p> 462 * This will query the specified clock to obtain the current date - today. 463 * Using this method allows the use of an alternate clock for testing. 464 * The alternate clock may be introduced using {@linkplain Clock dependency injection}. 465 * 466 * @param clock the clock to use, not null 467 * @return the current date, not null 468 * @throws DateTimeException if the current date cannot be obtained 469 */ now(Clock clock)470 public static HijrahDate now(Clock clock) { 471 return HijrahChronology.INSTANCE.dateNow(clock); 472 } 473 474 //------------------------------------------------------------------------- 475 /** 476 * Obtains an instance of {@code HijrahDate} from the Hijrah era year, 477 * month-of-year and day-of-month. This uses the Hijrah era. 478 * 479 * @param prolepticYear the proleptic year to represent in the Hijrah 480 * @param monthOfYear the month-of-year to represent, from 1 to 12 481 * @param dayOfMonth the day-of-month to represent, from 1 to 30 482 * @return the Hijrah date, never null 483 * @throws IllegalCalendarFieldValueException if the value of any field is out of range 484 * @throws InvalidCalendarFieldException if the day-of-month is invalid for the month-year 485 */ of(int prolepticYear, int monthOfYear, int dayOfMonth)486 public static HijrahDate of(int prolepticYear, int monthOfYear, int dayOfMonth) { 487 return (prolepticYear >= 1) ? 488 HijrahDate.of(HijrahEra.AH, prolepticYear, monthOfYear, dayOfMonth) : 489 HijrahDate.of(HijrahEra.BEFORE_AH, 1 - prolepticYear, monthOfYear, dayOfMonth); 490 } 491 492 /** 493 * Obtains an instance of {@code HijrahDate} from the era, year-of-era 494 * month-of-year and day-of-month. 495 * 496 * @param era the era to represent, not null 497 * @param yearOfEra the year-of-era to represent, from 1 to 9999 498 * @param monthOfYear the month-of-year to represent, from 1 to 12 499 * @param dayOfMonth the day-of-month to represent, from 1 to 31 500 * @return the Hijrah date, never null 501 * @throws IllegalCalendarFieldValueException if the value of any field is out of range 502 * @throws InvalidCalendarFieldException if the day-of-month is invalid for the month-year 503 */ of(HijrahEra era, int yearOfEra, int monthOfYear, int dayOfMonth)504 static HijrahDate of(HijrahEra era, int yearOfEra, int monthOfYear, int dayOfMonth) { 505 Jdk8Methods.requireNonNull(era, "era"); 506 checkValidYearOfEra(yearOfEra); 507 checkValidMonth(monthOfYear); 508 checkValidDayOfMonth(dayOfMonth); 509 long gregorianDays = getGregorianEpochDay(era.prolepticYear(yearOfEra), monthOfYear, dayOfMonth); 510 return new HijrahDate(gregorianDays); 511 } 512 513 /** 514 * Check the validity of a yearOfEra. 515 * @param yearOfEra the year to check 516 */ checkValidYearOfEra(int yearOfEra)517 private static void checkValidYearOfEra(int yearOfEra) { 518 if (yearOfEra < MIN_VALUE_OF_ERA || 519 yearOfEra > MAX_VALUE_OF_ERA) { 520 throw new DateTimeException("Invalid year of Hijrah Era"); 521 } 522 } 523 checkValidDayOfYear(int dayOfYear)524 private static void checkValidDayOfYear(int dayOfYear) { 525 if (dayOfYear < 1 || 526 dayOfYear > getMaximumDayOfYear()) { 527 throw new DateTimeException("Invalid day of year of Hijrah date"); 528 } 529 } 530 checkValidMonth(int month)531 private static void checkValidMonth(int month) { 532 if (month < 1 || month > 12) { 533 throw new DateTimeException("Invalid month of Hijrah date"); 534 } 535 } 536 checkValidDayOfMonth(int dayOfMonth)537 private static void checkValidDayOfMonth(int dayOfMonth) { 538 if (dayOfMonth < 1 || 539 dayOfMonth > getMaximumDayOfMonth()) { 540 throw new DateTimeException("Invalid day of month of Hijrah date, day " 541 + dayOfMonth + " greater than " + getMaximumDayOfMonth() + " or less than 1"); 542 } 543 } 544 545 /** 546 * Obtains an instance of {@code HijrahDate} from a date. 547 * 548 * @param date the date to use, not null 549 * @return the Hijrah date, never null 550 * @throws IllegalCalendarFieldValueException if the year is invalid 551 */ of(LocalDate date)552 static HijrahDate of(LocalDate date) { 553 long gregorianDays = date.toEpochDay(); 554 return new HijrahDate(gregorianDays); 555 } 556 ofEpochDay(long epochDay)557 static HijrahDate ofEpochDay(long epochDay) { 558 return new HijrahDate(epochDay); 559 } 560 561 /** 562 * Obtains a {@code HijrahDate} of the Islamic Umm Al-Qura calendar from a temporal object. 563 * <p> 564 * This obtains a date in the Hijrah calendar system based on the specified temporal. 565 * A {@code TemporalAccessor} represents an arbitrary set of date and time information, 566 * which this factory converts to an instance of {@code HijrahDate}. 567 * <p> 568 * The conversion typically uses the {@link ChronoField#EPOCH_DAY EPOCH_DAY} 569 * field, which is standardized across calendar systems. 570 * <p> 571 * This method matches the signature of the functional interface {@link TemporalQuery} 572 * allowing it to be used as a query via method reference, {@code HijrahDate::from}. 573 * 574 * @param temporal the temporal object to convert, not null 575 * @return the date in Hijrah calendar system, not null 576 * @throws DateTimeException if unable to convert to a {@code HijrahDate} 577 */ from(TemporalAccessor temporal)578 public static HijrahDate from(TemporalAccessor temporal) { 579 return HijrahChronology.INSTANCE.date(temporal); 580 } 581 582 //------------------------------------------------------------------------- 583 /** 584 * Constructs an instance with the specified date. 585 * 586 * @param gregorianDay the number of days from 0001/01/01 (Gregorian), caller calculated 587 */ HijrahDate(long gregorianDay)588 private HijrahDate(long gregorianDay) { 589 int[] dateInfo = getHijrahDateInfo(gregorianDay); 590 591 checkValidYearOfEra(dateInfo[1]); 592 checkValidMonth(dateInfo[2]); 593 checkValidDayOfMonth(dateInfo[3]); 594 checkValidDayOfYear(dateInfo[4]); 595 596 this.era = HijrahEra.of(dateInfo[0]); 597 this.yearOfEra = dateInfo[1]; 598 this.monthOfYear = dateInfo[2]; 599 this.dayOfMonth = dateInfo[3]; 600 this.dayOfYear = dateInfo[4]; 601 this.dayOfWeek = DayOfWeek.of(dateInfo[5]); 602 this.gregorianEpochDay = gregorianDay; 603 this.isLeapYear = isLeapYear(this.yearOfEra); 604 } 605 606 /** 607 * Replaces the date instance from the stream with a valid one. 608 * 609 * @return the resolved date, never null 610 */ readResolve()611 private Object readResolve() { 612 return new HijrahDate(this.gregorianEpochDay); 613 } 614 615 //----------------------------------------------------------------------- 616 @Override getChronology()617 public HijrahChronology getChronology() { 618 return HijrahChronology.INSTANCE; 619 } 620 621 @Override getEra()622 public HijrahEra getEra() { 623 return this.era; 624 } 625 626 @Override range(TemporalField field)627 public ValueRange range(TemporalField field) { 628 if (field instanceof ChronoField) { 629 if (isSupported(field)) { 630 ChronoField f = (ChronoField) field; 631 switch (f) { 632 case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); 633 case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); 634 case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, 5); // TODO 635 case YEAR_OF_ERA: return ValueRange.of(1, 1000); // TODO 636 } 637 return getChronology().range(f); 638 } 639 throw new UnsupportedTemporalTypeException("Unsupported field: " + field); 640 } 641 return field.rangeRefinedBy(this); 642 } 643 644 @Override getLong(TemporalField field)645 public long getLong(TemporalField field) { 646 if (field instanceof ChronoField) { 647 switch ((ChronoField) field) { 648 case DAY_OF_WEEK: return dayOfWeek.getValue(); 649 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((dayOfMonth - 1) % 7) + 1; 650 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((dayOfYear - 1) % 7) + 1; 651 case DAY_OF_MONTH: return this.dayOfMonth; 652 case DAY_OF_YEAR: return this.dayOfYear; 653 case EPOCH_DAY: return toEpochDay(); 654 case ALIGNED_WEEK_OF_MONTH: return ((dayOfMonth - 1) / 7) + 1; 655 case ALIGNED_WEEK_OF_YEAR: return ((dayOfYear - 1) / 7) + 1; 656 case MONTH_OF_YEAR: return monthOfYear; 657 case YEAR_OF_ERA: return yearOfEra; 658 case YEAR: return yearOfEra; 659 case ERA: return era.getValue(); 660 } 661 throw new UnsupportedTemporalTypeException("Unsupported field: " + field); 662 } 663 return field.getFrom(this); 664 } 665 666 //------------------------------------------------------------------------- 667 @Override with(TemporalAdjuster adjuster)668 public HijrahDate with(TemporalAdjuster adjuster) { 669 return (HijrahDate) super.with(adjuster); 670 } 671 672 @Override with(TemporalField field, long newValue)673 public HijrahDate with(TemporalField field, long newValue) { 674 if (field instanceof ChronoField) { 675 ChronoField f = (ChronoField) field; 676 f.checkValidValue(newValue); // TODO: validate value 677 int nvalue = (int) newValue; 678 switch (f) { 679 case DAY_OF_WEEK: return plusDays(newValue - dayOfWeek.getValue()); 680 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_MONTH)); 681 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_YEAR)); 682 case DAY_OF_MONTH: return resolvePreviousValid(yearOfEra, monthOfYear, nvalue); 683 case DAY_OF_YEAR: return resolvePreviousValid(yearOfEra, ((nvalue - 1) / 30) + 1, ((nvalue - 1) % 30) + 1); 684 case EPOCH_DAY: return new HijrahDate(nvalue); 685 case ALIGNED_WEEK_OF_MONTH: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_MONTH)) * 7); 686 case ALIGNED_WEEK_OF_YEAR: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_YEAR)) * 7); 687 case MONTH_OF_YEAR: return resolvePreviousValid(yearOfEra, nvalue, dayOfMonth); 688 case YEAR_OF_ERA: return resolvePreviousValid(yearOfEra >= 1 ? nvalue : 1 - nvalue, monthOfYear, dayOfMonth); 689 case YEAR: return resolvePreviousValid(nvalue, monthOfYear, dayOfMonth); 690 case ERA: return resolvePreviousValid(1 - yearOfEra, monthOfYear, dayOfMonth); 691 } 692 throw new UnsupportedTemporalTypeException("Unsupported field: " + field); 693 } 694 return field.adjustInto(this, newValue); 695 } 696 resolvePreviousValid(int yearOfEra, int month, int day)697 private static HijrahDate resolvePreviousValid(int yearOfEra, int month, int day) { 698 int monthDays = getMonthDays(month - 1, yearOfEra); 699 if (day > monthDays) { 700 day = monthDays; 701 } 702 return HijrahDate.of(yearOfEra, month, day); 703 } 704 705 @Override plus(TemporalAmount amount)706 public HijrahDate plus(TemporalAmount amount) { 707 return (HijrahDate) super.plus(amount); 708 } 709 710 @Override plus(long amountToAdd, TemporalUnit unit)711 public HijrahDate plus(long amountToAdd, TemporalUnit unit) { 712 return (HijrahDate) super.plus(amountToAdd, unit); 713 } 714 715 @Override minus(TemporalAmount amount)716 public HijrahDate minus(TemporalAmount amount) { 717 return (HijrahDate) super.minus(amount); 718 } 719 720 @Override minus(long amountToAdd, TemporalUnit unit)721 public HijrahDate minus(long amountToAdd, TemporalUnit unit) { 722 return (HijrahDate) super.minus(amountToAdd, unit); 723 } 724 725 //------------------------------------------------------------------------- 726 @Override 727 @SuppressWarnings("unchecked") atTime(LocalTime localTime)728 public final ChronoLocalDateTime<HijrahDate> atTime(LocalTime localTime) { 729 return (ChronoLocalDateTime<HijrahDate>) super.atTime(localTime); 730 } 731 732 @Override toEpochDay()733 public long toEpochDay() { 734 return getGregorianEpochDay(yearOfEra, monthOfYear, dayOfMonth); 735 } 736 737 //----------------------------------------------------------------------- 738 /** 739 * Checks if the year is a leap year, according to the Hijrah calendar system rules. 740 * 741 * @return true if this date is in a leap year 742 */ 743 @Override isLeapYear()744 public boolean isLeapYear() { 745 return this.isLeapYear; 746 } 747 748 //----------------------------------------------------------------------- 749 @Override plusYears(long years)750 HijrahDate plusYears(long years) { 751 if (years == 0) { 752 return this; 753 } 754 int newYear = Jdk8Methods.safeAdd(this.yearOfEra, (int)years); 755 return HijrahDate.of(this.era, newYear, this.monthOfYear, this.dayOfMonth); 756 } 757 758 @Override plusMonths(long months)759 HijrahDate plusMonths(long months) { 760 if (months == 0) { 761 return this; 762 } 763 int newMonth = this.monthOfYear - 1; 764 newMonth = newMonth + (int)months; 765 int years = newMonth / 12; 766 newMonth = newMonth % 12; 767 while (newMonth < 0) { 768 newMonth += 12; 769 years = Jdk8Methods.safeSubtract(years, 1); 770 } 771 int newYear = Jdk8Methods.safeAdd(this.yearOfEra, years); 772 return HijrahDate.of(this.era, newYear, newMonth + 1, this.dayOfMonth); 773 } 774 775 @Override plusDays(long days)776 HijrahDate plusDays(long days) { 777 return new HijrahDate(this.gregorianEpochDay + days); 778 } 779 780 //----------------------------------------------------------------------- 781 /** 782 * Returns the int array containing the following field from the julian day. 783 * 784 * int[0] = ERA 785 * int[1] = YEAR 786 * int[2] = MONTH 787 * int[3] = DATE 788 * int[4] = DAY_OF_YEAR 789 * int[5] = DAY_OF_WEEK 790 * 791 * @param julianDay a julian day. 792 */ getHijrahDateInfo(long gregorianDays)793 private static int[] getHijrahDateInfo(long gregorianDays) { 794 int era, year, month, date, dayOfWeek, dayOfYear; 795 796 int cycleNumber, yearInCycle, dayOfCycle; 797 798 long epochDay = gregorianDays - HIJRAH_JAN_1_1_GREGORIAN_DAY; 799 800 if (epochDay >= 0) { 801 cycleNumber = getCycleNumber(epochDay); // 0 - 99. 802 dayOfCycle = getDayOfCycle(epochDay, cycleNumber); // 0 - 10631. 803 yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29. 804 dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle); 805 // 0 - 354/355 806 year = cycleNumber * 30 + yearInCycle + 1; // 1-based year. 807 month = getMonthOfYear(dayOfYear, year); // 0-based month-of-year 808 date = getDayOfMonth(dayOfYear, month, year); // 0-based date 809 ++date; // Convert from 0-based to 1-based 810 era = HijrahEra.AH.getValue(); 811 } else { 812 cycleNumber = (int) epochDay / 10631; // 0 or negative number. 813 dayOfCycle = (int) epochDay % 10631; // -10630 - 0. 814 if (dayOfCycle == 0) { 815 dayOfCycle = -10631; 816 cycleNumber++; 817 } 818 yearInCycle = getYearInCycle(cycleNumber, dayOfCycle); // 0 - 29. 819 dayOfYear = getDayOfYear(cycleNumber, dayOfCycle, yearInCycle); 820 year = cycleNumber * 30 - yearInCycle; // negative number. 821 year = 1 - year; 822 dayOfYear = (isLeapYear(year) ? (dayOfYear + 355) 823 : (dayOfYear + 354)); 824 month = getMonthOfYear(dayOfYear, year); 825 date = getDayOfMonth(dayOfYear, month, year); 826 ++date; // Convert from 0-based to 1-based 827 era = HijrahEra.BEFORE_AH.getValue(); 828 } 829 // Hijrah day zero is a Friday 830 dayOfWeek = (int) ((epochDay + 5) % 7); 831 dayOfWeek += (dayOfWeek <= 0) ? 7 : 0; 832 833 int dateInfo[] = new int[6]; 834 dateInfo[0] = era; 835 dateInfo[1] = year; 836 dateInfo[2] = month + 1; // change to 1-based. 837 dateInfo[3] = date; 838 dateInfo[4] = dayOfYear + 1; // change to 1-based. 839 dateInfo[5] = dayOfWeek; 840 return dateInfo; 841 } 842 843 /** 844 * Return Gregorian epoch day from Hijrah year, month, and day. 845 * 846 * @param prolepticYear the year to represent, caller calculated 847 * @param monthOfYear the month-of-year to represent, caller calculated 848 * @param dayOfMonth the day-of-month to represent, caller calculated 849 * @return a julian day 850 */ getGregorianEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth)851 private static long getGregorianEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) { 852 long day = yearToGregorianEpochDay(prolepticYear); 853 day += getMonthDays(monthOfYear - 1, prolepticYear); 854 day += dayOfMonth; 855 return day; 856 } 857 858 /** 859 * Returns the Gregorian epoch day from the proleptic year 860 * @param prolepticYear the proleptic year 861 * @return the Epoch day 862 */ yearToGregorianEpochDay(int prolepticYear)863 private static long yearToGregorianEpochDay(int prolepticYear) { 864 865 int cycleNumber = (prolepticYear - 1) / 30; // 0-based. 866 int yearInCycle = (prolepticYear - 1) % 30; // 0-based. 867 868 int dayInCycle = getAdjustedCycle(cycleNumber)[Math.abs(yearInCycle)] 869 .intValue(); 870 871 if (yearInCycle < 0) { 872 dayInCycle = -dayInCycle; 873 } 874 875 Long cycleDays; 876 877 try { 878 cycleDays = ADJUSTED_CYCLES[cycleNumber]; 879 } catch (ArrayIndexOutOfBoundsException e) { 880 cycleDays = null; 881 } 882 883 if (cycleDays == null) { 884 cycleDays = Long.valueOf(cycleNumber * 10631); 885 } 886 887 return (cycleDays.longValue() + dayInCycle + HIJRAH_JAN_1_1_GREGORIAN_DAY - 1); 888 } 889 890 /** 891 * Returns the 30 year cycle number from the epoch day. 892 * 893 * @param epochDay an epoch day 894 * @return a cycle number 895 */ getCycleNumber(long epochDay)896 private static int getCycleNumber(long epochDay) { 897 Long[] days = ADJUSTED_CYCLES; 898 int cycleNumber; 899 try { 900 for (int i = 0; i < days.length; i++) { 901 if (epochDay < days[i].longValue()) { 902 return i - 1; 903 } 904 } 905 cycleNumber = (int) epochDay / 10631; 906 } catch (ArrayIndexOutOfBoundsException e) { 907 cycleNumber = (int) epochDay / 10631; 908 } 909 return cycleNumber; 910 } 911 912 /** 913 * Returns day of cycle from the epoch day and cycle number. 914 * 915 * @param epochDay an epoch day 916 * @param cycleNumber a cycle number 917 * @return a day of cycle 918 */ getDayOfCycle(long epochDay, int cycleNumber)919 private static int getDayOfCycle(long epochDay, int cycleNumber) { 920 Long day; 921 922 try { 923 day = ADJUSTED_CYCLES[cycleNumber]; 924 } catch (ArrayIndexOutOfBoundsException e) { 925 day = null; 926 } 927 if (day == null) { 928 day = Long.valueOf(cycleNumber * 10631); 929 } 930 return (int) (epochDay - day.longValue()); 931 } 932 933 /** 934 * Returns the year in cycle from the cycle number and day of cycle. 935 * 936 * @param cycleNumber a cycle number 937 * @param dayOfCycle day of cycle 938 * @return a year in cycle 939 */ getYearInCycle(int cycleNumber, long dayOfCycle)940 private static int getYearInCycle(int cycleNumber, long dayOfCycle) { 941 Integer[] cycles = getAdjustedCycle(cycleNumber); 942 if (dayOfCycle == 0) { 943 return 0; 944 } 945 946 if (dayOfCycle > 0) { 947 for (int i = 0; i < cycles.length; i++) { 948 if (dayOfCycle < cycles[i].intValue()) { 949 return i - 1; 950 } 951 } 952 return 29; 953 } else { 954 dayOfCycle = -dayOfCycle; 955 for (int i = 0; i < cycles.length; i++) { 956 if (dayOfCycle <= cycles[i].intValue()) { 957 return i - 1; 958 } 959 } 960 return 29; 961 } 962 } 963 964 /** 965 * Returns adjusted 30 year cycle startind day as Integer array from the 966 * cycle number specified. 967 * 968 * @param cycleNumber a cycle number 969 * @return an Integer array 970 */ getAdjustedCycle(int cycleNumber)971 private static Integer[] getAdjustedCycle(int cycleNumber) { 972 Integer[] cycles; 973 try { 974 cycles = ADJUSTED_CYCLE_YEARS.get(Integer.valueOf(cycleNumber)); 975 } catch (ArrayIndexOutOfBoundsException e) { 976 cycles = null; 977 } 978 if (cycles == null) { 979 cycles = DEFAULT_CYCLE_YEARS; 980 } 981 return cycles; 982 } 983 984 /** 985 * Returns adjusted month days as Integer array form the year specified. 986 * 987 * @param year a year 988 * @return an Integer array 989 */ getAdjustedMonthDays(int year)990 private static Integer[] getAdjustedMonthDays(int year) { 991 Integer[] newMonths; 992 try { 993 newMonths = ADJUSTED_MONTH_DAYS.get(Integer.valueOf(year)); 994 } catch (ArrayIndexOutOfBoundsException e) { 995 newMonths = null; 996 } 997 if (newMonths == null) { 998 if (isLeapYear(year)) { 999 newMonths = DEFAULT_LEAP_MONTH_DAYS; 1000 } else { 1001 newMonths = DEFAULT_MONTH_DAYS; 1002 } 1003 } 1004 return newMonths; 1005 } 1006 1007 /** 1008 * Returns adjusted month length as Integer array form the year specified. 1009 * 1010 * @param year a year 1011 * @return an Integer array 1012 */ getAdjustedMonthLength(int year)1013 private static Integer[] getAdjustedMonthLength(int year) { 1014 Integer[] newMonths; 1015 try { 1016 newMonths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf(year)); 1017 } catch (ArrayIndexOutOfBoundsException e) { 1018 newMonths = null; 1019 } 1020 if (newMonths == null) { 1021 if (isLeapYear(year)) { 1022 newMonths = DEFAULT_LEAP_MONTH_LENGTHS; 1023 } else { 1024 newMonths = DEFAULT_MONTH_LENGTHS; 1025 } 1026 } 1027 return newMonths; 1028 } 1029 1030 /** 1031 * Returns day-of-year. 1032 * 1033 * @param cycleNumber a cycle number 1034 * @param dayOfCycle day of cycle 1035 * @param yearInCycle year in cycle 1036 * @return day-of-year 1037 */ getDayOfYear(int cycleNumber, int dayOfCycle, int yearInCycle)1038 private static int getDayOfYear(int cycleNumber, int dayOfCycle, int yearInCycle) { 1039 Integer[] cycles = getAdjustedCycle(cycleNumber); 1040 1041 if (dayOfCycle > 0) { 1042 return dayOfCycle - cycles[yearInCycle].intValue(); 1043 } else { 1044 return cycles[yearInCycle].intValue() + dayOfCycle; 1045 } 1046 } 1047 1048 /** 1049 * Returns month-of-year. 0-based. 1050 * 1051 * @param dayOfYear day-of-year 1052 * @param year a year 1053 * @return month-of-year 1054 */ getMonthOfYear(int dayOfYear, int year)1055 private static int getMonthOfYear(int dayOfYear, int year) { 1056 1057 Integer[] newMonths = getAdjustedMonthDays(year); 1058 1059 if (dayOfYear >= 0) { 1060 for (int i = 0; i < newMonths.length; i++) { 1061 if (dayOfYear < newMonths[i].intValue()) { 1062 return i - 1; 1063 } 1064 } 1065 return 11; 1066 } else { 1067 dayOfYear = (isLeapYear(year) ? (dayOfYear + 355) 1068 : (dayOfYear + 354)); 1069 for (int i = 0; i < newMonths.length; i++) { 1070 if (dayOfYear < newMonths[i].intValue()) { 1071 return i - 1; 1072 } 1073 } 1074 return 11; 1075 } 1076 } 1077 1078 /** 1079 * Returns day-of-month. 1080 * 1081 * @param dayOfYear day of year 1082 * @param month month 1083 * @param year year 1084 * @return day-of-month 1085 */ getDayOfMonth(int dayOfYear, int month, int year)1086 private static int getDayOfMonth(int dayOfYear, int month, int year) { 1087 1088 Integer[] newMonths = getAdjustedMonthDays(year); 1089 1090 if (dayOfYear >= 0) { 1091 if (month > 0) { 1092 return dayOfYear - newMonths[month].intValue(); 1093 } else { 1094 return dayOfYear; 1095 } 1096 } else { 1097 dayOfYear = (isLeapYear(year) ? (dayOfYear + 355) 1098 : (dayOfYear + 354)); 1099 if (month > 0) { 1100 return dayOfYear - newMonths[month].intValue(); 1101 } else { 1102 return dayOfYear; 1103 } 1104 } 1105 } 1106 1107 /** 1108 * Determines if the given year is a leap year. 1109 * 1110 * @param year year 1111 * @return true if leap year 1112 */ isLeapYear(long year)1113 static boolean isLeapYear(long year) { 1114 return (14 + 11 * (year > 0 ? year : -year)) % 30 < 11; 1115 } 1116 1117 /** 1118 * Returns month days from the beginning of year. 1119 * 1120 * @param month month (0-based) 1121 * @parma year year 1122 * @return month days from the beginning of year 1123 */ getMonthDays(int month, int year)1124 private static int getMonthDays(int month, int year) { 1125 Integer[] newMonths = getAdjustedMonthDays(year); 1126 return newMonths[month].intValue(); 1127 } 1128 1129 /** 1130 * Returns month length. 1131 * 1132 * @param month month (0-based) 1133 * @param year year 1134 * @return month length 1135 */ getMonthLength(int month, int year)1136 static int getMonthLength(int month, int year) { 1137 Integer[] newMonths = getAdjustedMonthLength(year); 1138 return newMonths[month].intValue(); 1139 } 1140 1141 @Override lengthOfMonth()1142 public int lengthOfMonth() { 1143 return getMonthLength(monthOfYear - 1, yearOfEra); 1144 } 1145 1146 /** 1147 * Returns year length. 1148 * 1149 * @param year year 1150 * @return year length 1151 */ getYearLength(int year)1152 static int getYearLength(int year) { 1153 1154 int cycleNumber = (year - 1) / 30; 1155 Integer[] cycleYears; 1156 try { 1157 cycleYears = ADJUSTED_CYCLE_YEARS.get(cycleNumber); 1158 } catch (ArrayIndexOutOfBoundsException e) { 1159 cycleYears = null; 1160 } 1161 if (cycleYears != null) { 1162 int yearInCycle = (year - 1) % 30; 1163 if (yearInCycle == 29) { 1164 return ADJUSTED_CYCLES[cycleNumber + 1].intValue() 1165 - ADJUSTED_CYCLES[cycleNumber].intValue() 1166 - cycleYears[yearInCycle].intValue(); 1167 } 1168 return cycleYears[yearInCycle + 1].intValue() 1169 - cycleYears[yearInCycle].intValue(); 1170 } else { 1171 return isLeapYear(year) ? 355 : 354; 1172 } 1173 } 1174 1175 @Override lengthOfYear()1176 public int lengthOfYear() { 1177 return getYearLength(yearOfEra); // TODO: proleptic year 1178 } 1179 1180 /** 1181 * Returns maximum day-of-month. 1182 * 1183 * @return maximum day-of-month 1184 */ getMaximumDayOfMonth()1185 static int getMaximumDayOfMonth() { 1186 return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH]; 1187 } 1188 1189 /** 1190 * Returns smallest maximum day-of-month. 1191 * 1192 * @return smallest maximum day-of-month 1193 */ getSmallestMaximumDayOfMonth()1194 static int getSmallestMaximumDayOfMonth() { 1195 return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH]; 1196 } 1197 1198 /** 1199 * Returns maximum day-of-year. 1200 * 1201 * @return maximum day-of-year 1202 */ getMaximumDayOfYear()1203 static int getMaximumDayOfYear() { 1204 return ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR]; 1205 } 1206 1207 /** 1208 * Returns smallest maximum day-of-year. 1209 * 1210 * @return smallest maximum day-of-year 1211 */ getSmallestMaximumDayOfYear()1212 static int getSmallestMaximumDayOfYear() { 1213 return ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR]; 1214 } 1215 1216 // ----- Deviation handling -----// 1217 1218 /** 1219 * Adds deviation definition. The year and month sepcifed should be the 1220 * caluculated Hijrah year and month. The month is 0 based. e.g. 8 for 1221 * Ramadan (9th month) Addition of anything minus deviation days is 1222 * calculated negatively in the case the user wants to subtract days from 1223 * the calendar. For example, adding -1 days will subtract one day from the 1224 * current date. Please note that this behavior is different from the 1225 * addDeviaiton method. 1226 * 1227 * @param startYear start year 1228 * @param startMonth start month 1229 * @param endYear end year 1230 * @param endMonth end month 1231 * @param offset offset 1232 */ addDeviationAsHijrah(int startYear, int startMonth, int endYear, int endMonth, int offset)1233 private static void addDeviationAsHijrah(int startYear, 1234 int startMonth, int endYear, int endMonth, int offset) { 1235 1236 if (startYear < 1) { 1237 throw new IllegalArgumentException("startYear < 1"); 1238 } 1239 if (endYear < 1) { 1240 throw new IllegalArgumentException("endYear < 1"); 1241 } 1242 if (startMonth < 0 || startMonth > 11) { 1243 throw new IllegalArgumentException( 1244 "startMonth < 0 || startMonth > 11"); 1245 } 1246 if (endMonth < 0 || endMonth > 11) { 1247 throw new IllegalArgumentException("endMonth < 0 || endMonth > 11"); 1248 } 1249 if (endYear > 9999) { 1250 throw new IllegalArgumentException("endYear > 9999"); 1251 } 1252 if (endYear < startYear) { 1253 throw new IllegalArgumentException("startYear > endYear"); 1254 } 1255 if (endYear == startYear && endMonth < startMonth) { 1256 throw new IllegalArgumentException( 1257 "startYear == endYear && endMonth < startMonth"); 1258 } 1259 1260 // Adjusting start year. 1261 boolean isStartYLeap = isLeapYear(startYear); 1262 1263 // Adjusting the number of month. 1264 Integer[] orgStartMonthNums = ADJUSTED_MONTH_DAYS.get(Integer.valueOf( 1265 startYear)); 1266 if (orgStartMonthNums == null) { 1267 if (isStartYLeap) { 1268 orgStartMonthNums = new Integer[LEAP_NUM_DAYS.length]; 1269 for (int l = 0; l < LEAP_NUM_DAYS.length; l++) { 1270 orgStartMonthNums[l] = Integer.valueOf(LEAP_NUM_DAYS[l]); 1271 } 1272 } else { 1273 orgStartMonthNums = new Integer[NUM_DAYS.length]; 1274 for (int l = 0; l < NUM_DAYS.length; l++) { 1275 orgStartMonthNums[l] = Integer.valueOf(NUM_DAYS[l]); 1276 } 1277 } 1278 } 1279 1280 Integer[] newStartMonthNums = new Integer[orgStartMonthNums.length]; 1281 1282 for (int month = 0; month < 12; month++) { 1283 if (month > startMonth) { 1284 newStartMonthNums[month] = Integer.valueOf(orgStartMonthNums[month] 1285 .intValue() 1286 - offset); 1287 } else { 1288 newStartMonthNums[month] = Integer.valueOf(orgStartMonthNums[month] 1289 .intValue()); 1290 } 1291 } 1292 1293 ADJUSTED_MONTH_DAYS.put(Integer.valueOf(startYear), newStartMonthNums); 1294 1295 // Adjusting the days of month. 1296 1297 Integer[] orgStartMonthLengths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf( 1298 startYear)); 1299 if (orgStartMonthLengths == null) { 1300 if (isStartYLeap) { 1301 orgStartMonthLengths = new Integer[LEAP_MONTH_LENGTH.length]; 1302 for (int l = 0; l < LEAP_MONTH_LENGTH.length; l++) { 1303 orgStartMonthLengths[l] = Integer.valueOf(LEAP_MONTH_LENGTH[l]); 1304 } 1305 } else { 1306 orgStartMonthLengths = new Integer[MONTH_LENGTH.length]; 1307 for (int l = 0; l < MONTH_LENGTH.length; l++) { 1308 orgStartMonthLengths[l] = Integer.valueOf(MONTH_LENGTH[l]); 1309 } 1310 } 1311 } 1312 1313 Integer[] newStartMonthLengths = new Integer[orgStartMonthLengths.length]; 1314 1315 for (int month = 0; month < 12; month++) { 1316 if (month == startMonth) { 1317 newStartMonthLengths[month] = Integer.valueOf( 1318 orgStartMonthLengths[month].intValue() - offset); 1319 } else { 1320 newStartMonthLengths[month] = Integer.valueOf( 1321 orgStartMonthLengths[month].intValue()); 1322 } 1323 } 1324 1325 ADJUSTED_MONTH_LENGTHS.put(Integer.valueOf(startYear), newStartMonthLengths); 1326 1327 if (startYear != endYear) { 1328 // System.out.println("over year"); 1329 // Adjusting starting 30 year cycle. 1330 int sCycleNumber = (startYear - 1) / 30; 1331 int sYearInCycle = (startYear - 1) % 30; // 0-based. 1332 Integer[] startCycles = ADJUSTED_CYCLE_YEARS.get(Integer.valueOf( 1333 sCycleNumber)); 1334 if (startCycles == null) { 1335 startCycles = new Integer[CYCLEYEAR_START_DATE.length]; 1336 for (int j = 0; j < startCycles.length; j++) { 1337 startCycles[j] = Integer.valueOf(CYCLEYEAR_START_DATE[j]); 1338 } 1339 } 1340 1341 for (int j = sYearInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) { 1342 startCycles[j] = Integer.valueOf(startCycles[j].intValue() - offset); 1343 } 1344 1345 // System.out.println(sCycleNumber + ":" + sYearInCycle); 1346 ADJUSTED_CYCLE_YEARS.put(Integer.valueOf(sCycleNumber), startCycles); 1347 1348 int sYearInMaxY = (startYear - 1) / 30; 1349 int sEndInMaxY = (endYear - 1) / 30; 1350 1351 if (sYearInMaxY != sEndInMaxY) { 1352 // System.out.println("over 30"); 1353 // Adjusting starting 30 * MAX_ADJUSTED_CYCLE year cycle. 1354 // System.out.println(sYearInMaxY); 1355 1356 for (int j = sYearInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) { 1357 ADJUSTED_CYCLES[j] = Long.valueOf(ADJUSTED_CYCLES[j].longValue() 1358 - offset); 1359 } 1360 1361 // Adjusting ending 30 * MAX_ADJUSTED_CYCLE year cycles. 1362 for (int j = sEndInMaxY + 1; j < ADJUSTED_CYCLES.length; j++) { 1363 ADJUSTED_CYCLES[j] = Long.valueOf(ADJUSTED_CYCLES[j].longValue() 1364 + offset); 1365 } 1366 } 1367 1368 // Adjusting ending 30 year cycle. 1369 int eCycleNumber = (endYear - 1) / 30; 1370 int sEndInCycle = (endYear - 1) % 30; // 0-based. 1371 Integer[] endCycles = ADJUSTED_CYCLE_YEARS.get(Integer.valueOf( 1372 eCycleNumber)); 1373 if (endCycles == null) { 1374 endCycles = new Integer[CYCLEYEAR_START_DATE.length]; 1375 for (int j = 0; j < endCycles.length; j++) { 1376 endCycles[j] = Integer.valueOf(CYCLEYEAR_START_DATE[j]); 1377 } 1378 } 1379 for (int j = sEndInCycle + 1; j < CYCLEYEAR_START_DATE.length; j++) { 1380 endCycles[j] = Integer.valueOf(endCycles[j].intValue() + offset); 1381 } 1382 ADJUSTED_CYCLE_YEARS.put(Integer.valueOf(eCycleNumber), endCycles); 1383 } 1384 1385 // Adjusting ending year. 1386 boolean isEndYLeap = isLeapYear(endYear); 1387 1388 Integer[] orgEndMonthDays = ADJUSTED_MONTH_DAYS.get(Integer.valueOf(endYear)); 1389 1390 if (orgEndMonthDays == null) { 1391 if (isEndYLeap) { 1392 orgEndMonthDays = new Integer[LEAP_NUM_DAYS.length]; 1393 for (int l = 0; l < LEAP_NUM_DAYS.length; l++) { 1394 orgEndMonthDays[l] = Integer.valueOf(LEAP_NUM_DAYS[l]); 1395 } 1396 } else { 1397 orgEndMonthDays = new Integer[NUM_DAYS.length]; 1398 for (int l = 0; l < NUM_DAYS.length; l++) { 1399 orgEndMonthDays[l] = Integer.valueOf(NUM_DAYS[l]); 1400 } 1401 } 1402 } 1403 1404 Integer[] newEndMonthDays = new Integer[orgEndMonthDays.length]; 1405 1406 for (int month = 0; month < 12; month++) { 1407 if (month > endMonth) { 1408 newEndMonthDays[month] = Integer.valueOf(orgEndMonthDays[month] 1409 .intValue() 1410 + offset); 1411 } else { 1412 newEndMonthDays[month] = Integer.valueOf(orgEndMonthDays[month] 1413 .intValue()); 1414 } 1415 } 1416 1417 ADJUSTED_MONTH_DAYS.put(Integer.valueOf(endYear), newEndMonthDays); 1418 1419 // Adjusting the days of month. 1420 Integer[] orgEndMonthLengths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf( 1421 endYear)); 1422 1423 if (orgEndMonthLengths == null) { 1424 if (isEndYLeap) { 1425 orgEndMonthLengths = new Integer[LEAP_MONTH_LENGTH.length]; 1426 for (int l = 0; l < LEAP_MONTH_LENGTH.length; l++) { 1427 orgEndMonthLengths[l] = Integer.valueOf(LEAP_MONTH_LENGTH[l]); 1428 } 1429 } else { 1430 orgEndMonthLengths = new Integer[MONTH_LENGTH.length]; 1431 for (int l = 0; l < MONTH_LENGTH.length; l++) { 1432 orgEndMonthLengths[l] = Integer.valueOf(MONTH_LENGTH[l]); 1433 } 1434 } 1435 } 1436 1437 Integer[] newEndMonthLengths = new Integer[orgEndMonthLengths.length]; 1438 1439 for (int month = 0; month < 12; month++) { 1440 if (month == endMonth) { 1441 newEndMonthLengths[month] = Integer.valueOf( 1442 orgEndMonthLengths[month].intValue() + offset); 1443 } else { 1444 newEndMonthLengths[month] = Integer.valueOf( 1445 orgEndMonthLengths[month].intValue()); 1446 } 1447 } 1448 1449 ADJUSTED_MONTH_LENGTHS.put(Integer.valueOf(endYear), newEndMonthLengths); 1450 1451 Integer[] startMonthLengths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf( 1452 startYear)); 1453 Integer[] endMonthLengths = ADJUSTED_MONTH_LENGTHS.get(Integer.valueOf( 1454 endYear)); 1455 Integer[] startMonthDays = ADJUSTED_MONTH_DAYS 1456 .get(Integer.valueOf(startYear)); 1457 Integer[] endMonthDays = ADJUSTED_MONTH_DAYS.get(Integer.valueOf(endYear)); 1458 1459 int startMonthLength = startMonthLengths[startMonth].intValue(); 1460 int endMonthLength = endMonthLengths[endMonth].intValue(); 1461 int startMonthDay = startMonthDays[11].intValue() 1462 + startMonthLengths[11].intValue(); 1463 int endMonthDay = endMonthDays[11].intValue() 1464 + endMonthLengths[11].intValue(); 1465 1466 int maxMonthLength = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH] 1467 .intValue(); 1468 int leastMaxMonthLength = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH] 1469 .intValue(); 1470 1471 if (maxMonthLength < startMonthLength) { 1472 maxMonthLength = startMonthLength; 1473 } 1474 if (maxMonthLength < endMonthLength) { 1475 maxMonthLength = endMonthLength; 1476 } 1477 ADJUSTED_MAX_VALUES[POSITION_DAY_OF_MONTH] = Integer.valueOf(maxMonthLength); 1478 1479 if (leastMaxMonthLength > startMonthLength) { 1480 leastMaxMonthLength = startMonthLength; 1481 } 1482 if (leastMaxMonthLength > endMonthLength) { 1483 leastMaxMonthLength = endMonthLength; 1484 } 1485 ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_MONTH] = Integer.valueOf( 1486 leastMaxMonthLength); 1487 1488 int maxMonthDay = ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR].intValue(); 1489 int leastMaxMonthDay = ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR] 1490 .intValue(); 1491 1492 if (maxMonthDay < startMonthDay) { 1493 maxMonthDay = startMonthDay; 1494 } 1495 if (maxMonthDay < endMonthDay) { 1496 maxMonthDay = endMonthDay; 1497 } 1498 1499 ADJUSTED_MAX_VALUES[POSITION_DAY_OF_YEAR] = Integer.valueOf(maxMonthDay); 1500 1501 if (leastMaxMonthDay > startMonthDay) { 1502 leastMaxMonthDay = startMonthDay; 1503 } 1504 if (leastMaxMonthDay > endMonthDay) { 1505 leastMaxMonthDay = endMonthDay; 1506 } 1507 ADJUSTED_LEAST_MAX_VALUES[POSITION_DAY_OF_YEAR] = Integer.valueOf( 1508 leastMaxMonthDay); 1509 } 1510 1511 /** 1512 * Read hijrah_deviation.cfg file. The config file contains the deviation data with 1513 * following format. 1514 * 1515 * StartYear/StartMonth(0-based)-EndYear/EndMonth(0-based):Deviation day (1, 1516 * 2, -1, or -2) 1517 * 1518 * Line separator or ";" is used for the separator of each deviation data. 1519 * 1520 * Here is the example. 1521 * 1522 * 1429/0-1429/1:1 1523 * 1429/2-1429/7:1;1429/6-1429/11:1 1524 * 1429/11-9999/11:1 1525 * 1526 * @throws IOException for zip/jar file handling exception. 1527 * @throws ParseException if the format of the configuration file is wrong. 1528 */ readDeviationConfig()1529 private static void readDeviationConfig() throws IOException, ParseException { 1530 InputStream is = getConfigFileInputStream(); 1531 if (is != null) { 1532 BufferedReader br = null; 1533 try { 1534 br = new BufferedReader(new InputStreamReader(is)); 1535 String line = ""; 1536 int num = 0; 1537 while ((line = br.readLine()) != null) { 1538 num++; 1539 line = line.trim(); 1540 parseLine(line, num); 1541 } 1542 } finally { 1543 if (br != null) { 1544 br.close(); 1545 } 1546 } 1547 } 1548 } 1549 1550 /** 1551 * Parse each deviation element. 1552 * 1553 * @param line a line to parse 1554 * @param num line number 1555 * @throws ParseException if line has incorrect format. 1556 */ parseLine(String line, int num)1557 private static void parseLine(String line, int num) throws ParseException { 1558 StringTokenizer st = new StringTokenizer(line, ";"); 1559 while (st.hasMoreTokens()) { 1560 String deviationElement = st.nextToken(); 1561 int offsetIndex = deviationElement.indexOf(':'); 1562 if (offsetIndex != -1) { 1563 String offsetString = deviationElement.substring( 1564 offsetIndex + 1, deviationElement.length()); 1565 int offset; 1566 try { 1567 offset = Integer.parseInt(offsetString); 1568 } catch (NumberFormatException ex) { 1569 throw new ParseException( 1570 "Offset is not properly set at line " + num + ".", 1571 num); 1572 } 1573 int separatorIndex = deviationElement.indexOf('-'); 1574 if (separatorIndex != -1) { 1575 String startDateStg = deviationElement.substring(0, 1576 separatorIndex); 1577 String endDateStg = deviationElement.substring( 1578 separatorIndex + 1, offsetIndex); 1579 int startDateYearSepIndex = startDateStg.indexOf('/'); 1580 int endDateYearSepIndex = endDateStg.indexOf('/'); 1581 int startYear = -1; 1582 int endYear = -1; 1583 int startMonth = -1; 1584 int endMonth = -1; 1585 if (startDateYearSepIndex != -1) { 1586 String startYearStg = startDateStg.substring(0, 1587 startDateYearSepIndex); 1588 String startMonthStg = startDateStg.substring( 1589 startDateYearSepIndex + 1, startDateStg 1590 .length()); 1591 try { 1592 startYear = Integer.parseInt(startYearStg); 1593 } catch (NumberFormatException ex) { 1594 throw new ParseException( 1595 "Start year is not properly set at line " 1596 + num + ".", num); 1597 } 1598 try { 1599 startMonth = Integer.parseInt(startMonthStg); 1600 } catch (NumberFormatException ex) { 1601 throw new ParseException( 1602 "Start month is not properly set at line " 1603 + num + ".", num); 1604 } 1605 } else { 1606 throw new ParseException( 1607 "Start year/month has incorrect format at line " 1608 + num + ".", num); 1609 } 1610 if (endDateYearSepIndex != -1) { 1611 String endYearStg = endDateStg.substring(0, 1612 endDateYearSepIndex); 1613 String endMonthStg = endDateStg.substring( 1614 endDateYearSepIndex + 1, endDateStg.length()); 1615 try { 1616 endYear = Integer.parseInt(endYearStg); 1617 } catch (NumberFormatException ex) { 1618 throw new ParseException( 1619 "End year is not properly set at line " 1620 + num + ".", num); 1621 } 1622 try { 1623 endMonth = Integer.parseInt(endMonthStg); 1624 } catch (NumberFormatException ex) { 1625 throw new ParseException( 1626 "End month is not properly set at line " 1627 + num + ".", num); 1628 } 1629 } else { 1630 throw new ParseException( 1631 "End year/month has incorrect format at line " 1632 + num + ".", num); 1633 } 1634 if (startYear != -1 && startMonth != -1 && endYear != -1 1635 && endMonth != -1) { 1636 addDeviationAsHijrah(startYear, startMonth, endYear, 1637 endMonth, offset); 1638 } else { 1639 throw new ParseException("Unknown error at line " + num 1640 + ".", num); 1641 } 1642 } else { 1643 throw new ParseException( 1644 "Start and end year/month has incorrect format at line " 1645 + num + ".", num); 1646 } 1647 } else { 1648 throw new ParseException("Offset has incorrect format at line " 1649 + num + ".", num); 1650 } 1651 } 1652 } 1653 1654 /** 1655 * Return InputStream for deviation configuration file. 1656 * The default location of the deviation file is: 1657 * <pre> 1658 * $CLASSPATH/org/threeten/bp/chrono 1659 * </pre> 1660 * And the default file name is: 1661 * <pre> 1662 * hijrah_deviation.cfg 1663 * </pre> 1664 * The default location and file name can be overriden by setting 1665 * following two Java's system property. 1666 * <pre> 1667 * Location: org.threeten.bp.i18n.HijrahDate.deviationConfigDir 1668 * File name: org.threeten.bp.i18n.HijrahDate.deviationConfigFile 1669 * </pre> 1670 * Regarding the file format, see readDeviationConfig() method for details. 1671 * 1672 * @return InputStream for file reading exception. 1673 * @throws IOException for zip/jar file handling exception. 1674 */ getConfigFileInputStream()1675 private static InputStream getConfigFileInputStream() throws IOException { 1676 1677 String fileName = System 1678 .getProperty("org.threeten.bp.i18n.HijrahDate.deviationConfigFile"); 1679 1680 if (fileName == null) { 1681 fileName = DEFAULT_CONFIG_FILENAME; 1682 } 1683 1684 String dir = System 1685 .getProperty("org.threeten.bp.i18n.HijrahDate.deviationConfigDir"); 1686 1687 if (dir != null) { 1688 if (!(dir.length() == 0 && dir.endsWith(System 1689 .getProperty("file.separator")))) { 1690 dir = dir + System.getProperty("file.separator"); 1691 } 1692 File file = new File(dir + FILE_SEP + fileName); 1693 if (file.exists()) { 1694 try { 1695 return new FileInputStream(file); 1696 } catch (IOException ioe) { 1697 throw ioe; 1698 } 1699 } else { 1700 return null; 1701 } 1702 } else { 1703 String classPath = System.getProperty("java.class.path"); 1704 StringTokenizer st = new StringTokenizer(classPath, PATH_SEP); 1705 while (st.hasMoreTokens()) { 1706 String path = st.nextToken(); 1707 File file = new File(path); 1708 if (file.exists()) { 1709 if (file.isDirectory()) { 1710 File f = new File( 1711 path + FILE_SEP + DEFAULT_CONFIG_PATH, fileName); 1712 if (f.exists()) { 1713 try { 1714 return new FileInputStream(path + FILE_SEP 1715 + DEFAULT_CONFIG_PATH + FILE_SEP 1716 + fileName); 1717 } catch (IOException ioe) { 1718 throw ioe; 1719 } 1720 } 1721 } else { 1722 ZipFile zip; 1723 try { 1724 zip = new ZipFile(file); 1725 } catch (IOException ioe) { 1726 zip = null; 1727 } 1728 1729 if (zip != null) { 1730 String targetFile = DEFAULT_CONFIG_PATH + FILE_SEP 1731 + fileName; 1732 ZipEntry entry = zip.getEntry(targetFile); 1733 1734 if (entry == null) { 1735 if (FILE_SEP == '/') { 1736 targetFile = targetFile.replace('/', '\\'); 1737 } else if (FILE_SEP == '\\') { 1738 targetFile = targetFile.replace('\\', '/'); 1739 } 1740 entry = zip.getEntry(targetFile); 1741 } 1742 1743 if (entry != null) { 1744 try { 1745 return zip.getInputStream(entry); 1746 } catch (IOException ioe) { 1747 throw ioe; 1748 } 1749 } 1750 } 1751 } 1752 } 1753 } 1754 return null; 1755 } 1756 } 1757 //----------------------------------------------------------------------- writeReplace()1758 private Object writeReplace() { 1759 return new Ser(Ser.HIJRAH_DATE_TYPE, this); 1760 } 1761 writeExternal(DataOutput out)1762 void writeExternal(DataOutput out) throws IOException { 1763 // HijrahChrono is implicit in the Hijrah_DATE_TYPE 1764 out.writeInt(get(YEAR)); 1765 out.writeByte(get(MONTH_OF_YEAR)); 1766 out.writeByte(get(DAY_OF_MONTH)); 1767 } 1768 readExternal(DataInput in)1769 static ChronoLocalDate readExternal(DataInput in) throws IOException { 1770 int year = in.readInt(); 1771 int month = in.readByte(); 1772 int dayOfMonth = in.readByte(); 1773 return HijrahChronology.INSTANCE.date(year, month, dayOfMonth); 1774 } 1775 1776 } 1777