• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *  * Redistributions of source code must retain the above copyright notice,
10  *    this list of conditions and the following disclaimer.
11  *
12  *  * Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  *  * Neither the name of JSR-310 nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package org.threeten.bp.zone;
33 
34 import java.io.DataInput;
35 import java.io.DataOutput;
36 import java.io.IOException;
37 import java.io.Serializable;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.concurrent.ConcurrentHashMap;
43 import java.util.concurrent.ConcurrentMap;
44 
45 import org.threeten.bp.Duration;
46 import org.threeten.bp.Instant;
47 import org.threeten.bp.LocalDate;
48 import org.threeten.bp.LocalDateTime;
49 import org.threeten.bp.Year;
50 import org.threeten.bp.ZoneOffset;
51 import org.threeten.bp.jdk8.Jdk8Methods;
52 
53 /**
54  * The rules describing how the zone offset varies through the year and historically.
55  * <p>
56  * This class is used by the TZDB time-zone rules.
57  *
58  * <h3>Specification for implementors</h3>
59  * This class is immutable and thread-safe.
60  */
61 final class StandardZoneRules extends ZoneRules implements Serializable {
62 
63     /**
64      * Serialization version.
65      */
66     private static final long serialVersionUID = 3044319355680032515L;
67     /**
68      * The last year to have its transitions cached.
69      */
70     private static final int LAST_CACHED_YEAR = 2100;
71 
72     /**
73      * The transitions between standard offsets (epoch seconds), sorted.
74      */
75     private final long[] standardTransitions;
76     /**
77      * The standard offsets.
78      */
79     private final ZoneOffset[] standardOffsets;
80     /**
81      * The transitions between instants (epoch seconds), sorted.
82      */
83     private final long[] savingsInstantTransitions;
84     /**
85      * The transitions between local date-times, sorted.
86      * This is a paired array, where the first entry is the start of the transition
87      * and the second entry is the end of the transition.
88      */
89     private final LocalDateTime[] savingsLocalTransitions;
90     /**
91      * The wall offsets.
92      */
93     private final ZoneOffset[] wallOffsets;
94     /**
95      * The last rule.
96      */
97     private final ZoneOffsetTransitionRule[] lastRules;
98     /**
99      * The map of recent transitions.
100      */
101     private final ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache =
102                 new ConcurrentHashMap<Integer, ZoneOffsetTransition[]>();
103 
104     /**
105      * Creates an instance.
106      *
107      * @param baseStandardOffset  the standard offset to use before legal rules were set, not null
108      * @param baseWallOffset  the wall offset to use before legal rules were set, not null
109      * @param standardOffsetTransitionList  the list of changes to the standard offset, not null
110      * @param transitionList  the list of transitions, not null
111      * @param lastRules  the recurring last rules, size 15 or less, not null
112      */
StandardZoneRules( ZoneOffset baseStandardOffset, ZoneOffset baseWallOffset, List<ZoneOffsetTransition> standardOffsetTransitionList, List<ZoneOffsetTransition> transitionList, List<ZoneOffsetTransitionRule> lastRules)113     StandardZoneRules(
114             ZoneOffset baseStandardOffset,
115             ZoneOffset baseWallOffset,
116             List<ZoneOffsetTransition> standardOffsetTransitionList,
117             List<ZoneOffsetTransition> transitionList,
118             List<ZoneOffsetTransitionRule> lastRules) {
119         super();
120 
121         // convert standard transitions
122         this.standardTransitions = new long[standardOffsetTransitionList.size()];
123         this.standardOffsets = new ZoneOffset[standardOffsetTransitionList.size() + 1];
124         this.standardOffsets[0] = baseStandardOffset;
125         for (int i = 0; i < standardOffsetTransitionList.size(); i++) {
126             this.standardTransitions[i] = standardOffsetTransitionList.get(i).toEpochSecond();
127             this.standardOffsets[i + 1] = standardOffsetTransitionList.get(i).getOffsetAfter();
128         }
129 
130         // convert savings transitions to locals
131         List<LocalDateTime> localTransitionList = new ArrayList<LocalDateTime>();
132         List<ZoneOffset> localTransitionOffsetList = new ArrayList<ZoneOffset>();
133         localTransitionOffsetList.add(baseWallOffset);
134         for (ZoneOffsetTransition trans : transitionList) {
135             if (trans.isGap()) {
136                 localTransitionList.add(trans.getDateTimeBefore());
137                 localTransitionList.add(trans.getDateTimeAfter());
138             } else {
139                 localTransitionList.add(trans.getDateTimeAfter());
140                 localTransitionList.add(trans.getDateTimeBefore());
141             }
142             localTransitionOffsetList.add(trans.getOffsetAfter());
143         }
144         this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
145         this.wallOffsets = localTransitionOffsetList.toArray(new ZoneOffset[localTransitionOffsetList.size()]);
146 
147         // convert savings transitions to instants
148         this.savingsInstantTransitions = new long[transitionList.size()];
149         for (int i = 0; i < transitionList.size(); i++) {
150             this.savingsInstantTransitions[i] = transitionList.get(i).getInstant().getEpochSecond();
151         }
152 
153         // last rules
154         if (lastRules.size() > 15) {
155             throw new IllegalArgumentException("Too many transition rules");
156         }
157         this.lastRules = lastRules.toArray(new ZoneOffsetTransitionRule[lastRules.size()]);
158     }
159 
160     /**
161      * Constructor.
162      *
163      * @param standardTransitions  the standard transitions, not null
164      * @param standardOffsets  the standard offsets, not null
165      * @param savingsInstantTransitions  the standard transitions, not null
166      * @param wallOffsets  the wall offsets, not null
167      * @param lastRules  the recurring last rules, size 15 or less, not null
168      */
StandardZoneRules( long[] standardTransitions, ZoneOffset[] standardOffsets, long[] savingsInstantTransitions, ZoneOffset[] wallOffsets, ZoneOffsetTransitionRule[] lastRules)169     private StandardZoneRules(
170             long[] standardTransitions,
171             ZoneOffset[] standardOffsets,
172             long[] savingsInstantTransitions,
173             ZoneOffset[] wallOffsets,
174             ZoneOffsetTransitionRule[] lastRules) {
175         super();
176 
177         this.standardTransitions = standardTransitions;
178         this.standardOffsets = standardOffsets;
179         this.savingsInstantTransitions = savingsInstantTransitions;
180         this.wallOffsets = wallOffsets;
181         this.lastRules = lastRules;
182 
183         // convert savings transitions to locals
184         List<LocalDateTime> localTransitionList = new ArrayList<LocalDateTime>();
185         for (int i = 0; i < savingsInstantTransitions.length; i++) {
186             ZoneOffset before = wallOffsets[i];
187             ZoneOffset after = wallOffsets[i + 1];
188             ZoneOffsetTransition trans = new ZoneOffsetTransition(savingsInstantTransitions[i], before, after);
189             if (trans.isGap()) {
190                 localTransitionList.add(trans.getDateTimeBefore());
191                 localTransitionList.add(trans.getDateTimeAfter());
192             } else {
193                 localTransitionList.add(trans.getDateTimeAfter());
194                 localTransitionList.add(trans.getDateTimeBefore());
195             }
196         }
197         this.savingsLocalTransitions = localTransitionList.toArray(new LocalDateTime[localTransitionList.size()]);
198     }
199 
200     //-----------------------------------------------------------------------
201     /**
202      * Uses a serialization delegate.
203      *
204      * @return the replacing object, not null
205      */
writeReplace()206     private Object writeReplace() {
207         return new Ser(Ser.SZR, this);
208     }
209 
210     /**
211      * Writes the state to the stream.
212      *
213      * @param out  the output stream, not null
214      * @throws IOException if an error occurs
215      */
writeExternal(DataOutput out)216     void writeExternal(DataOutput out) throws IOException {
217         out.writeInt(standardTransitions.length);
218         for (long trans : standardTransitions) {
219             Ser.writeEpochSec(trans, out);
220         }
221         for (ZoneOffset offset : standardOffsets) {
222             Ser.writeOffset(offset, out);
223         }
224         out.writeInt(savingsInstantTransitions.length);
225         for (long trans : savingsInstantTransitions) {
226             Ser.writeEpochSec(trans, out);
227         }
228         for (ZoneOffset offset : wallOffsets) {
229             Ser.writeOffset(offset, out);
230         }
231         out.writeByte(lastRules.length);
232         for (ZoneOffsetTransitionRule rule : lastRules) {
233             rule.writeExternal(out);
234         }
235     }
236 
237     /**
238      * Reads the state from the stream.
239      *
240      * @param in  the input stream, not null
241      * @return the created object, not null
242      * @throws IOException if an error occurs
243      */
readExternal(DataInput in)244     static StandardZoneRules readExternal(DataInput in) throws IOException, ClassNotFoundException {
245         int stdSize = in.readInt();
246         long[] stdTrans = new long[stdSize];
247         for (int i = 0; i < stdSize; i++) {
248             stdTrans[i] = Ser.readEpochSec(in);
249         }
250         ZoneOffset[] stdOffsets = new ZoneOffset[stdSize + 1];
251         for (int i = 0; i < stdOffsets.length; i++) {
252             stdOffsets[i] = Ser.readOffset(in);
253         }
254         int savSize = in.readInt();
255         long[] savTrans = new long[savSize];
256         for (int i = 0; i < savSize; i++) {
257             savTrans[i] = Ser.readEpochSec(in);
258         }
259         ZoneOffset[] savOffsets = new ZoneOffset[savSize + 1];
260         for (int i = 0; i < savOffsets.length; i++) {
261             savOffsets[i] = Ser.readOffset(in);
262         }
263         int ruleSize = in.readByte();
264         ZoneOffsetTransitionRule[] rules = new ZoneOffsetTransitionRule[ruleSize];
265         for (int i = 0; i < ruleSize; i++) {
266             rules[i] = ZoneOffsetTransitionRule.readExternal(in);
267         }
268         return new StandardZoneRules(stdTrans, stdOffsets, savTrans, savOffsets, rules);
269     }
270 
271     //-----------------------------------------------------------------------
272     @Override
isFixedOffset()273     public boolean isFixedOffset() {
274         return savingsInstantTransitions.length == 0 && lastRules.length == 0 && wallOffsets[0].equals(standardOffsets[0]);
275     }
276 
277     //-----------------------------------------------------------------------
278     @Override
getOffset(Instant instant)279     public ZoneOffset getOffset(Instant instant) {
280         long epochSec = instant.getEpochSecond();
281 
282         // check if using last rules
283         if (lastRules.length > 0 && (savingsInstantTransitions.length == 0 ||
284                 epochSec > savingsInstantTransitions[savingsInstantTransitions.length - 1])) {
285             int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
286             ZoneOffsetTransition[] transArray = findTransitionArray(year);
287             ZoneOffsetTransition trans = null;
288             for (int i = 0; i < transArray.length; i++) {
289                 trans = transArray[i];
290                 if (epochSec < trans.toEpochSecond()) {
291                     return trans.getOffsetBefore();
292                 }
293             }
294             return trans.getOffsetAfter();
295         }
296 
297         // using historic rules
298         int index  = Arrays.binarySearch(savingsInstantTransitions, epochSec);
299         if (index < 0) {
300             // switch negative insert position to start of matched range
301             index = -index - 2;
302         }
303         return wallOffsets[index + 1];
304     }
305 
306     //-----------------------------------------------------------------------
307     @Override
getOffset(LocalDateTime localDateTime)308     public ZoneOffset getOffset(LocalDateTime localDateTime) {
309         Object info = getOffsetInfo(localDateTime);
310         if (info instanceof ZoneOffsetTransition) {
311             return ((ZoneOffsetTransition) info).getOffsetBefore();
312         }
313         return (ZoneOffset) info;
314     }
315 
316     @Override
getValidOffsets(LocalDateTime localDateTime)317     public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) {
318         // should probably be optimized
319         Object info = getOffsetInfo(localDateTime);
320         if (info instanceof ZoneOffsetTransition) {
321             return ((ZoneOffsetTransition) info).getValidOffsets();
322         }
323         return Collections.singletonList((ZoneOffset) info);
324     }
325 
326     @Override
getTransition(LocalDateTime localDateTime)327     public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) {
328         Object info = getOffsetInfo(localDateTime);
329         return (info instanceof ZoneOffsetTransition ? (ZoneOffsetTransition) info : null);
330     }
331 
getOffsetInfo(LocalDateTime dt)332     private Object getOffsetInfo(LocalDateTime dt) {
333         // check if using last rules
334         if (lastRules.length > 0 && (savingsLocalTransitions.length == 0 ||
335                 dt.isAfter(savingsLocalTransitions[savingsLocalTransitions.length - 1]))) {
336             ZoneOffsetTransition[] transArray = findTransitionArray(dt.getYear());
337             Object info = null;
338             for (ZoneOffsetTransition trans : transArray) {
339                 info = findOffsetInfo(dt, trans);
340                 if (info instanceof ZoneOffsetTransition || info.equals(trans.getOffsetBefore())) {
341                     return info;
342                 }
343             }
344             return info;
345         }
346 
347         // using historic rules
348         int index  = Arrays.binarySearch(savingsLocalTransitions, dt);
349         if (index == -1) {
350             // before first transition
351             return wallOffsets[0];
352         }
353         if (index < 0) {
354             // switch negative insert position to start of matched range
355             index = -index - 2;
356         } else if (index < savingsLocalTransitions.length - 1 &&
357                 savingsLocalTransitions[index].equals(savingsLocalTransitions[index + 1])) {
358             // handle overlap immediately following gap
359             index++;
360         }
361         if ((index & 1) == 0) {
362             // gap or overlap
363             LocalDateTime dtBefore = savingsLocalTransitions[index];
364             LocalDateTime dtAfter = savingsLocalTransitions[index + 1];
365             ZoneOffset offsetBefore = wallOffsets[index / 2];
366             ZoneOffset offsetAfter = wallOffsets[index / 2 + 1];
367             if (offsetAfter.getTotalSeconds() > offsetBefore.getTotalSeconds()) {
368                 // gap
369                 return new ZoneOffsetTransition(dtBefore, offsetBefore, offsetAfter);
370             } else {
371                 // overlap
372                 return new ZoneOffsetTransition(dtAfter, offsetBefore, offsetAfter);
373             }
374         } else {
375             // normal (neither gap or overlap)
376             return wallOffsets[index / 2 + 1];
377         }
378     }
379 
380     /**
381      * Finds the offset info for a local date-time and transition.
382      *
383      * @param dt  the date-time, not null
384      * @param trans  the transition, not null
385      * @return the offset info, not null
386      */
findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans)387     private Object findOffsetInfo(LocalDateTime dt, ZoneOffsetTransition trans) {
388         LocalDateTime localTransition = trans.getDateTimeBefore();
389         if (trans.isGap()) {
390             if (dt.isBefore(localTransition)) {
391                 return trans.getOffsetBefore();
392             }
393             if (dt.isBefore(trans.getDateTimeAfter())) {
394                 return trans;
395             } else {
396                 return trans.getOffsetAfter();
397             }
398         } else {
399             if (dt.isBefore(localTransition) == false) {
400                 return trans.getOffsetAfter();
401             }
402             if (dt.isBefore(trans.getDateTimeAfter())) {
403                 return trans.getOffsetBefore();
404             } else {
405                 return trans;
406             }
407         }
408     }
409 
410     @Override
isValidOffset(LocalDateTime localDateTime, ZoneOffset offset)411     public boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset) {
412         return getValidOffsets(localDateTime).contains(offset);
413     }
414 
415     //-----------------------------------------------------------------------
416     /**
417      * Finds the appropriate transition array for the given year.
418      *
419      * @param year  the year, not null
420      * @return the transition array, not null
421      */
findTransitionArray(int year)422     private ZoneOffsetTransition[] findTransitionArray(int year) {
423         Integer yearObj = year;  // should use Year class, but this saves a class load
424         ZoneOffsetTransition[] transArray = lastRulesCache.get(yearObj);
425         if (transArray != null) {
426             return transArray;
427         }
428         ZoneOffsetTransitionRule[] ruleArray = lastRules;
429         transArray  = new ZoneOffsetTransition[ruleArray.length];
430         for (int i = 0; i < ruleArray.length; i++) {
431             transArray[i] = ruleArray[i].createTransition(year);
432         }
433         if (year < LAST_CACHED_YEAR) {
434             lastRulesCache.putIfAbsent(yearObj, transArray);
435         }
436         return transArray;
437     }
438 
439     //-----------------------------------------------------------------------
440     @Override
getStandardOffset(Instant instant)441     public ZoneOffset getStandardOffset(Instant instant) {
442         long epochSec = instant.getEpochSecond();
443         int index  = Arrays.binarySearch(standardTransitions, epochSec);
444         if (index < 0) {
445             // switch negative insert position to start of matched range
446             index = -index - 2;
447         }
448         return standardOffsets[index + 1];
449     }
450 
451     @Override
getDaylightSavings(Instant instant)452     public Duration getDaylightSavings(Instant instant) {
453         ZoneOffset standardOffset = getStandardOffset(instant);
454         ZoneOffset actualOffset = getOffset(instant);
455         return Duration.ofSeconds(actualOffset.getTotalSeconds() - standardOffset.getTotalSeconds());
456     }
457 
458     @Override
isDaylightSavings(Instant instant)459     public boolean isDaylightSavings(Instant instant) {
460         return (getStandardOffset(instant).equals(getOffset(instant)) == false);
461     }
462 
463     //-----------------------------------------------------------------------
464     @Override
nextTransition(Instant instant)465     public ZoneOffsetTransition nextTransition(Instant instant) {
466         if (savingsInstantTransitions.length == 0) {
467             return null;
468         }
469 
470         long epochSec = instant.getEpochSecond();
471 
472         // check if using last rules
473         if (epochSec >= savingsInstantTransitions[savingsInstantTransitions.length - 1]) {
474             if (lastRules.length == 0) {
475                 return null;
476             }
477             // search year the instant is in
478             int year = findYear(epochSec, wallOffsets[wallOffsets.length - 1]);
479             ZoneOffsetTransition[] transArray = findTransitionArray(year);
480             for (ZoneOffsetTransition trans : transArray) {
481                 if (epochSec < trans.toEpochSecond()) {
482                     return trans;
483                 }
484             }
485             // use first from following year
486             if (year < Year.MAX_VALUE) {
487                 transArray = findTransitionArray(year + 1);
488                 return transArray[0];
489             }
490             return null;
491         }
492 
493         // using historic rules
494         int index  = Arrays.binarySearch(savingsInstantTransitions, epochSec);
495         if (index < 0) {
496             index = -index - 1;  // switched value is the next transition
497         } else {
498             index += 1;  // exact match, so need to add one to get the next
499         }
500         return new ZoneOffsetTransition(savingsInstantTransitions[index], wallOffsets[index], wallOffsets[index + 1]);
501     }
502 
503     @Override
previousTransition(Instant instant)504     public ZoneOffsetTransition previousTransition(Instant instant) {
505         if (savingsInstantTransitions.length == 0) {
506             return null;
507         }
508 
509         long epochSec = instant.getEpochSecond();
510         if (instant.getNano() > 0 && epochSec < Long.MAX_VALUE) {
511             epochSec += 1;  // allow rest of method to only use seconds
512         }
513 
514         // check if using last rules
515         long lastHistoric = savingsInstantTransitions[savingsInstantTransitions.length - 1];
516         if (lastRules.length > 0 && epochSec > lastHistoric) {
517             // search year the instant is in
518             ZoneOffset lastHistoricOffset = wallOffsets[wallOffsets.length - 1];
519             int year = findYear(epochSec, lastHistoricOffset);
520             ZoneOffsetTransition[] transArray = findTransitionArray(year);
521             for (int i = transArray.length - 1; i >= 0; i--) {
522                 if (epochSec > transArray[i].toEpochSecond()) {
523                     return transArray[i];
524                 }
525             }
526             // use last from preceeding year
527             int lastHistoricYear = findYear(lastHistoric, lastHistoricOffset);
528             if (--year > lastHistoricYear) {
529                 transArray = findTransitionArray(year);
530                 return transArray[transArray.length - 1];
531             }
532             // drop through
533         }
534 
535         // using historic rules
536         int index  = Arrays.binarySearch(savingsInstantTransitions, epochSec);
537         if (index < 0) {
538             index = -index - 1;
539         }
540         if (index <= 0) {
541             return null;
542         }
543         return new ZoneOffsetTransition(savingsInstantTransitions[index - 1], wallOffsets[index - 1], wallOffsets[index]);
544     }
545 
findYear(long epochSecond, ZoneOffset offset)546     private int findYear(long epochSecond, ZoneOffset offset) {
547         // inline for performance
548         long localSecond = epochSecond + offset.getTotalSeconds();
549         long localEpochDay = Jdk8Methods.floorDiv(localSecond, 86400);
550         return LocalDate.ofEpochDay(localEpochDay).getYear();
551     }
552 
553     //-------------------------------------------------------------------------
554     @Override
getTransitions()555     public List<ZoneOffsetTransition> getTransitions() {
556         List<ZoneOffsetTransition> list = new ArrayList<ZoneOffsetTransition>();
557         for (int i = 0; i < savingsInstantTransitions.length; i++) {
558             list.add(new ZoneOffsetTransition(savingsInstantTransitions[i], wallOffsets[i], wallOffsets[i + 1]));
559         }
560         return Collections.unmodifiableList(list);
561     }
562 
563     @Override
getTransitionRules()564     public List<ZoneOffsetTransitionRule> getTransitionRules() {
565         return Collections.unmodifiableList(Arrays.asList(lastRules));
566     }
567 
568     //-----------------------------------------------------------------------
569     @Override
equals(Object obj)570     public boolean equals(Object obj) {
571         if (this == obj) {
572            return true;
573         }
574         if (obj instanceof StandardZoneRules) {
575             StandardZoneRules other = (StandardZoneRules) obj;
576             return Arrays.equals(standardTransitions, other.standardTransitions) &&
577                     Arrays.equals(standardOffsets, other.standardOffsets) &&
578                     Arrays.equals(savingsInstantTransitions, other.savingsInstantTransitions) &&
579                     Arrays.equals(wallOffsets, other.wallOffsets) &&
580                     Arrays.equals(lastRules, other.lastRules);
581         }
582         if (obj instanceof Fixed) {
583             return isFixedOffset() && getOffset(Instant.EPOCH).equals(((Fixed) obj).getOffset(Instant.EPOCH));
584         }
585         return false;
586     }
587 
588     @Override
hashCode()589     public int hashCode() {
590         return Arrays.hashCode(standardTransitions) ^
591                 Arrays.hashCode(standardOffsets) ^
592                 Arrays.hashCode(savingsInstantTransitions) ^
593                 Arrays.hashCode(wallOffsets) ^
594                 Arrays.hashCode(lastRules);
595     }
596 
597     //-----------------------------------------------------------------------
598     /**
599      * Returns a string describing this object.
600      *
601      * @return a string for debugging, not null
602      */
603     @Override
toString()604     public String toString() {
605         return "StandardZoneRules[currentStandardOffset=" + standardOffsets[standardOffsets.length - 1] + "]";
606     }
607 
608 }
609