1 /* 2 * Copyright (c) 2013, 2019, 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) 2013, 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.chrono; 58 59 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 60 import static java.time.temporal.ChronoUnit.DAYS; 61 import static java.time.temporal.ChronoUnit.MONTHS; 62 import static java.time.temporal.ChronoUnit.YEARS; 63 64 import java.io.DataInput; 65 import java.io.DataOutput; 66 import java.io.IOException; 67 import java.io.InvalidObjectException; 68 import java.io.ObjectInputStream; 69 import java.io.ObjectStreamException; 70 import java.io.Serializable; 71 import java.time.DateTimeException; 72 import java.time.temporal.ChronoUnit; 73 import java.time.temporal.Temporal; 74 import java.time.temporal.TemporalAccessor; 75 import java.time.temporal.TemporalAmount; 76 import java.time.temporal.TemporalQueries; 77 import java.time.temporal.TemporalUnit; 78 import java.time.temporal.UnsupportedTemporalTypeException; 79 import java.time.temporal.ValueRange; 80 import java.util.List; 81 import java.util.Objects; 82 83 /** 84 * A period expressed in terms of a standard year-month-day calendar system. 85 * <p> 86 * This class is used by applications seeking to handle dates in non-ISO calendar systems. 87 * For example, the Japanese, Minguo, Thai Buddhist and others. 88 * 89 * @implSpec 90 * This class is immutable nad thread-safe. 91 * 92 * @since 1.8 93 */ 94 final class ChronoPeriodImpl 95 implements ChronoPeriod, Serializable { 96 // this class is only used by JDK chronology implementations and makes assumptions based on that fact 97 98 /** 99 * Serialization version. 100 */ 101 @java.io.Serial 102 private static final long serialVersionUID = 57387258289L; 103 104 /** 105 * The set of supported units. 106 */ 107 private static final List<TemporalUnit> SUPPORTED_UNITS = List.of(YEARS, MONTHS, DAYS); 108 109 /** 110 * The chronology. 111 */ 112 @SuppressWarnings("serial") // Not statically typed as Serializable 113 private final Chronology chrono; 114 /** 115 * The number of years. 116 */ 117 final int years; 118 /** 119 * The number of months. 120 */ 121 final int months; 122 /** 123 * The number of days. 124 */ 125 final int days; 126 127 /** 128 * Creates an instance. 129 */ ChronoPeriodImpl(Chronology chrono, int years, int months, int days)130 ChronoPeriodImpl(Chronology chrono, int years, int months, int days) { 131 Objects.requireNonNull(chrono, "chrono"); 132 this.chrono = chrono; 133 this.years = years; 134 this.months = months; 135 this.days = days; 136 } 137 138 //----------------------------------------------------------------------- 139 @Override get(TemporalUnit unit)140 public long get(TemporalUnit unit) { 141 if (unit == ChronoUnit.YEARS) { 142 return years; 143 } else if (unit == ChronoUnit.MONTHS) { 144 return months; 145 } else if (unit == ChronoUnit.DAYS) { 146 return days; 147 } else { 148 throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); 149 } 150 } 151 152 @Override getUnits()153 public List<TemporalUnit> getUnits() { 154 return ChronoPeriodImpl.SUPPORTED_UNITS; 155 } 156 157 @Override getChronology()158 public Chronology getChronology() { 159 return chrono; 160 } 161 162 //----------------------------------------------------------------------- 163 @Override isZero()164 public boolean isZero() { 165 return years == 0 && months == 0 && days == 0; 166 } 167 168 @Override isNegative()169 public boolean isNegative() { 170 return years < 0 || months < 0 || days < 0; 171 } 172 173 //----------------------------------------------------------------------- 174 @Override plus(TemporalAmount amountToAdd)175 public ChronoPeriod plus(TemporalAmount amountToAdd) { 176 ChronoPeriodImpl amount = validateAmount(amountToAdd); 177 return new ChronoPeriodImpl( 178 chrono, 179 Math.addExact(years, amount.years), 180 Math.addExact(months, amount.months), 181 Math.addExact(days, amount.days)); 182 } 183 184 @Override minus(TemporalAmount amountToSubtract)185 public ChronoPeriod minus(TemporalAmount amountToSubtract) { 186 ChronoPeriodImpl amount = validateAmount(amountToSubtract); 187 return new ChronoPeriodImpl( 188 chrono, 189 Math.subtractExact(years, amount.years), 190 Math.subtractExact(months, amount.months), 191 Math.subtractExact(days, amount.days)); 192 } 193 194 /** 195 * Obtains an instance of {@code ChronoPeriodImpl} from a temporal amount. 196 * 197 * @param amount the temporal amount to convert, not null 198 * @return the period, not null 199 */ validateAmount(TemporalAmount amount)200 private ChronoPeriodImpl validateAmount(TemporalAmount amount) { 201 Objects.requireNonNull(amount, "amount"); 202 if (!(amount instanceof ChronoPeriodImpl period)) { 203 throw new DateTimeException("Unable to obtain ChronoPeriod from TemporalAmount: " + amount.getClass()); 204 } 205 if (!(chrono.equals(period.getChronology()))) { 206 throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + period.getChronology().getId()); 207 } 208 return period; 209 } 210 211 //----------------------------------------------------------------------- 212 @Override multipliedBy(int scalar)213 public ChronoPeriod multipliedBy(int scalar) { 214 if (this.isZero() || scalar == 1) { 215 return this; 216 } 217 return new ChronoPeriodImpl( 218 chrono, 219 Math.multiplyExact(years, scalar), 220 Math.multiplyExact(months, scalar), 221 Math.multiplyExact(days, scalar)); 222 } 223 224 //----------------------------------------------------------------------- 225 @Override normalized()226 public ChronoPeriod normalized() { 227 long monthRange = monthRange(); 228 if (monthRange > 0) { 229 long totalMonths = years * monthRange + months; 230 long splitYears = totalMonths / monthRange; 231 int splitMonths = (int) (totalMonths % monthRange); // no overflow 232 if (splitYears == years && splitMonths == months) { 233 return this; 234 } 235 return new ChronoPeriodImpl(chrono, Math.toIntExact(splitYears), splitMonths, days); 236 237 } 238 return this; 239 } 240 241 /** 242 * Calculates the range of months. 243 * 244 * @return the month range, -1 if not fixed range 245 */ monthRange()246 private long monthRange() { 247 ValueRange startRange = chrono.range(MONTH_OF_YEAR); 248 if (startRange.isFixed() && startRange.isIntValue()) { 249 return startRange.getMaximum() - startRange.getMinimum() + 1; 250 } 251 return -1; 252 } 253 254 //------------------------------------------------------------------------- 255 @Override addTo(Temporal temporal)256 public Temporal addTo(Temporal temporal) { 257 validateChrono(temporal); 258 if (months == 0) { 259 if (years != 0) { 260 temporal = temporal.plus(years, YEARS); 261 } 262 } else { 263 long monthRange = monthRange(); 264 if (monthRange > 0) { 265 temporal = temporal.plus(years * monthRange + months, MONTHS); 266 } else { 267 if (years != 0) { 268 temporal = temporal.plus(years, YEARS); 269 } 270 temporal = temporal.plus(months, MONTHS); 271 } 272 } 273 if (days != 0) { 274 temporal = temporal.plus(days, DAYS); 275 } 276 return temporal; 277 } 278 279 280 281 @Override subtractFrom(Temporal temporal)282 public Temporal subtractFrom(Temporal temporal) { 283 validateChrono(temporal); 284 if (months == 0) { 285 if (years != 0) { 286 temporal = temporal.minus(years, YEARS); 287 } 288 } else { 289 long monthRange = monthRange(); 290 if (monthRange > 0) { 291 temporal = temporal.minus(years * monthRange + months, MONTHS); 292 } else { 293 if (years != 0) { 294 temporal = temporal.minus(years, YEARS); 295 } 296 temporal = temporal.minus(months, MONTHS); 297 } 298 } 299 if (days != 0) { 300 temporal = temporal.minus(days, DAYS); 301 } 302 return temporal; 303 } 304 305 /** 306 * Validates that the temporal has the correct chronology. 307 */ validateChrono(TemporalAccessor temporal)308 private void validateChrono(TemporalAccessor temporal) { 309 Objects.requireNonNull(temporal, "temporal"); 310 Chronology temporalChrono = temporal.query(TemporalQueries.chronology()); 311 if (temporalChrono != null && chrono.equals(temporalChrono) == false) { 312 throw new DateTimeException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + temporalChrono.getId()); 313 } 314 } 315 316 //----------------------------------------------------------------------- 317 @Override equals(Object obj)318 public boolean equals(Object obj) { 319 if (this == obj) { 320 return true; 321 } 322 return (obj instanceof ChronoPeriodImpl other) 323 && years == other.years && months == other.months 324 && days == other.days && chrono.equals(other.chrono); 325 } 326 327 @Override hashCode()328 public int hashCode() { 329 return (years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16)) ^ chrono.hashCode(); 330 } 331 332 //----------------------------------------------------------------------- 333 @Override toString()334 public String toString() { 335 if (isZero()) { 336 return getChronology().toString() + " P0D"; 337 } else { 338 StringBuilder buf = new StringBuilder(); 339 buf.append(getChronology().toString()).append(' ').append('P'); 340 if (years != 0) { 341 buf.append(years).append('Y'); 342 } 343 if (months != 0) { 344 buf.append(months).append('M'); 345 } 346 if (days != 0) { 347 buf.append(days).append('D'); 348 } 349 return buf.toString(); 350 } 351 } 352 353 //----------------------------------------------------------------------- 354 /** 355 * Writes the Chronology using a 356 * <a href="{@docRoot}/serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>. 357 * <pre> 358 * out.writeByte(12); // identifies this as a ChronoPeriodImpl 359 * out.writeUTF(getId()); // the chronology 360 * out.writeInt(years); 361 * out.writeInt(months); 362 * out.writeInt(days); 363 * </pre> 364 * 365 * @return the instance of {@code Ser}, not null 366 */ 367 @java.io.Serial writeReplace()368 protected Object writeReplace() { 369 return new Ser(Ser.CHRONO_PERIOD_TYPE, this); 370 } 371 372 /** 373 * Defend against malicious streams. 374 * 375 * @param s the stream to read 376 * @throws InvalidObjectException always 377 */ 378 @java.io.Serial readObject(ObjectInputStream s)379 private void readObject(ObjectInputStream s) throws ObjectStreamException { 380 throw new InvalidObjectException("Deserialization via serialization delegate"); 381 } 382 writeExternal(DataOutput out)383 void writeExternal(DataOutput out) throws IOException { 384 out.writeUTF(chrono.getId()); 385 out.writeInt(years); 386 out.writeInt(months); 387 out.writeInt(days); 388 } 389 readExternal(DataInput in)390 static ChronoPeriodImpl readExternal(DataInput in) throws IOException { 391 Chronology chrono = Chronology.of(in.readUTF()); 392 int years = in.readInt(); 393 int months = in.readInt(); 394 int days = in.readInt(); 395 return new ChronoPeriodImpl(chrono, years, months, days); 396 } 397 398 } 399