341 * The resource and calendar type are retrieved from properties |
347 * The resource and calendar type are retrieved from properties |
342 * in the {@code calendars.properties}. |
348 * in the {@code calendars.properties}. |
343 * The property names are {@code "calendar.hijrah." + id} |
349 * The property names are {@code "calendar.hijrah." + id} |
344 * and {@code "calendar.hijrah." + id + ".type"} |
350 * and {@code "calendar.hijrah." + id + ".type"} |
345 * @param id the id of the calendar |
351 * @param id the id of the calendar |
346 * @throws Exception if the resource can not be accessed or |
352 * @throws DateTimeException if the calendar type is missing from the properties file. |
347 * the format is invalid |
353 * @throws IllegalArgumentException if the id is empty |
348 */ |
354 */ |
349 private HijrahChronology(String id) throws Exception { |
355 private HijrahChronology(String id) throws DateTimeException { |
350 if (id.isEmpty()) { |
356 if (id.isEmpty()) { |
351 throw new IllegalArgumentException("calendar id is empty"); |
357 throw new IllegalArgumentException("calendar id is empty"); |
352 } |
358 } |
|
359 String propName = PROP_PREFIX + id + PROP_TYPE_SUFFIX; |
|
360 String calType = calendarProperties.getProperty(propName); |
|
361 if (calType == null || calType.isEmpty()) { |
|
362 throw new DateTimeException("calendarType is missing or empty for: " + propName); |
|
363 } |
353 this.typeId = id; |
364 this.typeId = id; |
354 this.calendarType = calendarProperties.getProperty(PROP_PREFIX + id + PROP_TYPE_SUFFIX); |
365 this.calendarType = calType; |
355 |
366 } |
356 try { |
367 |
357 String resource = calendarProperties.getProperty(PROP_PREFIX + id); |
368 /** |
358 Objects.requireNonNull(resource, "Resource missing for calendar"); |
369 * Check and ensure that the calendar data has been initialized. |
359 loadCalendarData(resource); |
370 * The initialization check is performed at the boundary between |
360 } catch (Exception ex) { |
371 * public and package methods. If a public calls another public method |
361 throw new Exception("Unable to initialize HijrahCalendar: " + id, ex); |
372 * a check is not necessary in the caller. |
|
373 * The constructors of HijrahDate call {@link #getEpochDay} or |
|
374 * {@link #getHijrahDateInfo} so every call from HijrahDate to a |
|
375 * HijrahChronology via package private methods has been checked. |
|
376 * |
|
377 * @throws DateTimeException if the calendar data configuration is |
|
378 * malformed or IOExceptions occur loading the data |
|
379 */ |
|
380 private void checkCalendarInit() { |
|
381 // Keep this short so it can be inlined for performance |
|
382 if (initComplete == false) { |
|
383 loadCalendarData(); |
|
384 initComplete = true; |
362 } |
385 } |
363 } |
386 } |
364 |
387 |
365 //----------------------------------------------------------------------- |
388 //----------------------------------------------------------------------- |
366 /** |
389 /** |
619 * @param monthOfYear the month-of-year to represent, 1-origin |
645 * @param monthOfYear the month-of-year to represent, 1-origin |
620 * @param dayOfMonth the day-of-month to represent, 1-origin |
646 * @param dayOfMonth the day-of-month to represent, 1-origin |
621 * @return the epoch day |
647 * @return the epoch day |
622 */ |
648 */ |
623 long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) { |
649 long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) { |
|
650 checkCalendarInit(); // ensure that the chronology is initialized |
624 checkValidMonth(monthOfYear); |
651 checkValidMonth(monthOfYear); |
625 int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1); |
652 int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1); |
626 if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) { |
653 if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) { |
627 throw new DateTimeException("Invalid Hijrah date, year: " + |
654 throw new DateTimeException("Invalid Hijrah date, year: " + |
628 prolepticYear + ", month: " + monthOfYear); |
655 prolepticYear + ", month: " + monthOfYear); |
844 throw pax.getException(); |
871 throw pax.getException(); |
845 } |
872 } |
846 } |
873 } |
847 |
874 |
848 /** |
875 /** |
849 * Loads and processes the Hijrah calendar properties file. |
876 * Loads and processes the Hijrah calendar properties file for this calendarType. |
850 * The starting Hijrah date and the corresponding ISO date are |
877 * The starting Hijrah date and the corresponding ISO date are |
851 * extracted and used to calculate the epochDate offset. |
878 * extracted and used to calculate the epochDate offset. |
852 * The version number is identified and ignored. |
879 * The version number is identified and ignored. |
853 * Everything else is the data for a year with containing the length of each |
880 * Everything else is the data for a year with containing the length of each |
854 * of 12 months. |
881 * of 12 months. |
855 * |
882 * |
856 * @param resourceName containing the properties defining the calendar, not null |
883 * @throws DateTimeException if initialization of the calendar data from the |
857 * @throws IllegalArgumentException if any of the values are malformed |
884 * resource fails |
858 * @throws NumberFormatException if numbers, including properties that should |
885 */ |
859 * be years are invalid |
886 private void loadCalendarData() { |
860 * @throws IOException if access to the property resource fails. |
887 try { |
861 */ |
888 String resourceName = calendarProperties.getProperty(PROP_PREFIX + typeId); |
862 private void loadCalendarData(String resourceName) throws Exception { |
889 Objects.requireNonNull(resourceName, "Resource missing for calendar: " + PROP_PREFIX + typeId); |
863 Properties props = readConfigProperties(resourceName); |
890 Properties props = readConfigProperties(resourceName); |
864 |
891 |
865 Map<Integer, int[]> years = new HashMap<>(); |
892 Map<Integer, int[]> years = new HashMap<>(); |
866 int minYear = Integer.MAX_VALUE; |
893 int minYear = Integer.MAX_VALUE; |
867 int maxYear = Integer.MIN_VALUE; |
894 int maxYear = Integer.MIN_VALUE; |
868 String id = null; |
895 String id = null; |
869 String type = null; |
896 String type = null; |
870 String version = null; |
897 String version = null; |
871 int isoStart = 0; |
898 int isoStart = 0; |
872 for (Map.Entry<Object, Object> entry : props.entrySet()) { |
899 for (Map.Entry<Object, Object> entry : props.entrySet()) { |
873 String key = (String) entry.getKey(); |
900 String key = (String) entry.getKey(); |
874 switch (key) { |
901 switch (key) { |
875 case KEY_ID: |
902 case KEY_ID: |
876 id = (String)entry.getValue(); |
903 id = (String)entry.getValue(); |
877 break; |
904 break; |
878 case KEY_TYPE: |
905 case KEY_TYPE: |
879 type = (String)entry.getValue(); |
906 type = (String)entry.getValue(); |
880 break; |
907 break; |
881 case KEY_VERSION: |
908 case KEY_VERSION: |
882 version = (String)entry.getValue(); |
909 version = (String)entry.getValue(); |
883 break; |
910 break; |
884 case KEY_ISO_START: { |
911 case KEY_ISO_START: { |
885 int[] ymd = parseYMD((String) entry.getValue()); |
912 int[] ymd = parseYMD((String) entry.getValue()); |
886 isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay(); |
913 isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay(); |
887 break; |
914 break; |
|
915 } |
|
916 default: |
|
917 try { |
|
918 // Everything else is either a year or invalid |
|
919 int year = Integer.valueOf(key); |
|
920 int[] months = parseMonths((String) entry.getValue()); |
|
921 years.put(year, months); |
|
922 maxYear = Math.max(maxYear, year); |
|
923 minYear = Math.min(minYear, year); |
|
924 } catch (NumberFormatException nfe) { |
|
925 throw new IllegalArgumentException("bad key: " + key); |
|
926 } |
888 } |
927 } |
889 default: |
|
890 try { |
|
891 // Everything else is either a year or invalid |
|
892 int year = Integer.valueOf(key); |
|
893 int[] months = parseMonths((String) entry.getValue()); |
|
894 years.put(year, months); |
|
895 maxYear = Math.max(maxYear, year); |
|
896 minYear = Math.min(minYear, year); |
|
897 } catch (NumberFormatException nfe) { |
|
898 throw new IllegalArgumentException("bad key: " + key); |
|
899 } |
|
900 } |
928 } |
901 } |
929 |
902 |
930 if (!getId().equals(id)) { |
903 if (!getId().equals(id)) { |
931 throw new IllegalArgumentException("Configuration is for a different calendar: " + id); |
904 throw new IllegalArgumentException("Configuration is for a different calendar: " + id); |
932 } |
905 } |
933 if (!getCalendarType().equals(type)) { |
906 if (!getCalendarType().equals(type)) { |
934 throw new IllegalArgumentException("Configuration is for a different calendar type: " + type); |
907 throw new IllegalArgumentException("Configuration is for a different calendar type: " + type); |
935 } |
908 } |
936 if (version == null || version.isEmpty()) { |
909 if (version == null || version.isEmpty()) { |
937 throw new IllegalArgumentException("Configuration does not contain a version"); |
910 throw new IllegalArgumentException("Configuration does not contain a version"); |
938 } |
911 } |
939 if (isoStart == 0) { |
912 if (isoStart == 0) { |
940 throw new IllegalArgumentException("Configuration does not contain a ISO start date"); |
913 throw new IllegalArgumentException("Configuration does not contain a ISO start date"); |
941 } |
914 } |
942 |
915 |
943 // Now create and validate the array of epochDays indexed by epochMonth |
916 // Now create and validate the array of epochDays indexed by epochMonth |
944 hijrahStartEpochMonth = minYear * 12; |
917 hijrahStartEpochMonth = minYear * 12; |
945 minEpochDay = isoStart; |
918 minEpochDay = isoStart; |
946 hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years); |
919 hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years); |
947 maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1]; |
920 maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1]; |
948 |
921 |
949 // Compute the min and max year length in days. |
922 // Compute the min and max year length in days. |
950 for (int year = minYear; year < maxYear; year++) { |
923 for (int year = minYear; year < maxYear; year++) { |
951 int length = getYearLength(year); |
924 int length = getYearLength(year); |
952 minYearLength = Math.min(minYearLength, length); |
925 minYearLength = Math.min(minYearLength, length); |
953 maxYearLength = Math.max(maxYearLength, length); |
926 maxYearLength = Math.max(maxYearLength, length); |
954 } |
|
955 } catch (Exception ex) { |
|
956 // Log error and throw a DateTimeException |
|
957 PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono"); |
|
958 logger.severe("Unable to initialize Hijrah calendar proxy: " + typeId, ex); |
|
959 throw new DateTimeException("Unable to initialize HijrahCalendar: " + typeId, ex); |
927 } |
960 } |
928 } |
961 } |
929 |
962 |
930 /** |
963 /** |
931 * Converts the map of year to month lengths ranging from minYear to maxYear |
964 * Converts the map of year to month lengths ranging from minYear to maxYear |