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 package java.time.calendar; 58 59 import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH; 60 import static java.time.temporal.ChronoField.ALIGNED_DAY_OF_WEEK_IN_YEAR; 61 import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_MONTH; 62 import static java.time.temporal.ChronoField.ALIGNED_WEEK_OF_YEAR; 63 import static java.time.temporal.ChronoField.DAY_OF_MONTH; 64 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 65 import static java.time.temporal.ChronoField.YEAR; 66 67 import java.io.IOException; 68 import java.io.ObjectInput; 69 import java.io.ObjectOutput; 70 import java.io.Serializable; 71 import java.time.DateTimeException; 72 import java.time.DayOfWeek; 73 import java.time.LocalDate; 74 import java.time.temporal.ChronoField; 75 import java.time.temporal.ChronoLocalDate; 76 import java.time.temporal.TemporalField; 77 import java.time.temporal.ValueRange; 78 import java.util.Objects; 79 80 /** 81 * A date in the Hijrah calendar system. 82 * <p> 83 * This implements {@code ChronoLocalDate} for the {@link HijrahChrono Hijrah calendar}. 84 * <p> 85 * The Hijrah calendar has a different total of days in a year than 86 * Gregorian calendar, and a month is based on the period of a complete 87 * revolution of the moon around the earth (as between successive new moons). 88 * The calendar cycles becomes longer and unstable, and sometimes a manual 89 * adjustment (for entering deviation) is necessary for correctness 90 * because of the complex algorithm. 91 * <p> 92 * HijrahDate supports the manual adjustment feature by providing a configuration 93 * file. The configuration file contains the adjustment (deviation) data with following format. 94 * <pre> 95 * StartYear/StartMonth(0-based)-EndYear/EndMonth(0-based):Deviation day (1, 2, -1, or -2) 96 * Line separator or ";" is used for the separator of each deviation data.</pre> 97 * Here is the example. 98 * <pre> 99 * 1429/0-1429/1:1 100 * 1429/2-1429/7:1;1429/6-1429/11:1 101 * 1429/11-9999/11:1</pre> 102 * The default location of the configuration file is: 103 * <pre> 104 * $CLASSPATH/java/time/i18n</pre> 105 * And the default file name is: 106 * <pre> 107 * hijrah_deviation.cfg</pre> 108 * The default location and file name can be overriden by setting 109 * following two Java's system property. 110 * <pre> 111 * Location: java.time.i18n.HijrahDate.deviationConfigDir 112 * File name: java.time.i18n.HijrahDate.deviationConfigFile</pre> 113 * 114 * <h3>Specification for implementors</h3> 115 * This class is immutable and thread-safe. 116 * 117 * @since 1.8 118 */ 119 final class HijrahDate 120 extends ChronoDateImpl<HijrahChrono> 121 implements ChronoLocalDate<HijrahChrono>, Serializable { 122 // this class is package-scoped so that future conversion to public 123 // would not change serialization 124 125 /** 126 * Serialization version. 127 */ 128 private static final long serialVersionUID = -5207853542612002020L; 129 130 /** 131 * The Chronology of this HijrahDate. 132 */ 133 private final HijrahChrono chrono; 134 /** 135 * The era. 136 */ 137 private final transient HijrahEra era; 138 /** 139 * The year. 140 */ 141 private final transient int yearOfEra; 142 /** 143 * The month-of-year. 144 */ 145 private final transient int monthOfYear; 146 /** 147 * The day-of-month. 148 */ 149 private final transient int dayOfMonth; 150 /** 151 * The day-of-year. 152 */ 153 private final transient int dayOfYear; 154 /** 155 * The day-of-week. 156 */ 157 private final transient DayOfWeek dayOfWeek; 158 /** 159 * Gregorian days for this object. Holding number of days since 1970/01/01. 160 * The number of days are calculated with pure Gregorian calendar 161 * based. 162 */ 163 private final long gregorianEpochDay; 164 /** 165 * True if year is leap year. 166 */ 167 private final transient boolean isLeapYear; 168 169 //------------------------------------------------------------------------- 170 /** 171 * Obtains an instance of {@code HijrahDate} from the Hijrah era year, 172 * month-of-year and day-of-month. This uses the Hijrah era. 173 * 174 * @param prolepticYear the proleptic year to represent in the Hijrah 175 * @param monthOfYear the month-of-year to represent, from 1 to 12 176 * @param dayOfMonth the day-of-month to represent, from 1 to 30 177 * @return the Hijrah date, never null 178 * @throws DateTimeException if the value of any field is out of range 179 */ 180 static HijrahDate of(HijrahChrono chrono, int prolepticYear, int monthOfYear, int dayOfMonth) { 181 return (prolepticYear >= 1) ? 182 HijrahDate.of(chrono, HijrahEra.AH, prolepticYear, monthOfYear, dayOfMonth) : 183 HijrahDate.of(chrono, HijrahEra.BEFORE_AH, 1 - prolepticYear, monthOfYear, dayOfMonth); 184 } 185 186 /** 187 * Obtains an instance of {@code HijrahDate} from the era, year-of-era 188 * month-of-year and day-of-month. 189 * 190 * @param era the era to represent, not null 191 * @param yearOfEra the year-of-era to represent, from 1 to 9999 192 * @param monthOfYear the month-of-year to represent, from 1 to 12 193 * @param dayOfMonth the day-of-month to represent, from 1 to 31 194 * @return the Hijrah date, never null 195 * @throws DateTimeException if the value of any field is out of range 196 */ 197 private static HijrahDate of(HijrahChrono chrono, HijrahEra era, int yearOfEra, int monthOfYear, int dayOfMonth) { 198 Objects.requireNonNull(era, "era"); 199 chrono.checkValidYearOfEra(yearOfEra); 200 chrono.checkValidMonth(monthOfYear); 201 chrono.checkValidDayOfMonth(dayOfMonth); 202 long gregorianDays = chrono.getGregorianEpochDay(era.prolepticYear(yearOfEra), monthOfYear, dayOfMonth); 203 return new HijrahDate(chrono, gregorianDays); 204 } 205 206 /** 207 * Obtains an instance of {@code HijrahDate} from a date. 208 * 209 * @param date the date to use, not null 210 * @return the Hijrah date, never null 211 * @throws DateTimeException if the year is invalid 212 */ 213 private static HijrahDate of(HijrahChrono chrono, LocalDate date) { 214 long gregorianDays = date.toEpochDay(); 215 return new HijrahDate(chrono, gregorianDays); 216 } 217 218 static HijrahDate ofEpochDay(HijrahChrono chrono, long epochDay) { 219 return new HijrahDate(chrono, epochDay); 220 } 221 222 /** 223 * Constructs an instance with the specified date. 224 * 225 * @param gregorianDay the number of days from 0001/01/01 (Gregorian), caller calculated 226 */ 227 private HijrahDate(HijrahChrono chrono, long gregorianDay) { 228 this.chrono = chrono; 229 int[] dateInfo = chrono.getHijrahDateInfo(gregorianDay); 230 231 chrono.checkValidYearOfEra(dateInfo[1]); 232 chrono.checkValidMonth(dateInfo[2]); 233 chrono.checkValidDayOfMonth(dateInfo[3]); 234 chrono.checkValidDayOfYear(dateInfo[4]); 235 236 this.era = HijrahEra.of(dateInfo[0]); 237 this.yearOfEra = dateInfo[1]; 238 this.monthOfYear = dateInfo[2]; 239 this.dayOfMonth = dateInfo[3]; 240 this.dayOfYear = dateInfo[4]; 241 this.dayOfWeek = DayOfWeek.of(dateInfo[5]); 242 this.gregorianEpochDay = gregorianDay; 243 this.isLeapYear = chrono.isLeapYear(this.yearOfEra); 244 } 245 246 //----------------------------------------------------------------------- 247 @Override 248 public HijrahChrono getChrono() { 249 return chrono; 250 } 251 252 @Override 253 public ValueRange range(TemporalField field) { 254 if (field instanceof ChronoField) { 255 if (isSupported(field)) { 256 ChronoField f = (ChronoField) field; 257 switch (f) { 258 case DAY_OF_MONTH: return ValueRange.of(1, lengthOfMonth()); 259 case DAY_OF_YEAR: return ValueRange.of(1, lengthOfYear()); 260 case ALIGNED_WEEK_OF_MONTH: return ValueRange.of(1, 5); // TODO 261 case YEAR_OF_ERA: return ValueRange.of(1, 1000); // TODO 262 } 263 return getChrono().range(f); 264 } 265 throw new DateTimeException("Unsupported field: " + field.getName()); 266 } 267 return field.doRange(this); 268 } 269 270 @Override 271 public long getLong(TemporalField field) { 272 if (field instanceof ChronoField) { 273 switch ((ChronoField) field) { 274 case DAY_OF_WEEK: return dayOfWeek.getValue(); 275 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((dayOfWeek.getValue() - 1) % 7) + 1; 276 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((dayOfYear - 1) % 7) + 1; 277 case DAY_OF_MONTH: return this.dayOfMonth; 278 case DAY_OF_YEAR: return this.dayOfYear; 279 case EPOCH_DAY: return toEpochDay(); 280 case ALIGNED_WEEK_OF_MONTH: return ((dayOfMonth - 1) / 7) + 1; 281 case ALIGNED_WEEK_OF_YEAR: return ((dayOfYear - 1) / 7) + 1; 282 case MONTH_OF_YEAR: return monthOfYear; 283 case YEAR_OF_ERA: return yearOfEra; 284 case YEAR: return yearOfEra; 285 case ERA: return era.getValue(); 286 } 287 throw new DateTimeException("Unsupported field: " + field.getName()); 288 } 289 return field.doGet(this); 290 } 291 292 @Override 293 public HijrahDate with(TemporalField field, long newValue) { 294 if (field instanceof ChronoField) { 295 ChronoField f = (ChronoField) field; 296 f.checkValidValue(newValue); // TODO: validate value 297 int nvalue = (int) newValue; 298 switch (f) { 299 case DAY_OF_WEEK: return plusDays(newValue - dayOfWeek.getValue()); 300 case ALIGNED_DAY_OF_WEEK_IN_MONTH: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_MONTH)); 301 case ALIGNED_DAY_OF_WEEK_IN_YEAR: return plusDays(newValue - getLong(ALIGNED_DAY_OF_WEEK_IN_YEAR)); 302 case DAY_OF_MONTH: return resolvePreviousValid(yearOfEra, monthOfYear, nvalue); 303 case DAY_OF_YEAR: return resolvePreviousValid(yearOfEra, ((nvalue - 1) / 30) + 1, ((nvalue - 1) % 30) + 1); 304 case EPOCH_DAY: return new HijrahDate(chrono, nvalue); 305 case ALIGNED_WEEK_OF_MONTH: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_MONTH)) * 7); 306 case ALIGNED_WEEK_OF_YEAR: return plusDays((newValue - getLong(ALIGNED_WEEK_OF_YEAR)) * 7); 307 case MONTH_OF_YEAR: return resolvePreviousValid(yearOfEra, nvalue, dayOfMonth); 308 case YEAR_OF_ERA: return resolvePreviousValid(yearOfEra >= 1 ? nvalue : 1 - nvalue, monthOfYear, dayOfMonth); 309 case YEAR: return resolvePreviousValid(nvalue, monthOfYear, dayOfMonth); 310 case ERA: return resolvePreviousValid(1 - yearOfEra, monthOfYear, dayOfMonth); 311 } 312 throw new DateTimeException("Unsupported field: " + field.getName()); 313 } 314 return (HijrahDate) ChronoLocalDate.super.with(field, newValue); 315 } 316 317 private HijrahDate resolvePreviousValid(int yearOfEra, int month, int day) { 318 int monthDays = getMonthDays(month - 1, yearOfEra); 319 if (day > monthDays) { 320 day = monthDays; 321 } 322 return HijrahDate.of(chrono, yearOfEra, month, day); 323 } 324 325 @Override 326 public long toEpochDay() { 327 return chrono.getGregorianEpochDay(yearOfEra, monthOfYear, dayOfMonth); 328 } 329 330 //----------------------------------------------------------------------- 331 @Override 332 public HijrahEra getEra() { 333 return this.era; 334 } 335 336 //----------------------------------------------------------------------- 337 /** 338 * Checks if the year is a leap year, according to the Hijrah calendar system rules. 339 * 340 * @return true if this date is in a leap year 341 */ 342 @Override 343 public boolean isLeapYear() { 344 return this.isLeapYear; 345 } 346 347 //----------------------------------------------------------------------- 348 @Override 349 public HijrahDate plusYears(long years) { 350 if (years == 0) { 351 return this; 352 } 353 int newYear = Math.addExact(this.yearOfEra, (int)years); 354 return HijrahDate.of(chrono, this.era, newYear, this.monthOfYear, this.dayOfMonth); 355 } 356 357 @Override 358 public HijrahDate plusMonths(long months) { 359 if (months == 0) { 360 return this; 361 } 362 int newMonth = this.monthOfYear - 1; 363 newMonth = newMonth + (int)months; 364 int years = newMonth / 12; 365 newMonth = newMonth % 12; 366 while (newMonth < 0) { 367 newMonth += 12; 368 years = Math.subtractExact(years, 1); 369 } 370 int newYear = Math.addExact(this.yearOfEra, years); 371 return HijrahDate.of(chrono, this.era, newYear, newMonth + 1, this.dayOfMonth); 372 } 373 374 @Override 375 public HijrahDate plusDays(long days) { 376 return new HijrahDate(chrono, this.gregorianEpochDay + days); 377 } 378 379 /** 380 * Returns month days from the beginning of year. 381 * 382 * @param month month (0-based) 383 * @parma year year 384 * @return month days from the beginning of year 385 */ 386 private int getMonthDays(int month, int year) { 387 int[] newMonths = chrono.getAdjustedMonthDays(year); 388 return newMonths[month]; 389 } 390 391 /** 392 * Returns month length. 393 * 394 * @param month month (0-based) 395 * @param year year 396 * @return month length 397 */ 398 private int getMonthLength(int month, int year) { 399 int[] newMonths = chrono.getAdjustedMonthLength(year); 400 return newMonths[month]; 401 } 402 403 @Override 404 public int lengthOfMonth() { 405 return getMonthLength(monthOfYear - 1, yearOfEra); 406 } 407 408 @Override 409 public int lengthOfYear() { 410 return chrono.getYearLength(yearOfEra); // TODO: proleptic year 411 } 412 413 //----------------------------------------------------------------------- 414 private Object writeReplace() { 415 return new Ser(Ser.HIJRAH_DATE_TYPE, this); 416 } 417 418 void writeExternal(ObjectOutput out) throws IOException { 419 // HijrahChrono is implicit in the Hijrah_DATE_TYPE 420 out.writeObject(chrono); 421 out.writeInt(get(YEAR)); 422 out.writeByte(get(MONTH_OF_YEAR)); 423 out.writeByte(get(DAY_OF_MONTH)); 424 } 425 426 /** 427 * Replaces the date instance from the stream with a valid one. 428 * ReadExternal has already read the fields and created a new instance 429 * from the data. 430 * 431 * @return the resolved date, never null 432 */ 433 private Object readResolve() { 434 return this; 435 } 436 437 static ChronoLocalDate<HijrahChrono> readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 438 HijrahChrono chrono = (HijrahChrono)in.readObject(); 439 int year = in.readInt(); 440 int month = in.readByte(); 441 int dayOfMonth = in.readByte(); 442 return chrono.date(year, month, dayOfMonth); 443 } 444 445 }