Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/time/ZoneOffset.java
38829 views
/*1* Copyright (c) 2012, 2016, 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*117* <p>118* This is a <a href="{@docRoot}/java/lang/doc-files/ValueBased.html">value-based</a>119* class; use of identity-sensitive operations (including reference equality120* ({@code ==}), identity hash code, or synchronization) on instances of121* {@code ZoneOffset} may have unpredictable results and should be avoided.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*/129public final class ZoneOffset130extends ZoneId131implements TemporalAccessor, TemporalAdjuster, Comparable<ZoneOffset>, Serializable {132133/** Cache of time-zone offset by offset in seconds. */134private static final ConcurrentMap<Integer, ZoneOffset> SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);135/** Cache of time-zone offset by ID. */136private static final ConcurrentMap<String, ZoneOffset> ID_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);137138/**139* The abs maximum seconds.140*/141private static final int MAX_SECONDS = 18 * SECONDS_PER_HOUR;142/**143* Serialization version.144*/145private static final long serialVersionUID = 2357656521762053153L;146147/**148* The time-zone offset for UTC, with an ID of 'Z'.149*/150public static final ZoneOffset UTC = ZoneOffset.ofTotalSeconds(0);151/**152* Constant for the maximum supported offset.153*/154public static final ZoneOffset MIN = ZoneOffset.ofTotalSeconds(-MAX_SECONDS);155/**156* Constant for the maximum supported offset.157*/158public static final ZoneOffset MAX = ZoneOffset.ofTotalSeconds(MAX_SECONDS);159160/**161* The total offset in seconds.162*/163private final int totalSeconds;164/**165* The string form of the time-zone offset.166*/167private final transient String id;168169//-----------------------------------------------------------------------170/**171* Obtains an instance of {@code ZoneOffset} using the ID.172* <p>173* This method parses the string ID of a {@code ZoneOffset} to174* return an instance. The parsing accepts all the formats generated by175* {@link #getId()}, plus some additional formats:176* <ul>177* <li>{@code Z} - for UTC178* <li>{@code +h}179* <li>{@code +hh}180* <li>{@code +hh:mm}181* <li>{@code -hh:mm}182* <li>{@code +hhmm}183* <li>{@code -hhmm}184* <li>{@code +hh:mm:ss}185* <li>{@code -hh:mm:ss}186* <li>{@code +hhmmss}187* <li>{@code -hhmmss}188* </ul>189* Note that ± means either the plus or minus symbol.190* <p>191* The ID of the returned offset will be normalized to one of the formats192* described by {@link #getId()}.193* <p>194* The maximum supported range is from +18:00 to -18:00 inclusive.195*196* @param offsetId the offset ID, not null197* @return the zone-offset, not null198* @throws DateTimeException if the offset ID is invalid199*/200@SuppressWarnings("fallthrough")201public static ZoneOffset of(String offsetId) {202Objects.requireNonNull(offsetId, "offsetId");203// "Z" is always in the cache204ZoneOffset offset = ID_CACHE.get(offsetId);205if (offset != null) {206return offset;207}208209// parse - +h, +hh, +hhmm, +hh:mm, +hhmmss, +hh:mm:ss210final int hours, minutes, seconds;211switch (offsetId.length()) {212case 2:213offsetId = offsetId.charAt(0) + "0" + offsetId.charAt(1); // fallthru214case 3:215hours = parseNumber(offsetId, 1, false);216minutes = 0;217seconds = 0;218break;219case 5:220hours = parseNumber(offsetId, 1, false);221minutes = parseNumber(offsetId, 3, false);222seconds = 0;223break;224case 6:225hours = parseNumber(offsetId, 1, false);226minutes = parseNumber(offsetId, 4, true);227seconds = 0;228break;229case 7:230hours = parseNumber(offsetId, 1, false);231minutes = parseNumber(offsetId, 3, false);232seconds = parseNumber(offsetId, 5, false);233break;234case 9:235hours = parseNumber(offsetId, 1, false);236minutes = parseNumber(offsetId, 4, true);237seconds = parseNumber(offsetId, 7, true);238break;239default:240throw new DateTimeException("Invalid ID for ZoneOffset, invalid format: " + offsetId);241}242char first = offsetId.charAt(0);243if (first != '+' && first != '-') {244throw new DateTimeException("Invalid ID for ZoneOffset, plus/minus not found when expected: " + offsetId);245}246if (first == '-') {247return ofHoursMinutesSeconds(-hours, -minutes, -seconds);248} else {249return ofHoursMinutesSeconds(hours, minutes, seconds);250}251}252253/**254* Parse a two digit zero-prefixed number.255*256* @param offsetId the offset ID, not null257* @param pos the position to parse, valid258* @param precededByColon should this number be prefixed by a precededByColon259* @return the parsed number, from 0 to 99260*/261private static int parseNumber(CharSequence offsetId, int pos, boolean precededByColon) {262if (precededByColon && offsetId.charAt(pos - 1) != ':') {263throw new DateTimeException("Invalid ID for ZoneOffset, colon not found when expected: " + offsetId);264}265char ch1 = offsetId.charAt(pos);266char ch2 = offsetId.charAt(pos + 1);267if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {268throw new DateTimeException("Invalid ID for ZoneOffset, non numeric characters found: " + offsetId);269}270return (ch1 - 48) * 10 + (ch2 - 48);271}272273//-----------------------------------------------------------------------274/**275* Obtains an instance of {@code ZoneOffset} using an offset in hours.276*277* @param hours the time-zone offset in hours, from -18 to +18278* @return the zone-offset, not null279* @throws DateTimeException if the offset is not in the required range280*/281public static ZoneOffset ofHours(int hours) {282return ofHoursMinutesSeconds(hours, 0, 0);283}284285/**286* Obtains an instance of {@code ZoneOffset} using an offset in287* hours and minutes.288* <p>289* The sign of the hours and minutes components must match.290* Thus, if the hours is negative, the minutes must be negative or zero.291* If the hours is zero, the minutes may be positive, negative or zero.292*293* @param hours the time-zone offset in hours, from -18 to +18294* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours295* @return the zone-offset, not null296* @throws DateTimeException if the offset is not in the required range297*/298public static ZoneOffset ofHoursMinutes(int hours, int minutes) {299return ofHoursMinutesSeconds(hours, minutes, 0);300}301302/**303* Obtains an instance of {@code ZoneOffset} using an offset in304* hours, minutes and seconds.305* <p>306* The sign of the hours, minutes and seconds components must match.307* Thus, if the hours is negative, the minutes and seconds must be negative or zero.308*309* @param hours the time-zone offset in hours, from -18 to +18310* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours and seconds311* @param seconds the time-zone offset in seconds, from 0 to ±59, sign matches hours and minutes312* @return the zone-offset, not null313* @throws DateTimeException if the offset is not in the required range314*/315public static ZoneOffset ofHoursMinutesSeconds(int hours, int minutes, int seconds) {316validate(hours, minutes, seconds);317int totalSeconds = totalSeconds(hours, minutes, seconds);318return ofTotalSeconds(totalSeconds);319}320321//-----------------------------------------------------------------------322/**323* Obtains an instance of {@code ZoneOffset} from a temporal object.324* <p>325* This obtains an offset based on the specified temporal.326* A {@code TemporalAccessor} represents an arbitrary set of date and time information,327* which this factory converts to an instance of {@code ZoneOffset}.328* <p>329* A {@code TemporalAccessor} represents some form of date and time information.330* This factory converts the arbitrary temporal object to an instance of {@code ZoneOffset}.331* <p>332* The conversion uses the {@link TemporalQueries#offset()} query, which relies333* on extracting the {@link ChronoField#OFFSET_SECONDS OFFSET_SECONDS} field.334* <p>335* This method matches the signature of the functional interface {@link TemporalQuery}336* allowing it to be used as a query via method reference, {@code ZoneOffset::from}.337*338* @param temporal the temporal object to convert, not null339* @return the zone-offset, not null340* @throws DateTimeException if unable to convert to an {@code ZoneOffset}341*/342public static ZoneOffset from(TemporalAccessor temporal) {343Objects.requireNonNull(temporal, "temporal");344ZoneOffset offset = temporal.query(TemporalQueries.offset());345if (offset == null) {346throw new DateTimeException("Unable to obtain ZoneOffset from TemporalAccessor: " +347temporal + " of type " + temporal.getClass().getName());348}349return offset;350}351352//-----------------------------------------------------------------------353/**354* Validates the offset fields.355*356* @param hours the time-zone offset in hours, from -18 to +18357* @param minutes the time-zone offset in minutes, from 0 to ±59358* @param seconds the time-zone offset in seconds, from 0 to ±59359* @throws DateTimeException if the offset is not in the required range360*/361private static void validate(int hours, int minutes, int seconds) {362if (hours < -18 || hours > 18) {363throw new DateTimeException("Zone offset hours not in valid range: value " + hours +364" is not in the range -18 to 18");365}366if (hours > 0) {367if (minutes < 0 || seconds < 0) {368throw new DateTimeException("Zone offset minutes and seconds must be positive because hours is positive");369}370} else if (hours < 0) {371if (minutes > 0 || seconds > 0) {372throw new DateTimeException("Zone offset minutes and seconds must be negative because hours is negative");373}374} else if ((minutes > 0 && seconds < 0) || (minutes < 0 && seconds > 0)) {375throw new DateTimeException("Zone offset minutes and seconds must have the same sign");376}377if (minutes < -59 || minutes > 59) {378throw new DateTimeException("Zone offset minutes not in valid range: value " +379minutes + " is not in the range -59 to 59");380}381if (seconds < -59 || seconds > 59) {382throw new DateTimeException("Zone offset seconds not in valid range: value " +383seconds + " is not in the range -59 to 59");384}385if (Math.abs(hours) == 18 && (minutes | seconds) != 0) {386throw new DateTimeException("Zone offset not in valid range: -18:00 to +18:00");387}388}389390/**391* Calculates the total offset in seconds.392*393* @param hours the time-zone offset in hours, from -18 to +18394* @param minutes the time-zone offset in minutes, from 0 to ±59, sign matches hours and seconds395* @param seconds the time-zone offset in seconds, from 0 to ±59, sign matches hours and minutes396* @return the total in seconds397*/398private static int totalSeconds(int hours, int minutes, int seconds) {399return hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE + seconds;400}401402//-----------------------------------------------------------------------403/**404* Obtains an instance of {@code ZoneOffset} specifying the total offset in seconds405* <p>406* The offset must be in the range {@code -18:00} to {@code +18:00}, which corresponds to -64800 to +64800.407*408* @param totalSeconds the total time-zone offset in seconds, from -64800 to +64800409* @return the ZoneOffset, not null410* @throws DateTimeException if the offset is not in the required range411*/412public static ZoneOffset ofTotalSeconds(int totalSeconds) {413if (totalSeconds < -MAX_SECONDS || totalSeconds > MAX_SECONDS) {414throw new DateTimeException("Zone offset not in valid range: -18:00 to +18:00");415}416if (totalSeconds % (15 * SECONDS_PER_MINUTE) == 0) {417Integer totalSecs = totalSeconds;418ZoneOffset result = SECONDS_CACHE.get(totalSecs);419if (result == null) {420result = new ZoneOffset(totalSeconds);421SECONDS_CACHE.putIfAbsent(totalSecs, result);422result = SECONDS_CACHE.get(totalSecs);423ID_CACHE.putIfAbsent(result.getId(), result);424}425return result;426} else {427return new ZoneOffset(totalSeconds);428}429}430431//-----------------------------------------------------------------------432/**433* Constructor.434*435* @param totalSeconds the total time-zone offset in seconds, from -64800 to +64800436*/437private ZoneOffset(int totalSeconds) {438super();439this.totalSeconds = totalSeconds;440id = buildId(totalSeconds);441}442443private static String buildId(int totalSeconds) {444if (totalSeconds == 0) {445return "Z";446} else {447int absTotalSeconds = Math.abs(totalSeconds);448StringBuilder buf = new StringBuilder();449int absHours = absTotalSeconds / SECONDS_PER_HOUR;450int absMinutes = (absTotalSeconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;451buf.append(totalSeconds < 0 ? "-" : "+")452.append(absHours < 10 ? "0" : "").append(absHours)453.append(absMinutes < 10 ? ":0" : ":").append(absMinutes);454int absSeconds = absTotalSeconds % SECONDS_PER_MINUTE;455if (absSeconds != 0) {456buf.append(absSeconds < 10 ? ":0" : ":").append(absSeconds);457}458return buf.toString();459}460}461462//-----------------------------------------------------------------------463/**464* Gets the total zone offset in seconds.465* <p>466* This is the primary way to access the offset amount.467* It returns the total of the hours, minutes and seconds fields as a468* single offset that can be added to a time.469*470* @return the total zone offset amount in seconds471*/472public int getTotalSeconds() {473return totalSeconds;474}475476/**477* Gets the normalized zone offset ID.478* <p>479* The ID is minor variation to the standard ISO-8601 formatted string480* for the offset. There are three formats:481* <ul>482* <li>{@code Z} - for UTC (ISO-8601)483* <li>{@code +hh:mm} or {@code -hh:mm} - if the seconds are zero (ISO-8601)484* <li>{@code +hh:mm:ss} or {@code -hh:mm:ss} - if the seconds are non-zero (not ISO-8601)485* </ul>486*487* @return the zone offset ID, not null488*/489@Override490public String getId() {491return id;492}493494/**495* Gets the associated time-zone rules.496* <p>497* The rules will always return this offset when queried.498* The implementation class is immutable, thread-safe and serializable.499*500* @return the rules, not null501*/502@Override503public ZoneRules getRules() {504return ZoneRules.of(this);505}506507//-----------------------------------------------------------------------508/**509* Checks if the specified field is supported.510* <p>511* This checks if this offset can be queried for the specified field.512* If false, then calling the {@link #range(TemporalField) range} and513* {@link #get(TemporalField) get} methods will throw an exception.514* <p>515* If the field is a {@link ChronoField} then the query is implemented here.516* The {@code OFFSET_SECONDS} field returns true.517* All other {@code ChronoField} instances will return false.518* <p>519* If the field is not a {@code ChronoField}, then the result of this method520* is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)}521* passing {@code this} as the argument.522* Whether the field is supported is determined by the field.523*524* @param field the field to check, null returns false525* @return true if the field is supported on this offset, false if not526*/527@Override528public boolean isSupported(TemporalField field) {529if (field instanceof ChronoField) {530return field == OFFSET_SECONDS;531}532return field != null && field.isSupportedBy(this);533}534535/**536* Gets the range of valid values for the specified field.537* <p>538* The range object expresses the minimum and maximum valid values for a field.539* This offset is used to enhance the accuracy of the returned range.540* If it is not possible to return the range, because the field is not supported541* or for some other reason, an exception is thrown.542* <p>543* If the field is a {@link ChronoField} then the query is implemented here.544* The {@link #isSupported(TemporalField) supported fields} will return545* appropriate range instances.546* All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.547* <p>548* If the field is not a {@code ChronoField}, then the result of this method549* is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)}550* passing {@code this} as the argument.551* Whether the range can be obtained is determined by the field.552*553* @param field the field to query the range for, not null554* @return the range of valid values for the field, not null555* @throws DateTimeException if the range for the field cannot be obtained556* @throws UnsupportedTemporalTypeException if the field is not supported557*/558@Override // override for Javadoc559public ValueRange range(TemporalField field) {560return TemporalAccessor.super.range(field);561}562563/**564* Gets the value of the specified field from this offset as an {@code int}.565* <p>566* This queries this offset for the value of the specified field.567* The returned value will always be within the valid range of values for the field.568* If it is not possible to return the value, because the field is not supported569* or for some other reason, an exception is thrown.570* <p>571* If the field is a {@link ChronoField} then the query is implemented here.572* The {@code OFFSET_SECONDS} field returns the value of the offset.573* All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.574* <p>575* If the field is not a {@code ChronoField}, then the result of this method576* is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}577* passing {@code this} as the argument. Whether the value can be obtained,578* and what the value represents, is determined by the field.579*580* @param field the field to get, not null581* @return the value for the field582* @throws DateTimeException if a value for the field cannot be obtained or583* the value is outside the range of valid values for the field584* @throws UnsupportedTemporalTypeException if the field is not supported or585* the range of values exceeds an {@code int}586* @throws ArithmeticException if numeric overflow occurs587*/588@Override // override for Javadoc and performance589public int get(TemporalField field) {590if (field == OFFSET_SECONDS) {591return totalSeconds;592} else if (field instanceof ChronoField) {593throw new UnsupportedTemporalTypeException("Unsupported field: " + field);594}595return range(field).checkValidIntValue(getLong(field), field);596}597598/**599* Gets the value of the specified field from this offset as a {@code long}.600* <p>601* This queries this offset for the value of the specified field.602* If it is not possible to return the value, because the field is not supported603* or for some other reason, an exception is thrown.604* <p>605* If the field is a {@link ChronoField} then the query is implemented here.606* The {@code OFFSET_SECONDS} field returns the value of the offset.607* All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}.608* <p>609* If the field is not a {@code ChronoField}, then the result of this method610* is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}611* passing {@code this} as the argument. Whether the value can be obtained,612* and what the value represents, is determined by the field.613*614* @param field the field to get, not null615* @return the value for the field616* @throws DateTimeException if a value for the field cannot be obtained617* @throws UnsupportedTemporalTypeException if the field is not supported618* @throws ArithmeticException if numeric overflow occurs619*/620@Override621public long getLong(TemporalField field) {622if (field == OFFSET_SECONDS) {623return totalSeconds;624} else if (field instanceof ChronoField) {625throw new UnsupportedTemporalTypeException("Unsupported field: " + field);626}627return field.getFrom(this);628}629630//-----------------------------------------------------------------------631/**632* Queries this offset using the specified query.633* <p>634* This queries this offset using the specified query strategy object.635* The {@code TemporalQuery} object defines the logic to be used to636* obtain the result. Read the documentation of the query to understand637* what the result of this method will be.638* <p>639* The result of this method is obtained by invoking the640* {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the641* specified query passing {@code this} as the argument.642*643* @param <R> the type of the result644* @param query the query to invoke, not null645* @return the query result, null may be returned (defined by the query)646* @throws DateTimeException if unable to query (defined by the query)647* @throws ArithmeticException if numeric overflow occurs (defined by the query)648*/649@SuppressWarnings("unchecked")650@Override651public <R> R query(TemporalQuery<R> query) {652if (query == TemporalQueries.offset() || query == TemporalQueries.zone()) {653return (R) this;654}655return TemporalAccessor.super.query(query);656}657658/**659* Adjusts the specified temporal object to have the same offset as this object.660* <p>661* This returns a temporal object of the same observable type as the input662* with the offset changed to be the same as this.663* <p>664* The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)}665* passing {@link ChronoField#OFFSET_SECONDS} as the field.666* <p>667* In most cases, it is clearer to reverse the calling pattern by using668* {@link Temporal#with(TemporalAdjuster)}:669* <pre>670* // these two lines are equivalent, but the second approach is recommended671* temporal = thisOffset.adjustInto(temporal);672* temporal = temporal.with(thisOffset);673* </pre>674* <p>675* This instance is immutable and unaffected by this method call.676*677* @param temporal the target object to be adjusted, not null678* @return the adjusted object, not null679* @throws DateTimeException if unable to make the adjustment680* @throws ArithmeticException if numeric overflow occurs681*/682@Override683public Temporal adjustInto(Temporal temporal) {684return temporal.with(OFFSET_SECONDS, totalSeconds);685}686687//-----------------------------------------------------------------------688/**689* Compares this offset to another offset in descending order.690* <p>691* The offsets are compared in the order that they occur for the same time692* of day around the world. Thus, an offset of {@code +10:00} comes before an693* offset of {@code +09:00} and so on down to {@code -18:00}.694* <p>695* The comparison is "consistent with equals", as defined by {@link Comparable}.696*697* @param other the other date to compare to, not null698* @return the comparator value, negative if less, positive if greater699* @throws NullPointerException if {@code other} is null700*/701@Override702public int compareTo(ZoneOffset other) {703// abs(totalSeconds) <= MAX_SECONDS, so no overflow can happen here704return other.totalSeconds - totalSeconds;705}706707//-----------------------------------------------------------------------708/**709* Checks if this offset is equal to another offset.710* <p>711* The comparison is based on the amount of the offset in seconds.712* This is equivalent to a comparison by ID.713*714* @param obj the object to check, null returns false715* @return true if this is equal to the other offset716*/717@Override718public boolean equals(Object obj) {719if (this == obj) {720return true;721}722if (obj instanceof ZoneOffset) {723return totalSeconds == ((ZoneOffset) obj).totalSeconds;724}725return false;726}727728/**729* A hash code for this offset.730*731* @return a suitable hash code732*/733@Override734public int hashCode() {735return totalSeconds;736}737738//-----------------------------------------------------------------------739/**740* Outputs this offset as a {@code String}, using the normalized ID.741*742* @return a string representation of this offset, not null743*/744@Override745public String toString() {746return id;747}748749// -----------------------------------------------------------------------750/**751* Writes the object using a752* <a href="../../serialized-form.html#java.time.Ser">dedicated serialized form</a>.753* @serialData754* <pre>755* out.writeByte(8); // identifies a ZoneOffset756* int offsetByte = totalSeconds % 900 == 0 ? totalSeconds / 900 : 127;757* out.writeByte(offsetByte);758* if (offsetByte == 127) {759* out.writeInt(totalSeconds);760* }761* </pre>762*763* @return the instance of {@code Ser}, not null764*/765private Object writeReplace() {766return new Ser(Ser.ZONE_OFFSET_TYPE, this);767}768769/**770* Defend against malicious streams.771*772* @param s the stream to read773* @throws InvalidObjectException always774*/775private void readObject(ObjectInputStream s) throws InvalidObjectException {776throw new InvalidObjectException("Deserialization via serialization delegate");777}778779@Override780void write(DataOutput out) throws IOException {781out.writeByte(Ser.ZONE_OFFSET_TYPE);782writeExternal(out);783}784785void writeExternal(DataOutput out) throws IOException {786final int offsetSecs = totalSeconds;787int offsetByte = offsetSecs % 900 == 0 ? offsetSecs / 900 : 127; // compress to -72 to +72788out.writeByte(offsetByte);789if (offsetByte == 127) {790out.writeInt(offsetSecs);791}792}793794static ZoneOffset readExternal(DataInput in) throws IOException {795int offsetByte = in.readByte();796return (offsetByte == 127 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(offsetByte * 900));797}798799}800801802