Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/time/chrono/HijrahChronology.java
38918 views
1
/*
2
* Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
/*
27
* Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos
28
*
29
* All rights reserved.
30
*
31
* Redistribution and use in source and binary forms, with or without
32
* modification, are permitted provided that the following conditions are met:
33
*
34
* * Redistributions of source code must retain the above copyright notice,
35
* this list of conditions and the following disclaimer.
36
*
37
* * Redistributions in binary form must reproduce the above copyright notice,
38
* this list of conditions and the following disclaimer in the documentation
39
* and/or other materials provided with the distribution.
40
*
41
* * Neither the name of JSR-310 nor the names of its contributors
42
* may be used to endorse or promote products derived from this software
43
* without specific prior written permission.
44
*
45
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
46
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
47
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
48
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
49
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
50
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
51
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
52
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
53
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
54
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
55
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
56
*/
57
58
package java.time.chrono;
59
60
import static java.time.temporal.ChronoField.EPOCH_DAY;
61
62
import java.io.File;
63
import java.io.FileInputStream;
64
import java.io.IOException;
65
import java.io.InputStream;
66
import java.io.InvalidObjectException;
67
import java.io.ObjectInputStream;
68
import java.io.Serializable;
69
import java.security.AccessController;
70
import java.security.PrivilegedActionException;
71
import java.time.Clock;
72
import java.time.DateTimeException;
73
import java.time.Instant;
74
import java.time.LocalDate;
75
import java.time.ZoneId;
76
import java.time.format.ResolverStyle;
77
import java.time.temporal.ChronoField;
78
import java.time.temporal.TemporalAccessor;
79
import java.time.temporal.TemporalField;
80
import java.time.temporal.ValueRange;
81
import java.util.Arrays;
82
import java.util.HashMap;
83
import java.util.List;
84
import java.util.Map;
85
import java.util.Objects;
86
import java.util.Properties;
87
88
import sun.util.logging.PlatformLogger;
89
90
/**
91
* The Hijrah calendar is a lunar calendar supporting Islamic calendars.
92
* <p>
93
* The HijrahChronology follows the rules of the Hijrah calendar system. The Hijrah
94
* calendar has several variants based on differences in when the new moon is
95
* determined to have occurred and where the observation is made.
96
* In some variants the length of each month is
97
* computed algorithmically from the astronomical data for the moon and earth and
98
* in others the length of the month is determined by an authorized sighting
99
* of the new moon. For the algorithmically based calendars the calendar
100
* can project into the future.
101
* For sighting based calendars only historical data from past
102
* sightings is available.
103
* <p>
104
* The length of each month is 29 or 30 days.
105
* Ordinary years have 354 days; leap years have 355 days.
106
*
107
* <p>
108
* CLDR and LDML identify variants:
109
* <table cellpadding="2" summary="Variants of Hijrah Calendars">
110
* <thead>
111
* <tr class="tableSubHeadingColor">
112
* <th class="colFirst" align="left" >Chronology ID</th>
113
* <th class="colFirst" align="left" >Calendar Type</th>
114
* <th class="colFirst" align="left" >Locale extension, see {@link java.util.Locale}</th>
115
* <th class="colLast" align="left" >Description</th>
116
* </tr>
117
* </thead>
118
* <tbody>
119
* <tr class="altColor">
120
* <td>Hijrah-umalqura</td>
121
* <td>islamic-umalqura</td>
122
* <td>ca-islamic-umalqura</td>
123
* <td>Islamic - Umm Al-Qura calendar of Saudi Arabia</td>
124
* </tr>
125
* </tbody>
126
* </table>
127
* <p>Additional variants may be available through {@link Chronology#getAvailableChronologies()}.
128
*
129
* <p>Example</p>
130
* <p>
131
* Selecting the chronology from the locale uses {@link Chronology#ofLocale}
132
* to find the Chronology based on Locale supported BCP 47 extension mechanism
133
* to request a specific calendar ("ca"). For example,
134
* </p>
135
* <pre>
136
* Locale locale = Locale.forLanguageTag("en-US-u-ca-islamic-umalqura");
137
* Chronology chrono = Chronology.ofLocale(locale);
138
* </pre>
139
*
140
* @implSpec
141
* This class is immutable and thread-safe.
142
*
143
* @implNote
144
* Each Hijrah variant is configured individually. Each variant is defined by a
145
* property resource that defines the {@code ID}, the {@code calendar type},
146
* the start of the calendar, the alignment with the
147
* ISO calendar, and the length of each month for a range of years.
148
* The variants are identified in the {@code calendars.properties} file.
149
* The new properties are prefixed with {@code "calendars.hijrah."}:
150
* <table cellpadding="2" border="0" summary="Configuration of Hijrah Calendar Variants">
151
* <thead>
152
* <tr class="tableSubHeadingColor">
153
* <th class="colFirst" align="left">Property Name</th>
154
* <th class="colFirst" align="left">Property value</th>
155
* <th class="colLast" align="left">Description </th>
156
* </tr>
157
* </thead>
158
* <tbody>
159
* <tr class="altColor">
160
* <td>calendars.hijrah.{ID}</td>
161
* <td>The property resource defining the {@code {ID}} variant</td>
162
* <td>The property resource is located with the {@code calendars.properties} file</td>
163
* </tr>
164
* <tr class="rowColor">
165
* <td>calendars.hijrah.{ID}.type</td>
166
* <td>The calendar type</td>
167
* <td>LDML defines the calendar type names</td>
168
* </tr>
169
* </tbody>
170
* </table>
171
* <p>
172
* The Hijrah property resource is a set of properties that describe the calendar.
173
* The syntax is defined by {@code java.util.Properties#load(Reader)}.
174
* <table cellpadding="2" summary="Configuration of Hijrah Calendar">
175
* <thead>
176
* <tr class="tableSubHeadingColor">
177
* <th class="colFirst" align="left" > Property Name</th>
178
* <th class="colFirst" align="left" > Property value</th>
179
* <th class="colLast" align="left" > Description </th>
180
* </tr>
181
* </thead>
182
* <tbody>
183
* <tr class="altColor">
184
* <td>id</td>
185
* <td>Chronology Id, for example, "Hijrah-umalqura"</td>
186
* <td>The Id of the calendar in common usage</td>
187
* </tr>
188
* <tr class="rowColor">
189
* <td>type</td>
190
* <td>Calendar type, for example, "islamic-umalqura"</td>
191
* <td>LDML defines the calendar types</td>
192
* </tr>
193
* <tr class="altColor">
194
* <td>version</td>
195
* <td>Version, for example: "1.8.0_1"</td>
196
* <td>The version of the Hijrah variant data</td>
197
* </tr>
198
* <tr class="rowColor">
199
* <td>iso-start</td>
200
* <td>ISO start date, formatted as {@code yyyy-MM-dd}, for example: "1900-04-30"</td>
201
* <td>The ISO date of the first day of the minimum Hijrah year.</td>
202
* </tr>
203
* <tr class="altColor">
204
* <td>yyyy - a numeric 4 digit year, for example "1434"</td>
205
* <td>The value is a sequence of 12 month lengths,
206
* for example: "29 30 29 30 29 30 30 30 29 30 29 29"</td>
207
* <td>The lengths of the 12 months of the year separated by whitespace.
208
* A numeric year property must be present for every year without any gaps.
209
* The month lengths must be between 29-32 inclusive.
210
* </td>
211
* </tr>
212
* </tbody>
213
* </table>
214
*
215
* @since 1.8
216
*/
217
public final class HijrahChronology extends AbstractChronology implements Serializable {
218
219
/**
220
* The Hijrah Calendar id.
221
*/
222
private final transient String typeId;
223
/**
224
* The Hijrah calendarType.
225
*/
226
private final transient String calendarType;
227
/**
228
* Serialization version.
229
*/
230
private static final long serialVersionUID = 3127340209035924785L;
231
/**
232
* Singleton instance of the Islamic Umm Al-Qura calendar of Saudi Arabia.
233
* Other Hijrah chronology variants may be available from
234
* {@link Chronology#getAvailableChronologies}.
235
*/
236
public static final HijrahChronology INSTANCE;
237
/**
238
* Flag to indicate the initialization of configuration data is complete.
239
* @see #checkCalendarInit()
240
*/
241
private transient volatile boolean initComplete;
242
/**
243
* Array of epoch days indexed by Hijrah Epoch month.
244
* Computed by {@link #loadCalendarData}.
245
*/
246
private transient int[] hijrahEpochMonthStartDays;
247
/**
248
* The minimum epoch day of this Hijrah calendar.
249
* Computed by {@link #loadCalendarData}.
250
*/
251
private transient int minEpochDay;
252
/**
253
* The maximum epoch day for which calendar data is available.
254
* Computed by {@link #loadCalendarData}.
255
*/
256
private transient int maxEpochDay;
257
/**
258
* The minimum epoch month.
259
* Computed by {@link #loadCalendarData}.
260
*/
261
private transient int hijrahStartEpochMonth;
262
/**
263
* The minimum length of a month.
264
* Computed by {@link #createEpochMonths}.
265
*/
266
private transient int minMonthLength;
267
/**
268
* The maximum length of a month.
269
* Computed by {@link #createEpochMonths}.
270
*/
271
private transient int maxMonthLength;
272
/**
273
* The minimum length of a year in days.
274
* Computed by {@link #createEpochMonths}.
275
*/
276
private transient int minYearLength;
277
/**
278
* The maximum length of a year in days.
279
* Computed by {@link #createEpochMonths}.
280
*/
281
private transient int maxYearLength;
282
/**
283
* A reference to the properties stored in
284
* ${java.home}/lib/calendars.properties
285
*/
286
private final transient static Properties calendarProperties;
287
288
/**
289
* Prefix of property names for Hijrah calendar variants.
290
*/
291
private static final String PROP_PREFIX = "calendar.hijrah.";
292
/**
293
* Suffix of property names containing the calendar type of a variant.
294
*/
295
private static final String PROP_TYPE_SUFFIX = ".type";
296
297
/**
298
* Static initialization of the predefined calendars found in the
299
* lib/calendars.properties file.
300
*/
301
static {
302
try {
303
calendarProperties = sun.util.calendar.BaseCalendar.getCalendarProperties();
304
} catch (IOException ioe) {
305
throw new InternalError("Can't initialize lib/calendars.properties", ioe);
306
}
307
308
try {
309
INSTANCE = new HijrahChronology("Hijrah-umalqura");
310
// Register it by its aliases
311
AbstractChronology.registerChrono(INSTANCE, "Hijrah");
312
AbstractChronology.registerChrono(INSTANCE, "islamic");
313
} catch (DateTimeException ex) {
314
// Absence of Hijrah calendar is fatal to initializing this class.
315
PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
316
logger.severe("Unable to initialize Hijrah calendar: Hijrah-umalqura", ex);
317
throw new RuntimeException("Unable to initialize Hijrah-umalqura calendar", ex.getCause());
318
}
319
registerVariants();
320
}
321
322
/**
323
* For each Hijrah variant listed, create the HijrahChronology and register it.
324
* Exceptions during initialization are logged but otherwise ignored.
325
*/
326
private static void registerVariants() {
327
for (String name : calendarProperties.stringPropertyNames()) {
328
if (name.startsWith(PROP_PREFIX)) {
329
String id = name.substring(PROP_PREFIX.length());
330
if (id.indexOf('.') >= 0) {
331
continue; // no name or not a simple name of a calendar
332
}
333
if (id.equals(INSTANCE.getId())) {
334
continue; // do not duplicate the default
335
}
336
try {
337
// Create and register the variant
338
HijrahChronology chrono = new HijrahChronology(id);
339
AbstractChronology.registerChrono(chrono);
340
} catch (DateTimeException ex) {
341
// Log error and continue
342
PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
343
logger.severe("Unable to initialize Hijrah calendar: " + id, ex);
344
}
345
}
346
}
347
}
348
349
/**
350
* Create a HijrahChronology for the named variant.
351
* The resource and calendar type are retrieved from properties
352
* in the {@code calendars.properties}.
353
* The property names are {@code "calendar.hijrah." + id}
354
* and {@code "calendar.hijrah." + id + ".type"}
355
* @param id the id of the calendar
356
* @throws DateTimeException if the calendar type is missing from the properties file.
357
* @throws IllegalArgumentException if the id is empty
358
*/
359
private HijrahChronology(String id) throws DateTimeException {
360
if (id.isEmpty()) {
361
throw new IllegalArgumentException("calendar id is empty");
362
}
363
String propName = PROP_PREFIX + id + PROP_TYPE_SUFFIX;
364
String calType = calendarProperties.getProperty(propName);
365
if (calType == null || calType.isEmpty()) {
366
throw new DateTimeException("calendarType is missing or empty for: " + propName);
367
}
368
this.typeId = id;
369
this.calendarType = calType;
370
}
371
372
/**
373
* Check and ensure that the calendar data has been initialized.
374
* The initialization check is performed at the boundary between
375
* public and package methods. If a public calls another public method
376
* a check is not necessary in the caller.
377
* The constructors of HijrahDate call {@link #getEpochDay} or
378
* {@link #getHijrahDateInfo} so every call from HijrahDate to a
379
* HijrahChronology via package private methods has been checked.
380
*
381
* @throws DateTimeException if the calendar data configuration is
382
* malformed or IOExceptions occur loading the data
383
*/
384
private void checkCalendarInit() {
385
// Keep this short so it can be inlined for performance
386
if (initComplete == false) {
387
loadCalendarData();
388
initComplete = true;
389
}
390
}
391
392
//-----------------------------------------------------------------------
393
/**
394
* Gets the ID of the chronology.
395
* <p>
396
* The ID uniquely identifies the {@code Chronology}. It can be used to
397
* lookup the {@code Chronology} using {@link Chronology#of(String)}.
398
*
399
* @return the chronology ID, non-null
400
* @see #getCalendarType()
401
*/
402
@Override
403
public String getId() {
404
return typeId;
405
}
406
407
/**
408
* Gets the calendar type of the Islamic calendar.
409
* <p>
410
* The calendar type is an identifier defined by the
411
* <em>Unicode Locale Data Markup Language (LDML)</em> specification.
412
* It can be used to lookup the {@code Chronology} using {@link Chronology#of(String)}.
413
*
414
* @return the calendar system type; non-null if the calendar has
415
* a standard type, otherwise null
416
* @see #getId()
417
*/
418
@Override
419
public String getCalendarType() {
420
return calendarType;
421
}
422
423
//-----------------------------------------------------------------------
424
/**
425
* Obtains a local date in Hijrah calendar system from the
426
* era, year-of-era, month-of-year and day-of-month fields.
427
*
428
* @param era the Hijrah era, not null
429
* @param yearOfEra the year-of-era
430
* @param month the month-of-year
431
* @param dayOfMonth the day-of-month
432
* @return the Hijrah local date, not null
433
* @throws DateTimeException if unable to create the date
434
* @throws ClassCastException if the {@code era} is not a {@code HijrahEra}
435
*/
436
@Override
437
public HijrahDate date(Era era, int yearOfEra, int month, int dayOfMonth) {
438
return date(prolepticYear(era, yearOfEra), month, dayOfMonth);
439
}
440
441
/**
442
* Obtains a local date in Hijrah calendar system from the
443
* proleptic-year, month-of-year and day-of-month fields.
444
*
445
* @param prolepticYear the proleptic-year
446
* @param month the month-of-year
447
* @param dayOfMonth the day-of-month
448
* @return the Hijrah local date, not null
449
* @throws DateTimeException if unable to create the date
450
*/
451
@Override
452
public HijrahDate date(int prolepticYear, int month, int dayOfMonth) {
453
return HijrahDate.of(this, prolepticYear, month, dayOfMonth);
454
}
455
456
/**
457
* Obtains a local date in Hijrah calendar system from the
458
* era, year-of-era and day-of-year fields.
459
*
460
* @param era the Hijrah era, not null
461
* @param yearOfEra the year-of-era
462
* @param dayOfYear the day-of-year
463
* @return the Hijrah local date, not null
464
* @throws DateTimeException if unable to create the date
465
* @throws ClassCastException if the {@code era} is not a {@code HijrahEra}
466
*/
467
@Override
468
public HijrahDate dateYearDay(Era era, int yearOfEra, int dayOfYear) {
469
return dateYearDay(prolepticYear(era, yearOfEra), dayOfYear);
470
}
471
472
/**
473
* Obtains a local date in Hijrah calendar system from the
474
* proleptic-year and day-of-year fields.
475
*
476
* @param prolepticYear the proleptic-year
477
* @param dayOfYear the day-of-year
478
* @return the Hijrah local date, not null
479
* @throws DateTimeException if the value of the year is out of range,
480
* or if the day-of-year is invalid for the year
481
*/
482
@Override
483
public HijrahDate dateYearDay(int prolepticYear, int dayOfYear) {
484
HijrahDate date = HijrahDate.of(this, prolepticYear, 1, 1);
485
if (dayOfYear > date.lengthOfYear()) {
486
throw new DateTimeException("Invalid dayOfYear: " + dayOfYear);
487
}
488
return date.plusDays(dayOfYear - 1);
489
}
490
491
/**
492
* Obtains a local date in the Hijrah calendar system from the epoch-day.
493
*
494
* @param epochDay the epoch day
495
* @return the Hijrah local date, not null
496
* @throws DateTimeException if unable to create the date
497
*/
498
@Override // override with covariant return type
499
public HijrahDate dateEpochDay(long epochDay) {
500
return HijrahDate.ofEpochDay(this, epochDay);
501
}
502
503
@Override
504
public HijrahDate dateNow() {
505
return dateNow(Clock.systemDefaultZone());
506
}
507
508
@Override
509
public HijrahDate dateNow(ZoneId zone) {
510
return dateNow(Clock.system(zone));
511
}
512
513
@Override
514
public HijrahDate dateNow(Clock clock) {
515
return date(LocalDate.now(clock));
516
}
517
518
@Override
519
public HijrahDate date(TemporalAccessor temporal) {
520
if (temporal instanceof HijrahDate) {
521
return (HijrahDate) temporal;
522
}
523
return HijrahDate.ofEpochDay(this, temporal.getLong(EPOCH_DAY));
524
}
525
526
@Override
527
@SuppressWarnings("unchecked")
528
public ChronoLocalDateTime<HijrahDate> localDateTime(TemporalAccessor temporal) {
529
return (ChronoLocalDateTime<HijrahDate>) super.localDateTime(temporal);
530
}
531
532
@Override
533
@SuppressWarnings("unchecked")
534
public ChronoZonedDateTime<HijrahDate> zonedDateTime(TemporalAccessor temporal) {
535
return (ChronoZonedDateTime<HijrahDate>) super.zonedDateTime(temporal);
536
}
537
538
@Override
539
@SuppressWarnings("unchecked")
540
public ChronoZonedDateTime<HijrahDate> zonedDateTime(Instant instant, ZoneId zone) {
541
return (ChronoZonedDateTime<HijrahDate>) super.zonedDateTime(instant, zone);
542
}
543
544
//-----------------------------------------------------------------------
545
@Override
546
public boolean isLeapYear(long prolepticYear) {
547
checkCalendarInit();
548
if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {
549
return false;
550
}
551
int len = getYearLength((int) prolepticYear);
552
return (len > 354);
553
}
554
555
@Override
556
public int prolepticYear(Era era, int yearOfEra) {
557
if (era instanceof HijrahEra == false) {
558
throw new ClassCastException("Era must be HijrahEra");
559
}
560
return yearOfEra;
561
}
562
563
@Override
564
public HijrahEra eraOf(int eraValue) {
565
switch (eraValue) {
566
case 1:
567
return HijrahEra.AH;
568
default:
569
throw new DateTimeException("invalid Hijrah era");
570
}
571
}
572
573
@Override
574
public List<Era> eras() {
575
return Arrays.<Era>asList(HijrahEra.values());
576
}
577
578
//-----------------------------------------------------------------------
579
@Override
580
public ValueRange range(ChronoField field) {
581
checkCalendarInit();
582
if (field instanceof ChronoField) {
583
ChronoField f = field;
584
switch (f) {
585
case DAY_OF_MONTH:
586
return ValueRange.of(1, 1, getMinimumMonthLength(), getMaximumMonthLength());
587
case DAY_OF_YEAR:
588
return ValueRange.of(1, getMaximumDayOfYear());
589
case ALIGNED_WEEK_OF_MONTH:
590
return ValueRange.of(1, 5);
591
case YEAR:
592
case YEAR_OF_ERA:
593
return ValueRange.of(getMinimumYear(), getMaximumYear());
594
case ERA:
595
return ValueRange.of(1, 1);
596
default:
597
return field.range();
598
}
599
}
600
return field.range();
601
}
602
603
//-----------------------------------------------------------------------
604
@Override // override for return type
605
public HijrahDate resolveDate(Map<TemporalField, Long> fieldValues, ResolverStyle resolverStyle) {
606
return (HijrahDate) super.resolveDate(fieldValues, resolverStyle);
607
}
608
609
//-----------------------------------------------------------------------
610
/**
611
* Check the validity of a year.
612
*
613
* @param prolepticYear the year to check
614
*/
615
int checkValidYear(long prolepticYear) {
616
if (prolepticYear < getMinimumYear() || prolepticYear > getMaximumYear()) {
617
throw new DateTimeException("Invalid Hijrah year: " + prolepticYear);
618
}
619
return (int) prolepticYear;
620
}
621
622
void checkValidDayOfYear(int dayOfYear) {
623
if (dayOfYear < 1 || dayOfYear > getMaximumDayOfYear()) {
624
throw new DateTimeException("Invalid Hijrah day of year: " + dayOfYear);
625
}
626
}
627
628
void checkValidMonth(int month) {
629
if (month < 1 || month > 12) {
630
throw new DateTimeException("Invalid Hijrah month: " + month);
631
}
632
}
633
634
//-----------------------------------------------------------------------
635
/**
636
* Returns an array containing the Hijrah year, month and day
637
* computed from the epoch day.
638
*
639
* @param epochDay the EpochDay
640
* @return int[0] = YEAR, int[1] = MONTH, int[2] = DATE
641
*/
642
int[] getHijrahDateInfo(int epochDay) {
643
checkCalendarInit(); // ensure that the chronology is initialized
644
if (epochDay < minEpochDay || epochDay >= maxEpochDay) {
645
throw new DateTimeException("Hijrah date out of range");
646
}
647
648
int epochMonth = epochDayToEpochMonth(epochDay);
649
int year = epochMonthToYear(epochMonth);
650
int month = epochMonthToMonth(epochMonth);
651
int day1 = epochMonthToEpochDay(epochMonth);
652
int date = epochDay - day1; // epochDay - dayOfEpoch(year, month);
653
654
int dateInfo[] = new int[3];
655
dateInfo[0] = year;
656
dateInfo[1] = month + 1; // change to 1-based.
657
dateInfo[2] = date + 1; // change to 1-based.
658
return dateInfo;
659
}
660
661
/**
662
* Return the epoch day computed from Hijrah year, month, and day.
663
*
664
* @param prolepticYear the year to represent, 0-origin
665
* @param monthOfYear the month-of-year to represent, 1-origin
666
* @param dayOfMonth the day-of-month to represent, 1-origin
667
* @return the epoch day
668
*/
669
long getEpochDay(int prolepticYear, int monthOfYear, int dayOfMonth) {
670
checkCalendarInit(); // ensure that the chronology is initialized
671
checkValidMonth(monthOfYear);
672
int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);
673
if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {
674
throw new DateTimeException("Invalid Hijrah date, year: " +
675
prolepticYear + ", month: " + monthOfYear);
676
}
677
if (dayOfMonth < 1 || dayOfMonth > getMonthLength(prolepticYear, monthOfYear)) {
678
throw new DateTimeException("Invalid Hijrah day of month: " + dayOfMonth);
679
}
680
return epochMonthToEpochDay(epochMonth) + (dayOfMonth - 1);
681
}
682
683
/**
684
* Returns day of year for the year and month.
685
*
686
* @param prolepticYear a proleptic year
687
* @param month a month, 1-origin
688
* @return the day of year, 1-origin
689
*/
690
int getDayOfYear(int prolepticYear, int month) {
691
return yearMonthToDayOfYear(prolepticYear, (month - 1));
692
}
693
694
/**
695
* Returns month length for the year and month.
696
*
697
* @param prolepticYear a proleptic year
698
* @param monthOfYear a month, 1-origin.
699
* @return the length of the month
700
*/
701
int getMonthLength(int prolepticYear, int monthOfYear) {
702
int epochMonth = yearToEpochMonth(prolepticYear) + (monthOfYear - 1);
703
if (epochMonth < 0 || epochMonth >= hijrahEpochMonthStartDays.length) {
704
throw new DateTimeException("Invalid Hijrah date, year: " +
705
prolepticYear + ", month: " + monthOfYear);
706
}
707
return epochMonthLength(epochMonth);
708
}
709
710
/**
711
* Returns year length.
712
* Note: The 12th month must exist in the data.
713
*
714
* @param prolepticYear a proleptic year
715
* @return year length in days
716
*/
717
int getYearLength(int prolepticYear) {
718
return yearMonthToDayOfYear(prolepticYear, 12);
719
}
720
721
/**
722
* Return the minimum supported Hijrah year.
723
*
724
* @return the minimum
725
*/
726
int getMinimumYear() {
727
return epochMonthToYear(0);
728
}
729
730
/**
731
* Return the maximum supported Hijrah ear.
732
*
733
* @return the minimum
734
*/
735
int getMaximumYear() {
736
return epochMonthToYear(hijrahEpochMonthStartDays.length - 1) - 1;
737
}
738
739
/**
740
* Returns maximum day-of-month.
741
*
742
* @return maximum day-of-month
743
*/
744
int getMaximumMonthLength() {
745
return maxMonthLength;
746
}
747
748
/**
749
* Returns smallest maximum day-of-month.
750
*
751
* @return smallest maximum day-of-month
752
*/
753
int getMinimumMonthLength() {
754
return minMonthLength;
755
}
756
757
/**
758
* Returns maximum day-of-year.
759
*
760
* @return maximum day-of-year
761
*/
762
int getMaximumDayOfYear() {
763
return maxYearLength;
764
}
765
766
/**
767
* Returns smallest maximum day-of-year.
768
*
769
* @return smallest maximum day-of-year
770
*/
771
int getSmallestMaximumDayOfYear() {
772
return minYearLength;
773
}
774
775
/**
776
* Returns the epochMonth found by locating the epochDay in the table. The
777
* epochMonth is the index in the table
778
*
779
* @param epochDay
780
* @return The index of the element of the start of the month containing the
781
* epochDay.
782
*/
783
private int epochDayToEpochMonth(int epochDay) {
784
// binary search
785
int ndx = Arrays.binarySearch(hijrahEpochMonthStartDays, epochDay);
786
if (ndx < 0) {
787
ndx = -ndx - 2;
788
}
789
return ndx;
790
}
791
792
/**
793
* Returns the year computed from the epochMonth
794
*
795
* @param epochMonth the epochMonth
796
* @return the Hijrah Year
797
*/
798
private int epochMonthToYear(int epochMonth) {
799
return (epochMonth + hijrahStartEpochMonth) / 12;
800
}
801
802
/**
803
* Returns the epochMonth for the Hijrah Year.
804
*
805
* @param year the HijrahYear
806
* @return the epochMonth for the beginning of the year.
807
*/
808
private int yearToEpochMonth(int year) {
809
return (year * 12) - hijrahStartEpochMonth;
810
}
811
812
/**
813
* Returns the Hijrah month from the epochMonth.
814
*
815
* @param epochMonth the epochMonth
816
* @return the month of the Hijrah Year
817
*/
818
private int epochMonthToMonth(int epochMonth) {
819
return (epochMonth + hijrahStartEpochMonth) % 12;
820
}
821
822
/**
823
* Returns the epochDay for the start of the epochMonth.
824
*
825
* @param epochMonth the epochMonth
826
* @return the epochDay for the start of the epochMonth.
827
*/
828
private int epochMonthToEpochDay(int epochMonth) {
829
return hijrahEpochMonthStartDays[epochMonth];
830
831
}
832
833
/**
834
* Returns the day of year for the requested HijrahYear and month.
835
*
836
* @param prolepticYear the Hijrah year
837
* @param month the Hijrah month
838
* @return the day of year for the start of the month of the year
839
*/
840
private int yearMonthToDayOfYear(int prolepticYear, int month) {
841
int epochMonthFirst = yearToEpochMonth(prolepticYear);
842
return epochMonthToEpochDay(epochMonthFirst + month)
843
- epochMonthToEpochDay(epochMonthFirst);
844
}
845
846
/**
847
* Returns the length of the epochMonth. It is computed from the start of
848
* the following month minus the start of the requested month.
849
*
850
* @param epochMonth the epochMonth; assumed to be within range
851
* @return the length in days of the epochMonth
852
*/
853
private int epochMonthLength(int epochMonth) {
854
// The very last entry in the epochMonth table is not the start of a month
855
return hijrahEpochMonthStartDays[epochMonth + 1]
856
- hijrahEpochMonthStartDays[epochMonth];
857
}
858
859
//-----------------------------------------------------------------------
860
private static final String KEY_ID = "id";
861
private static final String KEY_TYPE = "type";
862
private static final String KEY_VERSION = "version";
863
private static final String KEY_ISO_START = "iso-start";
864
865
/**
866
* Return the configuration properties from the resource.
867
* <p>
868
* The default location of the variant configuration resource is:
869
* <pre>
870
* "$java.home/lib/" + resource-name
871
* </pre>
872
*
873
* @param resource the name of the calendar property resource
874
* @return a Properties containing the properties read from the resource.
875
* @throws Exception if access to the property resource fails
876
*/
877
private static Properties readConfigProperties(final String resource) throws Exception {
878
try {
879
return AccessController
880
.doPrivileged((java.security.PrivilegedExceptionAction<Properties>)
881
() -> {
882
String libDir = System.getProperty("java.home") + File.separator + "lib";
883
File file = new File(libDir, resource);
884
Properties props = new Properties();
885
try (InputStream is = new FileInputStream(file)) {
886
props.load(is);
887
}
888
return props;
889
});
890
} catch (PrivilegedActionException pax) {
891
throw pax.getException();
892
}
893
}
894
895
/**
896
* Loads and processes the Hijrah calendar properties file for this calendarType.
897
* The starting Hijrah date and the corresponding ISO date are
898
* extracted and used to calculate the epochDate offset.
899
* The version number is identified and ignored.
900
* Everything else is the data for a year with containing the length of each
901
* of 12 months.
902
*
903
* @throws DateTimeException if initialization of the calendar data from the
904
* resource fails
905
*/
906
private void loadCalendarData() {
907
try {
908
String resourceName = calendarProperties.getProperty(PROP_PREFIX + typeId);
909
Objects.requireNonNull(resourceName, "Resource missing for calendar: " + PROP_PREFIX + typeId);
910
Properties props = readConfigProperties(resourceName);
911
912
Map<Integer, int[]> years = new HashMap<>();
913
int minYear = Integer.MAX_VALUE;
914
int maxYear = Integer.MIN_VALUE;
915
String id = null;
916
String type = null;
917
String version = null;
918
int isoStart = 0;
919
for (Map.Entry<Object, Object> entry : props.entrySet()) {
920
String key = (String) entry.getKey();
921
switch (key) {
922
case KEY_ID:
923
id = (String)entry.getValue();
924
break;
925
case KEY_TYPE:
926
type = (String)entry.getValue();
927
break;
928
case KEY_VERSION:
929
version = (String)entry.getValue();
930
break;
931
case KEY_ISO_START: {
932
int[] ymd = parseYMD((String) entry.getValue());
933
isoStart = (int) LocalDate.of(ymd[0], ymd[1], ymd[2]).toEpochDay();
934
break;
935
}
936
default:
937
try {
938
// Everything else is either a year or invalid
939
int year = Integer.valueOf(key);
940
int[] months = parseMonths((String) entry.getValue());
941
years.put(year, months);
942
maxYear = Math.max(maxYear, year);
943
minYear = Math.min(minYear, year);
944
} catch (NumberFormatException nfe) {
945
throw new IllegalArgumentException("bad key: " + key);
946
}
947
}
948
}
949
950
if (!getId().equals(id)) {
951
throw new IllegalArgumentException("Configuration is for a different calendar: " + id);
952
}
953
if (!getCalendarType().equals(type)) {
954
throw new IllegalArgumentException("Configuration is for a different calendar type: " + type);
955
}
956
if (version == null || version.isEmpty()) {
957
throw new IllegalArgumentException("Configuration does not contain a version");
958
}
959
if (isoStart == 0) {
960
throw new IllegalArgumentException("Configuration does not contain a ISO start date");
961
}
962
963
// Now create and validate the array of epochDays indexed by epochMonth
964
hijrahStartEpochMonth = minYear * 12;
965
minEpochDay = isoStart;
966
hijrahEpochMonthStartDays = createEpochMonths(minEpochDay, minYear, maxYear, years);
967
maxEpochDay = hijrahEpochMonthStartDays[hijrahEpochMonthStartDays.length - 1];
968
969
// Compute the min and max year length in days.
970
for (int year = minYear; year < maxYear; year++) {
971
int length = getYearLength(year);
972
minYearLength = Math.min(minYearLength, length);
973
maxYearLength = Math.max(maxYearLength, length);
974
}
975
} catch (Exception ex) {
976
// Log error and throw a DateTimeException
977
PlatformLogger logger = PlatformLogger.getLogger("java.time.chrono");
978
logger.severe("Unable to initialize Hijrah calendar proxy: " + typeId, ex);
979
throw new DateTimeException("Unable to initialize HijrahCalendar: " + typeId, ex);
980
}
981
}
982
983
/**
984
* Converts the map of year to month lengths ranging from minYear to maxYear
985
* into a linear contiguous array of epochDays. The index is the hijrahMonth
986
* computed from year and month and offset by minYear. The value of each
987
* entry is the epochDay corresponding to the first day of the month.
988
*
989
* @param minYear The minimum year for which data is provided
990
* @param maxYear The maximum year for which data is provided
991
* @param years a Map of year to the array of 12 month lengths
992
* @return array of epochDays for each month from min to max
993
*/
994
private int[] createEpochMonths(int epochDay, int minYear, int maxYear, Map<Integer, int[]> years) {
995
// Compute the size for the array of dates
996
int numMonths = (maxYear - minYear + 1) * 12 + 1;
997
998
// Initialize the running epochDay as the corresponding ISO Epoch day
999
int epochMonth = 0; // index into array of epochMonths
1000
int[] epochMonths = new int[numMonths];
1001
minMonthLength = Integer.MAX_VALUE;
1002
maxMonthLength = Integer.MIN_VALUE;
1003
1004
// Only whole years are valid, any zero's in the array are illegal
1005
for (int year = minYear; year <= maxYear; year++) {
1006
int[] months = years.get(year);// must not be gaps
1007
for (int month = 0; month < 12; month++) {
1008
int length = months[month];
1009
epochMonths[epochMonth++] = epochDay;
1010
1011
if (length < 29 || length > 32) {
1012
throw new IllegalArgumentException("Invalid month length in year: " + minYear);
1013
}
1014
epochDay += length;
1015
minMonthLength = Math.min(minMonthLength, length);
1016
maxMonthLength = Math.max(maxMonthLength, length);
1017
}
1018
}
1019
1020
// Insert the final epochDay
1021
epochMonths[epochMonth++] = epochDay;
1022
1023
if (epochMonth != epochMonths.length) {
1024
throw new IllegalStateException("Did not fill epochMonths exactly: ndx = " + epochMonth
1025
+ " should be " + epochMonths.length);
1026
}
1027
1028
return epochMonths;
1029
}
1030
1031
/**
1032
* Parses the 12 months lengths from a property value for a specific year.
1033
*
1034
* @param line the value of a year property
1035
* @return an array of int[12] containing the 12 month lengths
1036
* @throws IllegalArgumentException if the number of months is not 12
1037
* @throws NumberFormatException if the 12 tokens are not numbers
1038
*/
1039
private int[] parseMonths(String line) {
1040
int[] months = new int[12];
1041
String[] numbers = line.split("\\s");
1042
if (numbers.length != 12) {
1043
throw new IllegalArgumentException("wrong number of months on line: " + Arrays.toString(numbers) + "; count: " + numbers.length);
1044
}
1045
for (int i = 0; i < 12; i++) {
1046
try {
1047
months[i] = Integer.valueOf(numbers[i]);
1048
} catch (NumberFormatException nfe) {
1049
throw new IllegalArgumentException("bad key: " + numbers[i]);
1050
}
1051
}
1052
return months;
1053
}
1054
1055
/**
1056
* Parse yyyy-MM-dd into a 3 element array [yyyy, mm, dd].
1057
*
1058
* @param string the input string
1059
* @return the 3 element array with year, month, day
1060
*/
1061
private int[] parseYMD(String string) {
1062
// yyyy-MM-dd
1063
string = string.trim();
1064
try {
1065
if (string.charAt(4) != '-' || string.charAt(7) != '-') {
1066
throw new IllegalArgumentException("date must be yyyy-MM-dd");
1067
}
1068
int[] ymd = new int[3];
1069
ymd[0] = Integer.valueOf(string.substring(0, 4));
1070
ymd[1] = Integer.valueOf(string.substring(5, 7));
1071
ymd[2] = Integer.valueOf(string.substring(8, 10));
1072
return ymd;
1073
} catch (NumberFormatException ex) {
1074
throw new IllegalArgumentException("date must be yyyy-MM-dd", ex);
1075
}
1076
}
1077
1078
//-----------------------------------------------------------------------
1079
/**
1080
* Writes the Chronology using a
1081
* <a href="../../../serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.
1082
* @serialData
1083
* <pre>
1084
* out.writeByte(1); // identifies a Chronology
1085
* out.writeUTF(getId());
1086
* </pre>
1087
*
1088
* @return the instance of {@code Ser}, not null
1089
*/
1090
@Override
1091
Object writeReplace() {
1092
return super.writeReplace();
1093
}
1094
1095
/**
1096
* Defend against malicious streams.
1097
*
1098
* @param s the stream to read
1099
* @throws InvalidObjectException always
1100
*/
1101
private void readObject(ObjectInputStream s) throws InvalidObjectException {
1102
throw new InvalidObjectException("Deserialization via serialization delegate");
1103
}
1104
}
1105
1106