src/share/classes/java/time/ZoneOffset.java

Print this page




  44  *    and/or other materials provided with the distribution.
  45  *
  46  *  * Neither the name of JSR-310 nor the names of its contributors
  47  *    may be used to endorse or promote products derived from this software
  48  *    without specific prior written permission.
  49  *
  50  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  51  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  52  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  53  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  54  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  55  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  56  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  57  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  58  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  59  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  60  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  61  */
  62 package java.time;
  63 



  64 import static java.time.temporal.ChronoField.OFFSET_SECONDS;
  65 
  66 import java.io.DataInput;
  67 import java.io.DataOutput;
  68 import java.io.IOException;
  69 import java.io.InvalidObjectException;
  70 import java.io.ObjectStreamException;
  71 import java.io.Serializable;
  72 import java.time.temporal.ChronoField;
  73 import java.time.temporal.Queries;
  74 import java.time.temporal.Temporal;
  75 import java.time.temporal.TemporalAccessor;
  76 import java.time.temporal.TemporalAdjuster;
  77 import java.time.temporal.TemporalField;
  78 import java.time.temporal.TemporalQuery;
  79 import java.time.temporal.ValueRange;
  80 import java.time.zone.ZoneRules;
  81 import java.util.Objects;
  82 import java.util.concurrent.ConcurrentHashMap;
  83 import java.util.concurrent.ConcurrentMap;


 109  * <p>
 110  * Instances of {@code ZoneOffset} must be compared using {@link #equals}.
 111  * Implementations may choose to cache certain common offsets, however
 112  * applications must not rely on such caching.
 113  *
 114  * <h3>Specification for implementors</h3>
 115  * This class is immutable and thread-safe.
 116  *
 117  * @since 1.8
 118  */
 119 public final class ZoneOffset
 120         extends ZoneId
 121         implements TemporalAccessor, TemporalAdjuster, Comparable<ZoneOffset>, Serializable {
 122 
 123     /** Cache of time-zone offset by offset in seconds. */
 124     private static final ConcurrentMap<Integer, ZoneOffset> SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);
 125     /** Cache of time-zone offset by ID. */
 126     private static final ConcurrentMap<String, ZoneOffset> ID_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);
 127 
 128     /**
 129      * The number of seconds per hour.
 130      */
 131     private static final int SECONDS_PER_HOUR = 60 * 60;
 132     /**
 133      * The number of seconds per minute.
 134      */
 135     private static final int SECONDS_PER_MINUTE = 60;
 136     /**
 137      * The number of minutes per hour.
 138      */
 139     private static final int MINUTES_PER_HOUR = 60;
 140     /**
 141      * The abs maximum seconds.
 142      */
 143     private static final int MAX_SECONDS = 18 * SECONDS_PER_HOUR;
 144     /**
 145      * Serialization version.
 146      */
 147     private static final long serialVersionUID = 2357656521762053153L;
 148 
 149     /**
 150      * The time-zone offset for UTC, with an ID of 'Z'.
 151      */
 152     public static final ZoneOffset UTC = ZoneOffset.ofTotalSeconds(0);
 153     /**
 154      * Constant for the maximum supported offset.
 155      */
 156     public static final ZoneOffset MIN = ZoneOffset.ofTotalSeconds(-MAX_SECONDS);
 157     /**
 158      * Constant for the maximum supported offset.
 159      */
 160     public static final ZoneOffset MAX = ZoneOffset.ofTotalSeconds(MAX_SECONDS);


 222                 hours = parseNumber(offsetId, 1, false);
 223                 minutes = parseNumber(offsetId, 3, false);
 224                 seconds = 0;
 225                 break;
 226             case 6:
 227                 hours = parseNumber(offsetId, 1, false);
 228                 minutes = parseNumber(offsetId, 4, true);
 229                 seconds = 0;
 230                 break;
 231             case 7:
 232                 hours = parseNumber(offsetId, 1, false);
 233                 minutes = parseNumber(offsetId, 3, false);
 234                 seconds = parseNumber(offsetId, 5, false);
 235                 break;
 236             case 9:
 237                 hours = parseNumber(offsetId, 1, false);
 238                 minutes = parseNumber(offsetId, 4, true);
 239                 seconds = parseNumber(offsetId, 7, true);
 240                 break;
 241             default:
 242                 throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid");
 243         }
 244         char first = offsetId.charAt(0);
 245         if (first != '+' && first != '-') {
 246             throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid: Plus/minus not found when expected");
 247         }
 248         if (first == '-') {
 249             return ofHoursMinutesSeconds(-hours, -minutes, -seconds);
 250         } else {
 251             return ofHoursMinutesSeconds(hours, minutes, seconds);
 252         }
 253     }
 254 
 255     /**
 256      * Parse a two digit zero-prefixed number.
 257      *
 258      * @param offsetId  the offset ID, not null
 259      * @param pos  the position to parse, valid
 260      * @param precededByColon  should this number be prefixed by a precededByColon
 261      * @return the parsed number, from 0 to 99
 262      */
 263     private static int parseNumber(CharSequence offsetId, int pos, boolean precededByColon) {
 264         if (precededByColon && offsetId.charAt(pos - 1) != ':') {
 265             throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid: Colon not found when expected");
 266         }
 267         char ch1 = offsetId.charAt(pos);
 268         char ch2 = offsetId.charAt(pos + 1);
 269         if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
 270             throw new DateTimeException("Zone offset ID '" + offsetId + "' is invalid: Non numeric characters found");
 271         }
 272         return (ch1 - 48) * 10 + (ch2 - 48);
 273     }
 274 
 275     //-----------------------------------------------------------------------
 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 +18
 280      * @return the zone-offset, not null
 281      * @throws DateTimeException if the offset is not in the required range
 282      */
 283     public static ZoneOffset ofHours(int hours) {
 284         return ofHoursMinutesSeconds(hours, 0, 0);
 285     }
 286 
 287     /**
 288      * Obtains an instance of {@code ZoneOffset} using an offset in
 289      * hours and minutes.
 290      * <p>


 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 +18
 312      * @param minutes  the time-zone offset in minutes, from 0 to &plusmn;59, sign matches hours and seconds
 313      * @param seconds  the time-zone offset in seconds, from 0 to &plusmn;59, sign matches hours and minutes
 314      * @return the zone-offset, not null
 315      * @throws DateTimeException if the offset is not in the required range
 316      */
 317     public static ZoneOffset ofHoursMinutesSeconds(int hours, int minutes, int seconds) {
 318         validate(hours, minutes, seconds);
 319         int totalSeconds = totalSeconds(hours, minutes, seconds);
 320         return ofTotalSeconds(totalSeconds);
 321     }
 322 
 323     //-----------------------------------------------------------------------
 324     /**
 325      * Obtains an instance of {@code ZoneOffset} from a temporal object.
 326      * <p>




 327      * A {@code TemporalAccessor} represents some form of date and time information.
 328      * This factory converts the arbitrary temporal object to an instance of {@code ZoneOffset}.
 329      * <p>
 330      * The conversion extracts the {@link ChronoField#OFFSET_SECONDS offset-seconds} field.

 331      * <p>
 332      * This method matches the signature of the functional interface {@link TemporalQuery}
 333      * allowing it to be used in queries via method reference, {@code ZoneOffset::from}.
 334      *
 335      * @param temporal  the temporal object to convert, not null
 336      * @return the zone-offset, not null
 337      * @throws DateTimeException if unable to convert to an {@code ZoneOffset}
 338      */
 339     public static ZoneOffset from(TemporalAccessor temporal) {
 340         if (temporal instanceof ZoneOffset) {
 341             return (ZoneOffset) temporal;
 342         }
 343         try {
 344             return ofTotalSeconds(temporal.get(OFFSET_SECONDS));
 345         } catch (DateTimeException ex) {
 346             throw new DateTimeException("Unable to obtain ZoneOffset from TemporalAccessor: " + temporal.getClass(), ex);
 347         }

 348     }
 349 
 350     //-----------------------------------------------------------------------
 351     /**
 352      * Validates the offset fields.
 353      *
 354      * @param hours  the time-zone offset in hours, from -18 to +18
 355      * @param minutes  the time-zone offset in minutes, from 0 to &plusmn;59
 356      * @param seconds  the time-zone offset in seconds, from 0 to &plusmn;59
 357      * @throws DateTimeException if the offset is not in the required range
 358      */
 359     private static void validate(int hours, int minutes, int seconds) {
 360         if (hours < -18 || hours > 18) {
 361             throw new DateTimeException("Zone offset hours not in valid range: value " + hours +
 362                     " is not in the range -18 to 18");
 363         }
 364         if (hours > 0) {
 365             if (minutes < 0 || seconds < 0) {
 366                 throw new DateTimeException("Zone offset minutes and seconds must be positive because hours is positive");
 367             }


 498      * @return the rules, not null
 499      */
 500     @Override
 501     public ZoneRules getRules() {
 502         return ZoneRules.of(this);
 503     }
 504 
 505     //-----------------------------------------------------------------------
 506     /**
 507      * Checks if the specified field is supported.
 508      * <p>
 509      * This checks if this offset can be queried for the specified field.
 510      * If false, then calling the {@link #range(TemporalField) range} and
 511      * {@link #get(TemporalField) get} methods will throw an exception.
 512      * <p>
 513      * If the field is a {@link ChronoField} then the query is implemented here.
 514      * The {@code OFFSET_SECONDS} field returns true.
 515      * All other {@code ChronoField} instances will return false.
 516      * <p>
 517      * If the field is not a {@code ChronoField}, then the result of this method
 518      * is obtained by invoking {@code TemporalField.doIsSupported(TemporalAccessor)}
 519      * passing {@code this} as the argument.
 520      * Whether the field is supported is determined by the field.
 521      *
 522      * @param field  the field to check, null returns false
 523      * @return true if the field is supported on this offset, false if not
 524      */
 525     @Override
 526     public boolean isSupported(TemporalField field) {
 527         if (field instanceof ChronoField) {
 528             return field == OFFSET_SECONDS;
 529         }
 530         return field != null && field.doIsSupported(this);
 531     }
 532 
 533     /**
 534      * Gets the range of valid values for the specified field.
 535      * <p>
 536      * The range object expresses the minimum and maximum valid values for a field.
 537      * This offset is used to enhance the accuracy of the returned range.
 538      * If it is not possible to return the range, because the field is not supported
 539      * or for some other reason, an exception is thrown.
 540      * <p>
 541      * If the field is a {@link ChronoField} then the query is implemented here.
 542      * The {@link #isSupported(TemporalField) supported fields} will return
 543      * appropriate range instances.
 544      * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
 545      * <p>
 546      * If the field is not a {@code ChronoField}, then the result of this method
 547      * is obtained by invoking {@code TemporalField.doRange(TemporalAccessor)}
 548      * passing {@code this} as the argument.
 549      * Whether the range can be obtained is determined by the field.
 550      *
 551      * @param field  the field to query the range for, not null
 552      * @return the range of valid values for the field, not null
 553      * @throws DateTimeException if the range for the field cannot be obtained
 554      */
 555     @Override  // override for Javadoc
 556     public ValueRange range(TemporalField field) {
 557         return TemporalAccessor.super.range(field);
 558     }
 559 
 560     /**
 561      * Gets the value of the specified field from this offset as an {@code int}.
 562      * <p>
 563      * This queries this offset for the value for the specified field.
 564      * The returned value will always be within the valid range of values for the field.
 565      * If it is not possible to return the value, because the field is not supported
 566      * or for some other reason, an exception is thrown.
 567      * <p>
 568      * If the field is a {@link ChronoField} then the query is implemented here.
 569      * The {@code OFFSET_SECONDS} field returns the value of the offset.
 570      * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
 571      * <p>
 572      * If the field is not a {@code ChronoField}, then the result of this method
 573      * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)}
 574      * passing {@code this} as the argument. Whether the value can be obtained,
 575      * and what the value represents, is determined by the field.
 576      *
 577      * @param field  the field to get, not null
 578      * @return the value for the field
 579      * @throws DateTimeException if a value for the field cannot be obtained
 580      * @throws ArithmeticException if numeric overflow occurs
 581      */
 582     @Override  // override for Javadoc and performance
 583     public int get(TemporalField field) {
 584         if (field == OFFSET_SECONDS) {
 585             return totalSeconds;
 586         } else if (field instanceof ChronoField) {
 587             throw new DateTimeException("Unsupported field: " + field.getName());
 588         }
 589         return range(field).checkValidIntValue(getLong(field), field);
 590     }
 591 
 592     /**
 593      * Gets the value of the specified field from this offset as a {@code long}.
 594      * <p>
 595      * This queries this offset for the value for the specified field.
 596      * If it is not possible to return the value, because the field is not supported
 597      * or for some other reason, an exception is thrown.
 598      * <p>
 599      * If the field is a {@link ChronoField} then the query is implemented here.
 600      * The {@code OFFSET_SECONDS} field returns the value of the offset.
 601      * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
 602      * <p>
 603      * If the field is not a {@code ChronoField}, then the result of this method
 604      * is obtained by invoking {@code TemporalField.doGet(TemporalAccessor)}
 605      * passing {@code this} as the argument. Whether the value can be obtained,
 606      * and what the value represents, is determined by the field.
 607      *
 608      * @param field  the field to get, not null
 609      * @return the value for the field
 610      * @throws DateTimeException if a value for the field cannot be obtained
 611      * @throws ArithmeticException if numeric overflow occurs
 612      */
 613     @Override
 614     public long getLong(TemporalField field) {
 615         if (field == OFFSET_SECONDS) {
 616             return totalSeconds;
 617         } else if (field instanceof ChronoField) {
 618             throw new DateTimeException("Unsupported field: " + field.getName());
 619         }
 620         return field.doGet(this);
 621     }
 622 
 623     //-----------------------------------------------------------------------
 624     /**
 625      * Queries this offset using the specified query.
 626      * <p>
 627      * This queries this offset using the specified query strategy object.
 628      * The {@code TemporalQuery} object defines the logic to be used to
 629      * obtain the result. Read the documentation of the query to understand
 630      * what the result of this method will be.
 631      * <p>
 632      * The result of this method is obtained by invoking the
 633      * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the
 634      * specified query passing {@code this} as the argument.
 635      *
 636      * @param <R> the type of the result
 637      * @param query  the query to invoke, not null
 638      * @return the query result, null may be returned (defined by the query)
 639      * @throws DateTimeException if unable to query (defined by the query)
 640      * @throws ArithmeticException if numeric overflow occurs (defined by the query)




  44  *    and/or other materials provided with the distribution.
  45  *
  46  *  * Neither the name of JSR-310 nor the names of its contributors
  47  *    may be used to endorse or promote products derived from this software
  48  *    without specific prior written permission.
  49  *
  50  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  51  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  52  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  53  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  54  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  55  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  56  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  57  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  58  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  59  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  60  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  61  */
  62 package java.time;
  63 
  64 import static java.time.LocalTime.MINUTES_PER_HOUR;
  65 import static java.time.LocalTime.SECONDS_PER_HOUR;
  66 import static java.time.LocalTime.SECONDS_PER_MINUTE;
  67 import static java.time.temporal.ChronoField.OFFSET_SECONDS;
  68 
  69 import java.io.DataInput;
  70 import java.io.DataOutput;
  71 import java.io.IOException;
  72 import java.io.InvalidObjectException;
  73 import java.io.ObjectStreamException;
  74 import java.io.Serializable;
  75 import java.time.temporal.ChronoField;
  76 import java.time.temporal.Queries;
  77 import java.time.temporal.Temporal;
  78 import java.time.temporal.TemporalAccessor;
  79 import java.time.temporal.TemporalAdjuster;
  80 import java.time.temporal.TemporalField;
  81 import java.time.temporal.TemporalQuery;
  82 import java.time.temporal.ValueRange;
  83 import java.time.zone.ZoneRules;
  84 import java.util.Objects;
  85 import java.util.concurrent.ConcurrentHashMap;
  86 import java.util.concurrent.ConcurrentMap;


 112  * <p>
 113  * Instances of {@code ZoneOffset} must be compared using {@link #equals}.
 114  * Implementations may choose to cache certain common offsets, however
 115  * applications must not rely on such caching.
 116  *
 117  * <h3>Specification for implementors</h3>
 118  * This class is immutable and thread-safe.
 119  *
 120  * @since 1.8
 121  */
 122 public final class ZoneOffset
 123         extends ZoneId
 124         implements TemporalAccessor, TemporalAdjuster, Comparable<ZoneOffset>, Serializable {
 125 
 126     /** Cache of time-zone offset by offset in seconds. */
 127     private static final ConcurrentMap<Integer, ZoneOffset> SECONDS_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);
 128     /** Cache of time-zone offset by ID. */
 129     private static final ConcurrentMap<String, ZoneOffset> ID_CACHE = new ConcurrentHashMap<>(16, 0.75f, 4);
 130 
 131     /**












 132      * The abs maximum seconds.
 133      */
 134     private static final int MAX_SECONDS = 18 * SECONDS_PER_HOUR;
 135     /**
 136      * Serialization version.
 137      */
 138     private static final long serialVersionUID = 2357656521762053153L;
 139 
 140     /**
 141      * The time-zone offset for UTC, with an ID of 'Z'.
 142      */
 143     public static final ZoneOffset UTC = ZoneOffset.ofTotalSeconds(0);
 144     /**
 145      * Constant for the maximum supported offset.
 146      */
 147     public static final ZoneOffset MIN = ZoneOffset.ofTotalSeconds(-MAX_SECONDS);
 148     /**
 149      * Constant for the maximum supported offset.
 150      */
 151     public static final ZoneOffset MAX = ZoneOffset.ofTotalSeconds(MAX_SECONDS);


 213                 hours = parseNumber(offsetId, 1, false);
 214                 minutes = parseNumber(offsetId, 3, false);
 215                 seconds = 0;
 216                 break;
 217             case 6:
 218                 hours = parseNumber(offsetId, 1, false);
 219                 minutes = parseNumber(offsetId, 4, true);
 220                 seconds = 0;
 221                 break;
 222             case 7:
 223                 hours = parseNumber(offsetId, 1, false);
 224                 minutes = parseNumber(offsetId, 3, false);
 225                 seconds = parseNumber(offsetId, 5, false);
 226                 break;
 227             case 9:
 228                 hours = parseNumber(offsetId, 1, false);
 229                 minutes = parseNumber(offsetId, 4, true);
 230                 seconds = parseNumber(offsetId, 7, true);
 231                 break;
 232             default:
 233                 throw new DateTimeException("Invalid ID for ZoneOffset, invalid format: " + offsetId);
 234         }
 235         char first = offsetId.charAt(0);
 236         if (first != '+' && first != '-') {
 237             throw new DateTimeException("Invalid ID for ZoneOffset, plus/minus not found when expected: " + offsetId);
 238         }
 239         if (first == '-') {
 240             return ofHoursMinutesSeconds(-hours, -minutes, -seconds);
 241         } else {
 242             return ofHoursMinutesSeconds(hours, minutes, seconds);
 243         }
 244     }
 245 
 246     /**
 247      * Parse a two digit zero-prefixed number.
 248      *
 249      * @param offsetId  the offset ID, not null
 250      * @param pos  the position to parse, valid
 251      * @param precededByColon  should this number be prefixed by a precededByColon
 252      * @return the parsed number, from 0 to 99
 253      */
 254     private static int parseNumber(CharSequence offsetId, int pos, boolean precededByColon) {
 255         if (precededByColon && offsetId.charAt(pos - 1) != ':') {
 256             throw new DateTimeException("Invalid ID for ZoneOffset, colon not found when expected: " + offsetId);
 257         }
 258         char ch1 = offsetId.charAt(pos);
 259         char ch2 = offsetId.charAt(pos + 1);
 260         if (ch1 < '0' || ch1 > '9' || ch2 < '0' || ch2 > '9') {
 261             throw new DateTimeException("Invalid ID for ZoneOffset, non numeric characters found: " + offsetId);
 262         }
 263         return (ch1 - 48) * 10 + (ch2 - 48);
 264     }
 265 
 266     //-----------------------------------------------------------------------
 267     /**
 268      * Obtains an instance of {@code ZoneOffset} using an offset in hours.
 269      *
 270      * @param hours  the time-zone offset in hours, from -18 to +18
 271      * @return the zone-offset, not null
 272      * @throws DateTimeException if the offset is not in the required range
 273      */
 274     public static ZoneOffset ofHours(int hours) {
 275         return ofHoursMinutesSeconds(hours, 0, 0);
 276     }
 277 
 278     /**
 279      * Obtains an instance of {@code ZoneOffset} using an offset in
 280      * hours and minutes.
 281      * <p>


 298      * <p>
 299      * The sign of the hours, minutes and seconds components must match.
 300      * Thus, if the hours is negative, the minutes and seconds must be negative or zero.
 301      *
 302      * @param hours  the time-zone offset in hours, from -18 to +18
 303      * @param minutes  the time-zone offset in minutes, from 0 to &plusmn;59, sign matches hours and seconds
 304      * @param seconds  the time-zone offset in seconds, from 0 to &plusmn;59, sign matches hours and minutes
 305      * @return the zone-offset, not null
 306      * @throws DateTimeException if the offset is not in the required range
 307      */
 308     public static ZoneOffset ofHoursMinutesSeconds(int hours, int minutes, int seconds) {
 309         validate(hours, minutes, seconds);
 310         int totalSeconds = totalSeconds(hours, minutes, seconds);
 311         return ofTotalSeconds(totalSeconds);
 312     }
 313 
 314     //-----------------------------------------------------------------------
 315     /**
 316      * Obtains an instance of {@code ZoneOffset} from a temporal object.
 317      * <p>
 318      * This obtains an offset based on the specified temporal.
 319      * A {@code TemporalAccessor} represents an arbitrary set of date and time information,
 320      * which this factory converts to an instance of {@code ZoneOffset}.
 321      * <p>
 322      * A {@code TemporalAccessor} represents some form of date and time information.
 323      * This factory converts the arbitrary temporal object to an instance of {@code ZoneOffset}.
 324      * <p>
 325      * The conversion uses the {@link Queries#offset()} query, which relies
 326      * on extracting the {@link ChronoField#OFFSET_SECONDS OFFSET_SECONDS} field.
 327      * <p>
 328      * This method matches the signature of the functional interface {@link TemporalQuery}
 329      * allowing it to be used in queries via method reference, {@code ZoneOffset::from}.
 330      *
 331      * @param temporal  the temporal object to convert, not null
 332      * @return the zone-offset, not null
 333      * @throws DateTimeException if unable to convert to an {@code ZoneOffset}
 334      */
 335     public static ZoneOffset from(TemporalAccessor temporal) {
 336         ZoneOffset offset = temporal.query(Queries.offset());
 337         if (offset == null) {
 338             throw new DateTimeException("Unable to obtain ZoneOffset from TemporalAccessor: " + temporal.getClass());




 339         }
 340         return offset;
 341     }
 342 
 343     //-----------------------------------------------------------------------
 344     /**
 345      * Validates the offset fields.
 346      *
 347      * @param hours  the time-zone offset in hours, from -18 to +18
 348      * @param minutes  the time-zone offset in minutes, from 0 to &plusmn;59
 349      * @param seconds  the time-zone offset in seconds, from 0 to &plusmn;59
 350      * @throws DateTimeException if the offset is not in the required range
 351      */
 352     private static void validate(int hours, int minutes, int seconds) {
 353         if (hours < -18 || hours > 18) {
 354             throw new DateTimeException("Zone offset hours not in valid range: value " + hours +
 355                     " is not in the range -18 to 18");
 356         }
 357         if (hours > 0) {
 358             if (minutes < 0 || seconds < 0) {
 359                 throw new DateTimeException("Zone offset minutes and seconds must be positive because hours is positive");
 360             }


 491      * @return the rules, not null
 492      */
 493     @Override
 494     public ZoneRules getRules() {
 495         return ZoneRules.of(this);
 496     }
 497 
 498     //-----------------------------------------------------------------------
 499     /**
 500      * Checks if the specified field is supported.
 501      * <p>
 502      * This checks if this offset can be queried for the specified field.
 503      * If false, then calling the {@link #range(TemporalField) range} and
 504      * {@link #get(TemporalField) get} methods will throw an exception.
 505      * <p>
 506      * If the field is a {@link ChronoField} then the query is implemented here.
 507      * The {@code OFFSET_SECONDS} field returns true.
 508      * All other {@code ChronoField} instances will return false.
 509      * <p>
 510      * If the field is not a {@code ChronoField}, then the result of this method
 511      * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)}
 512      * passing {@code this} as the argument.
 513      * Whether the field is supported is determined by the field.
 514      *
 515      * @param field  the field to check, null returns false
 516      * @return true if the field is supported on this offset, false if not
 517      */
 518     @Override
 519     public boolean isSupported(TemporalField field) {
 520         if (field instanceof ChronoField) {
 521             return field == OFFSET_SECONDS;
 522         }
 523         return field != null && field.isSupportedBy(this);
 524     }
 525 
 526     /**
 527      * Gets the range of valid values for the specified field.
 528      * <p>
 529      * The range object expresses the minimum and maximum valid values for a field.
 530      * This offset is used to enhance the accuracy of the returned range.
 531      * If it is not possible to return the range, because the field is not supported
 532      * or for some other reason, an exception is thrown.
 533      * <p>
 534      * If the field is a {@link ChronoField} then the query is implemented here.
 535      * The {@link #isSupported(TemporalField) supported fields} will return
 536      * appropriate range instances.
 537      * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
 538      * <p>
 539      * If the field is not a {@code ChronoField}, then the result of this method
 540      * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)}
 541      * passing {@code this} as the argument.
 542      * Whether the range can be obtained is determined by the field.
 543      *
 544      * @param field  the field to query the range for, not null
 545      * @return the range of valid values for the field, not null
 546      * @throws DateTimeException if the range for the field cannot be obtained
 547      */
 548     @Override  // override for Javadoc
 549     public ValueRange range(TemporalField field) {
 550         return TemporalAccessor.super.range(field);
 551     }
 552 
 553     /**
 554      * Gets the value of the specified field from this offset as an {@code int}.
 555      * <p>
 556      * This queries this offset for the value for the specified field.
 557      * The returned value will always be within the valid range of values for the field.
 558      * If it is not possible to return the value, because the field is not supported
 559      * or for some other reason, an exception is thrown.
 560      * <p>
 561      * If the field is a {@link ChronoField} then the query is implemented here.
 562      * The {@code OFFSET_SECONDS} field returns the value of the offset.
 563      * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
 564      * <p>
 565      * If the field is not a {@code ChronoField}, then the result of this method
 566      * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
 567      * passing {@code this} as the argument. Whether the value can be obtained,
 568      * and what the value represents, is determined by the field.
 569      *
 570      * @param field  the field to get, not null
 571      * @return the value for the field
 572      * @throws DateTimeException if a value for the field cannot be obtained
 573      * @throws ArithmeticException if numeric overflow occurs
 574      */
 575     @Override  // override for Javadoc and performance
 576     public int get(TemporalField field) {
 577         if (field == OFFSET_SECONDS) {
 578             return totalSeconds;
 579         } else if (field instanceof ChronoField) {
 580             throw new DateTimeException("Unsupported field: " + field.getName());
 581         }
 582         return range(field).checkValidIntValue(getLong(field), field);
 583     }
 584 
 585     /**
 586      * Gets the value of the specified field from this offset as a {@code long}.
 587      * <p>
 588      * This queries this offset for the value for the specified field.
 589      * If it is not possible to return the value, because the field is not supported
 590      * or for some other reason, an exception is thrown.
 591      * <p>
 592      * If the field is a {@link ChronoField} then the query is implemented here.
 593      * The {@code OFFSET_SECONDS} field returns the value of the offset.
 594      * All other {@code ChronoField} instances will throw a {@code DateTimeException}.
 595      * <p>
 596      * If the field is not a {@code ChronoField}, then the result of this method
 597      * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)}
 598      * passing {@code this} as the argument. Whether the value can be obtained,
 599      * and what the value represents, is determined by the field.
 600      *
 601      * @param field  the field to get, not null
 602      * @return the value for the field
 603      * @throws DateTimeException if a value for the field cannot be obtained
 604      * @throws ArithmeticException if numeric overflow occurs
 605      */
 606     @Override
 607     public long getLong(TemporalField field) {
 608         if (field == OFFSET_SECONDS) {
 609             return totalSeconds;
 610         } else if (field instanceof ChronoField) {
 611             throw new DateTimeException("Unsupported field: " + field.getName());
 612         }
 613         return field.getFrom(this);
 614     }
 615 
 616     //-----------------------------------------------------------------------
 617     /**
 618      * Queries this offset using the specified query.
 619      * <p>
 620      * This queries this offset using the specified query strategy object.
 621      * The {@code TemporalQuery} object defines the logic to be used to
 622      * obtain the result. Read the documentation of the query to understand
 623      * what the result of this method will be.
 624      * <p>
 625      * The result of this method is obtained by invoking the
 626      * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the
 627      * specified query passing {@code this} as the argument.
 628      *
 629      * @param <R> the type of the result
 630      * @param query  the query to invoke, not null
 631      * @return the query result, null may be returned (defined by the query)
 632      * @throws DateTimeException if unable to query (defined by the query)
 633      * @throws ArithmeticException if numeric overflow occurs (defined by the query)