Path: blob/master/src/java.base/share/classes/java/time/chrono/ChronoPeriodImpl.java
41159 views
/*1* Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* Copyright (c) 2013, Stephen Colebourne & Michael Nascimento Santos27*28* All rights reserved.29*30* Redistribution and use in source and binary forms, with or without31* modification, are permitted provided that the following conditions are met:32*33* * Redistributions of source code must retain the above copyright notice,34* this list of conditions and the following disclaimer.35*36* * Redistributions in binary form must reproduce the above copyright notice,37* this list of conditions and the following disclaimer in the documentation38* and/or other materials provided with the distribution.39*40* * Neither the name of JSR-310 nor the names of its contributors41* may be used to endorse or promote products derived from this software42* without specific prior written permission.43*44* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS45* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT46* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR47* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR48* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,49* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,50* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR51* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF52* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING53* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS54* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.55*/56package java.time.chrono;5758import static java.time.temporal.ChronoField.MONTH_OF_YEAR;59import static java.time.temporal.ChronoUnit.DAYS;60import static java.time.temporal.ChronoUnit.MONTHS;61import static java.time.temporal.ChronoUnit.YEARS;6263import java.io.DataInput;64import java.io.DataOutput;65import java.io.IOException;66import java.io.InvalidObjectException;67import java.io.ObjectInputStream;68import java.io.ObjectStreamException;69import java.io.Serializable;70import java.time.DateTimeException;71import java.time.temporal.ChronoUnit;72import java.time.temporal.Temporal;73import java.time.temporal.TemporalAccessor;74import java.time.temporal.TemporalAmount;75import java.time.temporal.TemporalQueries;76import java.time.temporal.TemporalUnit;77import java.time.temporal.UnsupportedTemporalTypeException;78import java.time.temporal.ValueRange;79import java.util.List;80import java.util.Objects;8182/**83* A period expressed in terms of a standard year-month-day calendar system.84* <p>85* This class is used by applications seeking to handle dates in non-ISO calendar systems.86* For example, the Japanese, Minguo, Thai Buddhist and others.87*88* @implSpec89* This class is immutable nad thread-safe.90*91* @since 1.892*/93final class ChronoPeriodImpl94implements ChronoPeriod, Serializable {95// this class is only used by JDK chronology implementations and makes assumptions based on that fact9697/**98* Serialization version.99*/100@java.io.Serial101private static final long serialVersionUID = 57387258289L;102103/**104* The set of supported units.105*/106private static final List<TemporalUnit> SUPPORTED_UNITS = List.of(YEARS, MONTHS, DAYS);107108/**109* The chronology.110*/111@SuppressWarnings("serial") // Not statically typed as Serializable112private final Chronology chrono;113/**114* The number of years.115*/116final int years;117/**118* The number of months.119*/120final int months;121/**122* The number of days.123*/124final int days;125126/**127* Creates an instance.128*/129ChronoPeriodImpl(Chronology chrono, int years, int months, int days) {130Objects.requireNonNull(chrono, "chrono");131this.chrono = chrono;132this.years = years;133this.months = months;134this.days = days;135}136137//-----------------------------------------------------------------------138@Override139public long get(TemporalUnit unit) {140if (unit == ChronoUnit.YEARS) {141return years;142} else if (unit == ChronoUnit.MONTHS) {143return months;144} else if (unit == ChronoUnit.DAYS) {145return days;146} else {147throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);148}149}150151@Override152public List<TemporalUnit> getUnits() {153return ChronoPeriodImpl.SUPPORTED_UNITS;154}155156@Override157public Chronology getChronology() {158return chrono;159}160161//-----------------------------------------------------------------------162@Override163public boolean isZero() {164return years == 0 && months == 0 && days == 0;165}166167@Override168public boolean isNegative() {169return years < 0 || months < 0 || days < 0;170}171172//-----------------------------------------------------------------------173@Override174public ChronoPeriod plus(TemporalAmount amountToAdd) {175ChronoPeriodImpl amount = validateAmount(amountToAdd);176return new ChronoPeriodImpl(177chrono,178Math.addExact(years, amount.years),179Math.addExact(months, amount.months),180Math.addExact(days, amount.days));181}182183@Override184public ChronoPeriod minus(TemporalAmount amountToSubtract) {185ChronoPeriodImpl amount = validateAmount(amountToSubtract);186return new ChronoPeriodImpl(187chrono,188Math.subtractExact(years, amount.years),189Math.subtractExact(months, amount.months),190Math.subtractExact(days, amount.days));191}192193/**194* Obtains an instance of {@code ChronoPeriodImpl} from a temporal amount.195*196* @param amount the temporal amount to convert, not null197* @return the period, not null198*/199private ChronoPeriodImpl validateAmount(TemporalAmount amount) {200Objects.requireNonNull(amount, "amount");201if (!(amount instanceof ChronoPeriodImpl period)) {202throw new DateTimeException("Unable to obtain ChronoPeriod from TemporalAmount: " + amount.getClass());203}204if (!(chrono.equals(period.getChronology()))) {205throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + period.getChronology().getId());206}207return period;208}209210//-----------------------------------------------------------------------211@Override212public ChronoPeriod multipliedBy(int scalar) {213if (this.isZero() || scalar == 1) {214return this;215}216return new ChronoPeriodImpl(217chrono,218Math.multiplyExact(years, scalar),219Math.multiplyExact(months, scalar),220Math.multiplyExact(days, scalar));221}222223//-----------------------------------------------------------------------224@Override225public ChronoPeriod normalized() {226long monthRange = monthRange();227if (monthRange > 0) {228long totalMonths = years * monthRange + months;229long splitYears = totalMonths / monthRange;230int splitMonths = (int) (totalMonths % monthRange); // no overflow231if (splitYears == years && splitMonths == months) {232return this;233}234return new ChronoPeriodImpl(chrono, Math.toIntExact(splitYears), splitMonths, days);235236}237return this;238}239240/**241* Calculates the range of months.242*243* @return the month range, -1 if not fixed range244*/245private long monthRange() {246ValueRange startRange = chrono.range(MONTH_OF_YEAR);247if (startRange.isFixed() && startRange.isIntValue()) {248return startRange.getMaximum() - startRange.getMinimum() + 1;249}250return -1;251}252253//-------------------------------------------------------------------------254@Override255public Temporal addTo(Temporal temporal) {256validateChrono(temporal);257if (months == 0) {258if (years != 0) {259temporal = temporal.plus(years, YEARS);260}261} else {262long monthRange = monthRange();263if (monthRange > 0) {264temporal = temporal.plus(years * monthRange + months, MONTHS);265} else {266if (years != 0) {267temporal = temporal.plus(years, YEARS);268}269temporal = temporal.plus(months, MONTHS);270}271}272if (days != 0) {273temporal = temporal.plus(days, DAYS);274}275return temporal;276}277278279280@Override281public Temporal subtractFrom(Temporal temporal) {282validateChrono(temporal);283if (months == 0) {284if (years != 0) {285temporal = temporal.minus(years, YEARS);286}287} else {288long monthRange = monthRange();289if (monthRange > 0) {290temporal = temporal.minus(years * monthRange + months, MONTHS);291} else {292if (years != 0) {293temporal = temporal.minus(years, YEARS);294}295temporal = temporal.minus(months, MONTHS);296}297}298if (days != 0) {299temporal = temporal.minus(days, DAYS);300}301return temporal;302}303304/**305* Validates that the temporal has the correct chronology.306*/307private void validateChrono(TemporalAccessor temporal) {308Objects.requireNonNull(temporal, "temporal");309Chronology temporalChrono = temporal.query(TemporalQueries.chronology());310if (temporalChrono != null && chrono.equals(temporalChrono) == false) {311throw new DateTimeException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + temporalChrono.getId());312}313}314315//-----------------------------------------------------------------------316@Override317public boolean equals(Object obj) {318if (this == obj) {319return true;320}321return (obj instanceof ChronoPeriodImpl other)322&& years == other.years && months == other.months323&& days == other.days && chrono.equals(other.chrono);324}325326@Override327public int hashCode() {328return (years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16)) ^ chrono.hashCode();329}330331//-----------------------------------------------------------------------332@Override333public String toString() {334if (isZero()) {335return getChronology().toString() + " P0D";336} else {337StringBuilder buf = new StringBuilder();338buf.append(getChronology().toString()).append(' ').append('P');339if (years != 0) {340buf.append(years).append('Y');341}342if (months != 0) {343buf.append(months).append('M');344}345if (days != 0) {346buf.append(days).append('D');347}348return buf.toString();349}350}351352//-----------------------------------------------------------------------353/**354* Writes the Chronology using a355* <a href="{@docRoot}/serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.356* <pre>357* out.writeByte(12); // identifies this as a ChronoPeriodImpl358* out.writeUTF(getId()); // the chronology359* out.writeInt(years);360* out.writeInt(months);361* out.writeInt(days);362* </pre>363*364* @return the instance of {@code Ser}, not null365*/366@java.io.Serial367protected Object writeReplace() {368return new Ser(Ser.CHRONO_PERIOD_TYPE, this);369}370371/**372* Defend against malicious streams.373*374* @param s the stream to read375* @throws InvalidObjectException always376*/377@java.io.Serial378private void readObject(ObjectInputStream s) throws ObjectStreamException {379throw new InvalidObjectException("Deserialization via serialization delegate");380}381382void writeExternal(DataOutput out) throws IOException {383out.writeUTF(chrono.getId());384out.writeInt(years);385out.writeInt(months);386out.writeInt(days);387}388389static ChronoPeriodImpl readExternal(DataInput in) throws IOException {390Chronology chrono = Chronology.of(in.readUTF());391int years = in.readInt();392int months = in.readInt();393int days = in.readInt();394return new ChronoPeriodImpl(chrono, years, months, days);395}396397}398399400