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