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