Path: blob/master/src/java.base/share/classes/java/time/ZoneOffset.java
41152 views
/*1* Copyright (c) 2012, 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* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file:30*31* Copyright (c) 2007-2012, Stephen Colebourne & Michael Nascimento Santos32*33* All rights reserved.34*35* Redistribution and use in source and binary forms, with or without36* modification, are permitted provided that the following conditions are met:37*38* * Redistributions of source code must retain the above copyright notice,39* this list of conditions and the following disclaimer.40*41* * Redistributions in binary form must reproduce the above copyright notice,42* this list of conditions and the following disclaimer in the documentation43* and/or other materials provided with the distribution.44*45* * Neither the name of JSR-310 nor the names of its contributors46* may be used to endorse or promote products derived from this software47* without specific prior written permission.48*49* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS50* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT51* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR52* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR53* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,54* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,55* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR56* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF57* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING58* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS59* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.60*/61package java.time;6263import static java.time.LocalTime.MINUTES_PER_HOUR;64import static java.time.LocalTime.SECONDS_PER_HOUR;65import static java.time.LocalTime.SECONDS_PER_MINUTE;66import static java.time.temporal.ChronoField.OFFSET_SECONDS;6768import java.io.DataInput;69import java.io.DataOutput;70import java.io.IOException;71import java.io.InvalidObjectException;72import java.io.ObjectInputStream;73import java.io.Serializable;74import java.time.temporal.ChronoField;75import java.time.temporal.Temporal;76import java.time.temporal.TemporalAccessor;77import java.time.temporal.TemporalAdjuster;78import java.time.temporal.TemporalField;79import java.time.temporal.TemporalQueries;80import java.time.temporal.TemporalQuery;81import java.time.temporal.UnsupportedTemporalTypeException;82import java.time.temporal.ValueRange;83import java.time.zone.ZoneRules;84import java.util.Objects;85import java.util.concurrent.ConcurrentHashMap;86import java.util.concurrent.ConcurrentMap;8788/**89* A time-zone offset from Greenwich/UTC, such as {@code +02:00}.90* <p>91* A time-zone offset is the amount of time that a time-zone differs from Greenwich/UTC.92* This is usually a fixed number of hours and minutes.93* <p>94* Different parts of the world have different time-zone offsets.95* The rules for how offsets vary by place and time of year are captured in the96* {@link ZoneId} class.97* <p>98* For example, Paris is one hour ahead of Greenwich/UTC in winter and two hours99* ahead in summer. The {@code ZoneId} instance for Paris will reference two100* {@code ZoneOffset} instances - a {@code +01:00} instance for winter,101* and a {@code +02:00} instance for summer.102* <p>103* In 2008, time-zone offsets around the world extended from -12:00 to +14:00.104* To prevent any problems with that range being extended, yet still provide105* validation, the range of offsets is restricted to -18:00 to 18:00 inclusive.106* <p>107* This class is designed for use with the ISO calendar system.108* The fields of hours, minutes and seconds make assumptions that are valid for the109* standard ISO definitions of those fields. This class may be used with other110* calendar systems providing the definition of the time fields matches those111* of the ISO calendar system.112* <p>113* Instances of {@code ZoneOffset} must be compared using {@link #equals}.114* Implementations may choose to cache certain common offsets, however115* applications must not rely on such caching.116* <p>117* This is a <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a>118* class; programmers should treat instances that are119* {@linkplain #equals(Object) equal} as interchangeable and should not120* use instances for synchronization, or unpredictable behavior may121* occur. For example, in a future release, synchronization may fail.122* The {@code equals} method should be used for comparisons.123*124* @implSpec125* This class is immutable and thread-safe.126*127* @since 1.8128*/129@jdk.internal.ValueBased130public final class ZoneOffset131extends ZoneId132implements TemporalAccessor, TemporalAdjuster, Comparable<ZoneOffset>, Serializable {133134/** Cache of time-zone offset by offset in seconds. */135private static final ConcurrentMap<Integer, ZoneOffset> SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);136/** Cache of time-zone offset by ID. */137private static final ConcurrentMap<String, ZoneOffset> ID_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);138139/**140* The abs maximum seconds.141*/142private static final int MAX_SECONDS = 18 * SECONDS_PER_HOUR;143/**144* Serialization version.145*/146@java.io.Serial147private static final long serialVersionUID = 2357656521762053153L;148149/**150* The time-zone offset for UTC, with an ID of 'Z'.151*/152public static final ZoneOffset UTC = ZoneOffset.ofTotalSeconds(0);153/**154* Constant for the minimum supported offset.155*/156public static final ZoneOffset MIN = ZoneOffset.ofTotalSeconds(-MAX_SECONDS);157/**158* Constant for the maximum supported offset.159*/160public static final ZoneOffset MAX = ZoneOffset.ofTotalSeconds(MAX_SECONDS);161162/**163* The total offset in seconds.164*/165private final int totalSeconds;166/**167* The string form of the time-zone offset.168*/169private final transient String id;170171//-----------------------------------------------------------------------172/**173* Obtains an instance of {@code ZoneOffset} using the ID.174* <p>175* This method parses the string ID of a {@code ZoneOffset} to176* return an instance. The parsing accepts all the formats generated by177* {@link #getId()}, plus some additional formats:178* <ul>179* <li>{@code Z} - for UTC180* <li>{@code +h}181* <li>{@code +hh}182* <li>{@code +hh:mm}183* <li>{@code -hh:mm}184* <li>{@code +hhmm}185* <li>{@code -hhmm}186* <li>{@code +hh:mm:ss}187* <li>{@code -hh:mm:ss}188* <li>{@code +hhmmss}189* <li>{@code -hhmmss}190* </ul>191* Note that ± means either the plus or minus symbol.192* <p>193* The ID of the returned offset will be normalized to one of the formats194* described by {@link #getId()}.195* <p>196* The maximum supported range is from +18:00 to -18:00 inclusive.197*198* @param offsetId the offset ID, not null199* @return the zone-offset, not null200* @throws DateTimeException if the offset ID is invalid201*/202@SuppressWarnings("fallthrough")203public static ZoneOffset of(String offsetId) {204Objects.requireNonNull(offsetId, "offsetId");205// "Z" is always in the cache206ZoneOffset offset = ID_CACHE.get(offsetId);207if (offset != null) {208return offset;209}210211// parse - +h, +hh, +hhmm, +hh:mm, +hhmmss, +hh:mm:ss212final int hours, minutes, seconds;213switch (offsetId.length()) {214case 2:215offsetId = offsetId.charAt(0) + "0" + offsetId.charAt(1); // fallthru216case 3:217hours = parseNumber(offsetId, 1, false);218minutes = 0;219seconds = 0;220break;221case 5:222hours = parseNumber(offsetId, 1, false);223minutes = parseNumber(offsetId, 3, false);224seconds = 0;225break;226case 6:227hours = parseNumber(offsetId, 1, false);228minutes = parseNumber(offsetId, 4, true);229seconds = 0;230break;231case 7:232hours = parseNumber(offsetId, 1, false);233minutes = parseNumber(offsetId, 3, false);234seconds = parseNumber(offsetId, 5, false);235break;236case 9:237hours = parseNumber(offsetId, 1, false);238minutes = parseNumber(offsetId, 4, true);239seconds = parseNumber(offsetId, 7, true);240break;241default:242throw new DateTimeException("Invalid ID for ZoneOffset, invalid format: " + offsetId);243}244char first = offsetId.charAt(0);245if (first != '+' && first != '-') {246throw new DateTimeException("Invalid ID for ZoneOffset, plus/minus not found when expected: " + offsetId);247}248if (first == '-') {249return ofHoursMinutesSeconds(-hours, -minutes, -seconds);250} else {251return ofHoursMinutesSeconds(hours, minutes, seconds);252}253}254255/**256* Parse a two digit zero-prefixed number.257*258* @param offsetId the offset ID, not null259* @param pos the position to parse, valid260* @param precededByColon should this number be prefixed by a precededByColon261* @return the parsed number, from 0 to 99262*/263private static int parseNumber(CharSequence offsetId, int pos, boolean precededByColon) {264if (precededByColon && offsetId.charAt(pos - 1) != ':') {265throw new DateTimeException("Invalid ID for ZoneOffset, colon not found when expected: " + offsetId);266}267char ch1 = offsetId.charAt(pos);268char ch2 = offsetId.charAt(pos + 1);269if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {270throw new DateTimeException("Invalid ID for ZoneOffset, non numeric characters found: " + offsetId);271}272return (ch1 - 48) * 10 + (ch2 - 48);273}274275//-----------------------------------------------------------------------276/**277* Obtains an instance of {@code ZoneOffset} using an offset in hours.278*279* @param hours the time-zone offset in hours, from -18 to +18280* @return the zone-offset, not null281* @throws DateTimeException if the offset is not in the required range282*/283public static ZoneOffset ofHours(int hours) {284return ofHoursMinutesSeconds(hours, 0, 0);285}286287/**288* Obtains an instance of {@code ZoneOffset} using an offset in289* hours and minutes.290* <p>291* The sign of the hours and minutes components must match.292* Thus, if the hours is negative, the minutes must be negative or zero.293* If the hours is zero, the minutes may be positive, negative or zero.294*295* @param hours the time-zone offset in hours, from -18 to +18296* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours297* @return the zone-offset, not null298* @throws DateTimeException if the offset is not in the required range299*/300public static ZoneOffset ofHoursMinutes(int hours, int minutes) {301return ofHoursMinutesSeconds(hours, minutes, 0);302}303304/**305* Obtains an instance of {@code ZoneOffset} using an offset in306* hours, minutes and seconds.307* <p>308* The sign of the hours, minutes and seconds components must match.309* Thus, if the hours is negative, the minutes and seconds must be negative or zero.310*311* @param hours the time-zone offset in hours, from -18 to +18312* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours and seconds313* @param seconds the time-zone offset in seconds, from 0 to ±59, sign matches hours and minutes314* @return the zone-offset, not null315* @throws DateTimeException if the offset is not in the required range316*/317public static ZoneOffset ofHoursMinutesSeconds(int hours, int minutes, int seconds) {318validate(hours, minutes, seconds);319int totalSeconds = totalSeconds(hours, minutes, seconds);320return ofTotalSeconds(totalSeconds);321}322323//-----------------------------------------------------------------------324/**325* Obtains an instance of {@code ZoneOffset} from a temporal object.326* <p>327* This obtains an offset based on the specified temporal.328* A {@code TemporalAccessor} represents an arbitrary set of date and time information,329* which this factory converts to an instance of {@code ZoneOffset}.330* <p>331* A {@code TemporalAccessor} represents some form of date and time information.332* This factory converts the arbitrary temporal object to an instance of {@code ZoneOffset}.333* <p>334* The conversion uses the {@link TemporalQueries#offset()} query, which relies335* on extracting the {@link ChronoField#OFFSET_SECONDS OFFSET_SECONDS} field.336* <p>337* This method matches the signature of the functional interface {@link TemporalQuery}338* allowing it to be used as a query via method reference, {@code ZoneOffset::from}.339*340* @param temporal the temporal object to convert, not null341* @return the zone-offset, not null342* @throws DateTimeException if unable to convert to an {@code ZoneOffset}343*/344public static ZoneOffset from(TemporalAccessor temporal) {345Objects.requireNonNull(temporal, "temporal");346ZoneOffset offset = temporal.query(TemporalQueries.offset());347if (offset == null) {348throw new DateTimeException("Unable to obtain ZoneOffset from TemporalAccessor: " +349temporal + " of type " + temporal.getClass().getName());350}351return offset;352}353354//-----------------------------------------------------------------------355/**356* Validates the offset fields.357*358* @param hours the time-zone offset in hours, from -18 to +18359* @param minutes the time-zone offset in minutes, from 0 to ±59360* @param seconds the time-zone offset in seconds, from 0 to ±59361* @throws DateTimeException if the offset is not in the required range362*/363private static void validate(int hours, int minutes, int seconds) {364if (hours < -18 || hours > 18) {365throw new DateTimeException("Zone offset hours not in valid range: value " + hours +366" is not in the range -18 to 18");367}368if (hours > 0) {369if (minutes < 0 || seconds < 0) {370throw new DateTimeException("Zone offset minutes and seconds must be positive because hours is positive");371}372} else if (hours < 0) {373if (minutes > 0 || seconds > 0) {374throw new DateTimeException("Zone offset minutes and seconds must be negative because hours is negative");375}376} else if ((minutes > 0 && seconds < 0) || (minutes < 0 && seconds > 0)) {377throw new DateTimeException("Zone offset minutes and seconds must have the same sign");378}379if (minutes < -59 || minutes > 59) {380throw new DateTimeException("Zone offset minutes not in valid range: value " +381minutes + " is not in the range -59 to 59");382}383if (seconds < -59 || seconds > 59) {384throw new DateTimeException("Zone offset seconds not in valid range: value " +385seconds + " is not in the range -59 to 59");386}387if (Math.abs(hours) == 18 && (minutes | seconds) != 0) {388throw new DateTimeException("Zone offset not in valid range: -18:00 to +18:00");389}390}391392/**393* Calculates the total offset in seconds.394*395* @param hours the time-zone offset in hours, from -18 to +18396* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours and seconds397* @param seconds the time-zone offset in seconds, from 0 to ±59, sign matches hours and minutes398* @return the total in seconds399*/400private static int totalSeconds(int hours, int minutes, int seconds) {401return hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE + seconds;402}403404//-----------------------------------------------------------------------405/**406* Obtains an instance of {@code ZoneOffset} specifying the total offset in seconds407* <p>408* The offset must be in the range {@code -18:00} to {@code +18:00}, which corresponds to -64800 to +64800.409*410* @param totalSeconds the total time-zone offset in seconds, from -64800 to +64800411* @return the ZoneOffset, not null412* @throws DateTimeException if the offset is not in the required range413*/414public static ZoneOffset ofTotalSeconds(int totalSeconds) {415if (totalSeconds < -MAX_SECONDS || totalSeconds > MAX_SECONDS) {416throw new DateTimeException("Zone offset not in valid range: -18:00 to +18:00");417}418if (totalSeconds % (15 * SECONDS_PER_MINUTE) == 0) {419Integer totalSecs = totalSeconds;420ZoneOffset result = SECONDS_CACHE.get(totalSecs);421if (result == null) {422result = new ZoneOffset(totalSeconds);423SECONDS_CACHE.putIfAbsent(totalSecs, result);424result = SECONDS_CACHE.get(totalSecs);425ID_CACHE.putIfAbsent(result.getId(), result);426}427return result;428} else {429return new ZoneOffset(totalSeconds);430}431}432433//-----------------------------------------------------------------------434/**435* Constructor.436*437* @param totalSeconds the total time-zone offset in seconds, from -64800 to +64800438*/439private ZoneOffset(int totalSeconds) {440super();441this.totalSeconds = totalSeconds;442id = buildId(totalSeconds);443}444445private static String buildId(int totalSeconds) {446if (totalSeconds == 0) {447return "Z";448} else {449int absTotalSeconds = Math.abs(totalSeconds);450StringBuilder buf = new StringBuilder();451int absHours = absTotalSeconds / SECONDS_PER_HOUR;452int absMinutes = (absTotalSeconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;453buf.append(totalSeconds < 0 ? "-" : "+")454.append(absHours < 10 ? "0" : "").append(absHours)455.append(absMinutes < 10 ? ":0" : ":").append(absMinutes);456int absSeconds = absTotalSeconds % SECONDS_PER_MINUTE;457if (absSeconds != 0) {458buf.append(absSeconds < 10 ? ":0" : ":").append(absSeconds);459}460return buf.toString();461}462}463464//-----------------------------------------------------------------------465/**466* Gets the total zone offset in seconds.467* <p>468* This is the primary way to access the offset amount.469* It returns the total of the hours, minutes and seconds fields as a470* single offset that can be added to a time.471*472* @return the total zone offset amount in seconds473*/474public int getTotalSeconds() {475return totalSeconds;476}477478/**479* Gets the normalized zone offset ID.480* <p>481* The ID is minor variation to the standard ISO-8601 formatted string482* for the offset. There are three formats:483* <ul>484* <li>{@code Z} - for UTC (ISO-8601)485* <li>{@code +hh:mm} or {@code -hh:mm} - if the seconds are zero (ISO-8601)486* <li>{@code +hh:mm:ss} or {@code -hh:mm:ss} - if the seconds are non-zero (not ISO-8601)487* </ul>488*489* @return the zone offset ID, not null490*/491@Override492public String getId() {493return id;494}495496/**497* Gets the associated time-zone rules.498* <p>499* The rules will always return this offset when queried.500* The implementation class is immutable, thread-safe and serializable.501*502* @return the rules, not null503*/504@Override505public ZoneRules getRules() {506return ZoneRules.of(this);507}508509//-----------------------------------------------------------------------510/**511* Checks if the specified field is supported.512* <p>513* This checks if this offset can be queried for the specified field.514* If false, then calling the {@link #range(TemporalField) range} and515* {@link #get(TemporalField) get} methods will throw an exception.516* <p>517* If the field is a {@link ChronoField} then the query is implemented here.518* The {@code OFFSET_SECONDS} field returns true.519* All other {@code ChronoField} instances will return false.520* <p>521* If the field is not a {@code ChronoField}, then the result of this method522* is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)}523* passing {@code this} as the argument.524* Whether the field is supported is determined by the field.525*526* @param field the field to check, null returns false527* @return true if the field is supported on this offset, false if not528*/529@Override530public boolean isSupported(TemporalField field) {531if (field instanceof ChronoField) {532return field == OFFSET_SECONDS;533}534return field != null && field.isSupportedBy(this);535}536537/**538* Gets the range of valid values for the specified field.539* <p>540* The range object expresses the minimum and maximum valid values for a field.541* This offset is used to enhance the accuracy of the returned range.542* If it is not possible to return the range, because the field is not supported543* or for some other reason, an exception is thrown.544* <p>545* If the field is a {@link ChronoField} then the query is implemented here.546* The {@link #isSupported(TemporalField) supported fields} will return547* appropriate range instances.548* All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.549* <p>550* If the field is not a {@code ChronoField}, then the result of this method551* is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)}552* passing {@code this} as the argument.553* Whether the range can be obtained is determined by the field.554*555* @param field the field to query the range for, not null556* @return the range of valid values for the field, not null557* @throws DateTimeException if the range for the field cannot be obtained558* @throws UnsupportedTemporalTypeException if the field is not supported559*/560@Override // override for Javadoc561public ValueRange range(TemporalField field) {562return TemporalAccessor.super.range(field);563}564565/**566* Gets the value of the specified field from this offset as an {@code int}.567* <p>568* This queries this offset for the value of the specified field.569* The returned value will always be within the valid range of values for the field.570* If it is not possible to return the value, because the field is not supported571* or for some other reason, an exception is thrown.572* <p>573* If the field is a {@link ChronoField} then the query is implemented here.574* The {@code OFFSET_SECONDS} field returns the value of the offset.575* All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.576* <p>577* If the field is not a {@code ChronoField}, then the result of this method578* is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}579* passing {@code this} as the argument. Whether the value can be obtained,580* and what the value represents, is determined by the field.581*582* @param field the field to get, not null583* @return the value for the field584* @throws DateTimeException if a value for the field cannot be obtained or585* the value is outside the range of valid values for the field586* @throws UnsupportedTemporalTypeException if the field is not supported or587* the range of values exceeds an {@code int}588* @throws ArithmeticException if numeric overflow occurs589*/590@Override // override for Javadoc and performance591public int get(TemporalField field) {592if (field == OFFSET_SECONDS) {593return totalSeconds;594} else if (field instanceof ChronoField) {595throw new UnsupportedTemporalTypeException("Unsupported field: " + field);596}597return range(field).checkValidIntValue(getLong(field), field);598}599600/**601* Gets the value of the specified field from this offset as a {@code long}.602* <p>603* This queries this offset for the value of the specified field.604* If it is not possible to return the value, because the field is not supported605* or for some other reason, an exception is thrown.606* <p>607* If the field is a {@link ChronoField} then the query is implemented here.608* The {@code OFFSET_SECONDS} field returns the value of the offset.609* All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.610* <p>611* If the field is not a {@code ChronoField}, then the result of this method612* is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}613* passing {@code this} as the argument. Whether the value can be obtained,614* and what the value represents, is determined by the field.615*616* @param field the field to get, not null617* @return the value for the field618* @throws DateTimeException if a value for the field cannot be obtained619* @throws UnsupportedTemporalTypeException if the field is not supported620* @throws ArithmeticException if numeric overflow occurs621*/622@Override623public long getLong(TemporalField field) {624if (field == OFFSET_SECONDS) {625return totalSeconds;626} else if (field instanceof ChronoField) {627throw new UnsupportedTemporalTypeException("Unsupported field: " + field);628}629return field.getFrom(this);630}631632//-----------------------------------------------------------------------633/**634* Queries this offset using the specified query.635* <p>636* This queries this offset using the specified query strategy object.637* The {@code TemporalQuery} object defines the logic to be used to638* obtain the result. Read the documentation of the query to understand639* what the result of this method will be.640* <p>641* The result of this method is obtained by invoking the642* {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the643* specified query passing {@code this} as the argument.644*645* @param <R> the type of the result646* @param query the query to invoke, not null647* @return the query result, null may be returned (defined by the query)648* @throws DateTimeException if unable to query (defined by the query)649* @throws ArithmeticException if numeric overflow occurs (defined by the query)650*/651@SuppressWarnings("unchecked")652@Override653public <R> R query(TemporalQuery<R> query) {654if (query == TemporalQueries.offset() || query == TemporalQueries.zone()) {655return (R) this;656}657return TemporalAccessor.super.query(query);658}659660/**661* Adjusts the specified temporal object to have the same offset as this object.662* <p>663* This returns a temporal object of the same observable type as the input664* with the offset changed to be the same as this.665* <p>666* The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)}667* passing {@link ChronoField#OFFSET_SECONDS} as the field.668* <p>669* In most cases, it is clearer to reverse the calling pattern by using670* {@link Temporal#with(TemporalAdjuster)}:671* <pre>672* // these two lines are equivalent, but the second approach is recommended673* temporal = thisOffset.adjustInto(temporal);674* temporal = temporal.with(thisOffset);675* </pre>676* <p>677* This instance is immutable and unaffected by this method call.678*679* @param temporal the target object to be adjusted, not null680* @return the adjusted object, not null681* @throws DateTimeException if unable to make the adjustment682* @throws ArithmeticException if numeric overflow occurs683*/684@Override685public Temporal adjustInto(Temporal temporal) {686return temporal.with(OFFSET_SECONDS, totalSeconds);687}688689//-----------------------------------------------------------------------690/**691* Compares this offset to another offset in descending order.692* <p>693* The offsets are compared in the order that they occur for the same time694* of day around the world. Thus, an offset of {@code +10:00} comes before an695* offset of {@code +09:00} and so on down to {@code -18:00}.696* <p>697* The comparison is "consistent with equals", as defined by {@link Comparable}.698*699* @param other the other date to compare to, not null700* @return the comparator value, negative if less, positive if greater701* @throws NullPointerException if {@code other} is null702*/703@Override704public int compareTo(ZoneOffset other) {705// abs(totalSeconds) <= MAX_SECONDS, so no overflow can happen here706return other.totalSeconds - totalSeconds;707}708709//-----------------------------------------------------------------------710/**711* Checks if this offset is equal to another offset.712* <p>713* The comparison is based on the amount of the offset in seconds.714* This is equivalent to a comparison by ID.715*716* @param obj the object to check, null returns false717* @return true if this is equal to the other offset718*/719@Override720public boolean equals(Object obj) {721if (this == obj) {722return true;723}724if (obj instanceof ZoneOffset) {725return totalSeconds == ((ZoneOffset) obj).totalSeconds;726}727return false;728}729730/**731* A hash code for this offset.732*733* @return a suitable hash code734*/735@Override736public int hashCode() {737return totalSeconds;738}739740//-----------------------------------------------------------------------741/**742* Outputs this offset as a {@code String}, using the normalized ID.743*744* @return a string representation of this offset, not null745*/746@Override747public String toString() {748return id;749}750751// -----------------------------------------------------------------------752/**753* Writes the object using a754* <a href="{@docRoot}/serialized-form.html#java.time.Ser">dedicated serialized form</a>.755* @serialData756* <pre>757* out.writeByte(8); // identifies a ZoneOffset758* int offsetByte = totalSeconds % 900 == 0 ? totalSeconds / 900 : 127;759* out.writeByte(offsetByte);760* if (offsetByte == 127) {761* out.writeInt(totalSeconds);762* }763* </pre>764*765* @return the instance of {@code Ser}, not null766*/767@java.io.Serial768private Object writeReplace() {769return new Ser(Ser.ZONE_OFFSET_TYPE, this);770}771772/**773* Defend against malicious streams.774*775* @param s the stream to read776* @throws InvalidObjectException always777*/778@java.io.Serial779private void readObject(ObjectInputStream s) throws InvalidObjectException {780throw new InvalidObjectException("Deserialization via serialization delegate");781}782783@Override784void write(DataOutput out) throws IOException {785out.writeByte(Ser.ZONE_OFFSET_TYPE);786writeExternal(out);787}788789void writeExternal(DataOutput out) throws IOException {790final int offsetSecs = totalSeconds;791int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72792out.writeByte(offsetByte);793if (offsetByte == 127) {794out.writeInt(offsetSecs);795}796}797798static ZoneOffset readExternal(DataInput in) throws IOException {799int offsetByte = in.readByte();800return (offsetByte == 127 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(offsetByte * 900));801}802803}804805806