jdk/src/share/classes/java/time/chrono/HijrahChronology.java
changeset 17474 8c100beabcc0
parent 16852 60207b2b4b42
child 19030 32f129cb6351
equal deleted inserted replaced
17473:35cd9b3a98ff 17474:8c100beabcc0
   131  * <pre>
   131  * <pre>
   132  *      Locale locale = Locale.forLanguageTag("en-US-u-ca-islamic-cv-umalqura");
   132  *      Locale locale = Locale.forLanguageTag("en-US-u-ca-islamic-cv-umalqura");
   133  *      Chronology chrono = Chronology.ofLocale(locale);
   133  *      Chronology chrono = Chronology.ofLocale(locale);
   134  * </pre>
   134  * </pre>
   135  *
   135  *
   136  * <h3>Specification for implementors</h3>
   136  * @implSpec
   137  * This class is immutable and thread-safe.
   137  * This class is immutable and thread-safe.
   138  * <h3>Implementation Note for Hijrah Calendar Variant Configuration</h3>
   138  *
       
   139  * @implNote
   139  * Each Hijrah variant is configured individually. Each variant is defined by a
   140  * Each Hijrah variant is configured individually. Each variant is defined by a
   140  * property resource that defines the {@code ID}, the {@code calendar type},
   141  * property resource that defines the {@code ID}, the {@code calendar type},
   141  * the start of the calendar, the alignment with the
   142  * the start of the calendar, the alignment with the
   142  * ISO calendar, and the length of each month for a range of years.
   143  * ISO calendar, and the length of each month for a range of years.
   143  * The variants are identified in the {@code calendars.properties} file.
   144  * The variants are identified in the {@code calendars.properties} file.
   228      * Other Hijrah chronology variants may be available from
   229      * Other Hijrah chronology variants may be available from
   229      * {@link Chronology#getAvailableChronologies}.
   230      * {@link Chronology#getAvailableChronologies}.
   230      */
   231      */
   231     public static final HijrahChronology INSTANCE;
   232     public static final HijrahChronology INSTANCE;
   232     /**
   233     /**
       
   234      * Flag to indicate the initialization of configuration data is complete.
       
   235      * @see #checkCalendarInit()
       
   236      */
       
   237     private volatile boolean initComplete;
       
   238     /**
   233      * Array of epoch days indexed by Hijrah Epoch month.
   239      * Array of epoch days indexed by Hijrah Epoch month.
   234      * Computed by {@link #loadCalendarData}.
   240      * Computed by {@link #loadCalendarData}.
   235      */
   241      */
   236     private transient int[] hijrahEpochMonthStartDays;
   242     private transient int[] hijrahEpochMonthStartDays;
   237     /**
   243     /**
   283      * Suffix of property names containing the calendar type of a variant.
   289      * Suffix of property names containing the calendar type of a variant.
   284      */
   290      */
   285     private static final String PROP_TYPE_SUFFIX = ".type";
   291     private static final String PROP_TYPE_SUFFIX = ".type";
   286 
   292 
   287     /**
   293     /**
   288      * Name data.
   294      * Static initialization of the predefined calendars found in the
       
   295      * lib/calendars.properties file.
   289      */
   296      */
   290     static {
   297     static {
   291         try {
   298         try {
   292             calendarProperties = sun.util.calendar.BaseCalendar.getCalendarProperties();
   299             calendarProperties = sun.util.calendar.BaseCalendar.getCalendarProperties();
   293         } catch (IOException ioe) {
   300         } catch (IOException ioe) {
   297         try {
   304         try {
   298             INSTANCE = new HijrahChronology("Hijrah-umalqura");
   305             INSTANCE = new HijrahChronology("Hijrah-umalqura");
   299             // Register it by its aliases
   306             // Register it by its aliases
   300             Chronology.registerChrono(INSTANCE, "Hijrah");
   307             Chronology.registerChrono(INSTANCE, "Hijrah");
   301             Chronology.registerChrono(INSTANCE, "islamic");
   308             Chronology.registerChrono(INSTANCE, "islamic");
   302 
   309         } catch (DateTimeException ex) {
   303         } catch (Exception ex) {
       
   304             // Absence of Hijrah calendar is fatal to initializing this class.
   310             // Absence of Hijrah calendar is fatal to initializing this class.
   305             PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
   311             PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
   306             logger.severe("Unable to initialize Hijrah calendar: Hijrah-umalqura", ex);
   312             logger.severe("Unable to initialize Hijrah calendar: Hijrah-umalqura", ex);
   307             throw new RuntimeException("Unable to initialize Hijrah-umalqura calendar", ex.getCause());
   313             throw new RuntimeException("Unable to initialize Hijrah-umalqura calendar", ex.getCause());
   308         }
   314         }
   325                 }
   331                 }
   326                 try {
   332                 try {
   327                     // Create and register the variant
   333                     // Create and register the variant
   328                     HijrahChronology chrono = new HijrahChronology(id);
   334                     HijrahChronology chrono = new HijrahChronology(id);
   329                     Chronology.registerChrono(chrono);
   335                     Chronology.registerChrono(chrono);
   330                 } catch (Exception ex) {
   336                 } catch (DateTimeException ex) {
   331                     // Log error and continue
   337                     // Log error and continue
   332                     PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
   338                     PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
   333                     logger.severe("Unable to initialize Hijrah calendar: " + id, ex);
   339                     logger.severe("Unable to initialize Hijrah calendar: " + id, ex);
   334                 }
   340                 }
   335             }
   341             }
   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     /**
   507     }
   530     }
   508 
   531 
   509     //-----------------------------------------------------------------------
   532     //-----------------------------------------------------------------------
   510     @Override
   533     @Override
   511     public boolean isLeapYear(long prolepticYear) {
   534     public boolean isLeapYear(long prolepticYear) {
       
   535         checkCalendarInit();
   512         int epochMonth = yearToEpochMonth((int) prolepticYear);
   536         int epochMonth = yearToEpochMonth((int) prolepticYear);
   513         if (epochMonth < 0 || epochMonth > maxEpochDay) {
   537         if (epochMonth < 0 || epochMonth > maxEpochDay) {
   514             throw new DateTimeException("Hijrah date out of range");
   538             throw new DateTimeException("Hijrah date out of range");
   515         }
   539         }
   516         int len = getYearLength((int) prolepticYear);
   540         int len = getYearLength((int) prolepticYear);
   541     }
   565     }
   542 
   566 
   543     //-----------------------------------------------------------------------
   567     //-----------------------------------------------------------------------
   544     @Override
   568     @Override
   545     public ValueRange range(ChronoField field) {
   569     public ValueRange range(ChronoField field) {
       
   570         checkCalendarInit();
   546         if (field instanceof ChronoField) {
   571         if (field instanceof ChronoField) {
   547             ChronoField f = field;
   572             ChronoField f = field;
   548             switch (f) {
   573             switch (f) {
   549                 case DAY_OF_MONTH:
   574                 case DAY_OF_MONTH:
   550                     return ValueRange.of(1, 1, getMinimumMonthLength(), getMaximumMonthLength());
   575                     return ValueRange.of(1, 1, getMinimumMonthLength(), getMaximumMonthLength());
   593      *
   618      *
   594      * @param epochDay  the EpochDay
   619      * @param epochDay  the EpochDay
   595      * @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE
   620      * @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE
   596      */
   621      */
   597     int[] getHijrahDateInfo(int epochDay) {
   622     int[] getHijrahDateInfo(int epochDay) {
       
   623         checkCalendarInit();    // ensure that the chronology is initialized
   598         if (epochDay < minEpochDay || epochDay >= maxEpochDay) {
   624         if (epochDay < minEpochDay || epochDay >= maxEpochDay) {
   599             throw new DateTimeException("Hijrah date out of range");
   625             throw new DateTimeException("Hijrah date out of range");
   600         }
   626         }
   601 
   627 
   602         int epochMonth = epochDayToEpochMonth(epochDay);
   628         int epochMonth = epochDayToEpochMonth(epochDay);
   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