• 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.Serializable;
35 import java.util.Collections;
36 import java.util.List;
37 
38 import org.threeten.bp.Duration;
39 import org.threeten.bp.Instant;
40 import org.threeten.bp.LocalDateTime;
41 import org.threeten.bp.ZoneId;
42 import org.threeten.bp.ZoneOffset;
43 import org.threeten.bp.jdk8.Jdk8Methods;
44 
45 /**
46  * The rules defining how the zone offset varies for a single time-zone.
47  * <p>
48  * The rules model all the historic and future transitions for a time-zone.
49  * {@link ZoneOffsetTransition} is used for known transitions, typically historic.
50  * {@link ZoneOffsetTransitionRule} is used for future transitions that are based
51  * on the result of an algorithm.
52  * <p>
53  * The rules are loaded via {@link ZoneRulesProvider} using a {@link ZoneId}.
54  * The same rules may be shared internally between multiple zone IDs.
55  * <p>
56  * Serializing an instance of {@code ZoneRules} will store the entire set of rules.
57  * It does not store the zone ID as it is not part of the state of this object.
58  * <p>
59  * A rule implementation may or may not store full information about historic
60  * and future transitions, and the information stored is only as accurate as
61  * that supplied to the implementation by the rules provider.
62  * Applications should treat the data provided as representing the best information
63  * available to the implementation of this rule.
64  *
65  * <h3>Specification for implementors</h3>
66  * The supplied implementations of this class are immutable and thread-safe.
67  */
68 public abstract class ZoneRules {
69 
70     /**
71      * Obtains an instance of {@code ZoneRules} with full transition rules.
72      *
73      * @param baseStandardOffset  the standard offset to use before legal rules were set, not null
74      * @param baseWallOffset  the wall offset to use before legal rules were set, not null
75      * @param standardOffsetTransitionList  the list of changes to the standard offset, not null
76      * @param transitionList  the list of transitions, not null
77      * @param lastRules  the recurring last rules, size 16 or less, not null
78      * @return the zone rules, not null
79      */
of(ZoneOffset baseStandardOffset, ZoneOffset baseWallOffset, List<ZoneOffsetTransition> standardOffsetTransitionList, List<ZoneOffsetTransition> transitionList, List<ZoneOffsetTransitionRule> lastRules)80     public static ZoneRules of(ZoneOffset baseStandardOffset,
81                                ZoneOffset baseWallOffset,
82                                List<ZoneOffsetTransition> standardOffsetTransitionList,
83                                List<ZoneOffsetTransition> transitionList,
84                                List<ZoneOffsetTransitionRule> lastRules) {
85         Jdk8Methods.requireNonNull(baseStandardOffset, "baseStandardOffset");
86         Jdk8Methods.requireNonNull(baseWallOffset, "baseWallOffset");
87         Jdk8Methods.requireNonNull(standardOffsetTransitionList, "standardOffsetTransitionList");
88         Jdk8Methods.requireNonNull(transitionList, "transitionList");
89         Jdk8Methods.requireNonNull(lastRules, "lastRules");
90         return new StandardZoneRules(baseStandardOffset, baseWallOffset,
91                              standardOffsetTransitionList, transitionList, lastRules);
92     }
93 
94     /**
95      * Obtains an instance of {@code ZoneRules} that always uses the same offset.
96      * <p>
97      * The returned rules always have the same offset.
98      *
99      * @param offset  the offset, not null
100      * @return the zone rules, not null
101      */
of(ZoneOffset offset)102     public static ZoneRules of(ZoneOffset offset) {
103         Jdk8Methods.requireNonNull(offset, "offset");
104         return new Fixed(offset);
105     }
106 
107     /**
108      * Restricted constructor.
109      */
ZoneRules()110     ZoneRules() {
111     }
112 
113     //-----------------------------------------------------------------------
114     /**
115      * Checks of the zone rules are fixed, such that the offset never varies.
116      *
117      * @return true if the time-zone is fixed and the offset never changes
118      */
isFixedOffset()119     public abstract boolean isFixedOffset();
120 
121     //-----------------------------------------------------------------------
122     /**
123      * Gets the offset applicable at the specified instant in these rules.
124      * <p>
125      * The mapping from an instant to an offset is simple, there is only
126      * one valid offset for each instant.
127      * This method returns that offset.
128      *
129      * @param instant  the instant to find the offset for, not null, but null
130      *  may be ignored if the rules have a single offset for all instants
131      * @return the offset, not null
132      */
getOffset(Instant instant)133     public abstract ZoneOffset getOffset(Instant instant);
134 
135     /**
136      * Gets a suitable offset for the specified local date-time in these rules.
137      * <p>
138      * The mapping from a local date-time to an offset is not straightforward.
139      * There are three cases:
140      * <p><ul>
141      * <li>Normal, with one valid offset. For the vast majority of the year, the normal
142      *  case applies, where there is a single valid offset for the local date-time.</li>
143      * <li>Gap, with zero valid offsets. This is when clocks jump forward typically
144      *  due to the spring daylight savings change from "winter" to "summer".
145      *  In a gap there are local date-time values with no valid offset.</li>
146      * <li>Overlap, with two valid offsets. This is when clocks are set back typically
147      *  due to the autumn daylight savings change from "summer" to "winter".
148      *  In an overlap there are local date-time values with two valid offsets.</li>
149      * </ul><p>
150      * Thus, for any given local date-time there can be zero, one or two valid offsets.
151      * This method returns the single offset in the Normal case, and in the Gap or Overlap
152      * case it returns the offset before the transition.
153      * <p>
154      * Since, in the case of Gap and Overlap, the offset returned is a "best" value, rather
155      * than the "correct" value, it should be treated with care. Applications that care
156      * about the correct offset should use a combination of this method,
157      * {@link #getValidOffsets(LocalDateTime)} and {@link #getTransition(LocalDateTime)}.
158      *
159      * @param localDateTime  the local date-time to query, not null, but null
160      *  may be ignored if the rules have a single offset for all instants
161      * @return the best available offset for the local date-time, not null
162      */
getOffset(LocalDateTime localDateTime)163     public abstract ZoneOffset getOffset(LocalDateTime localDateTime);
164 
165     /**
166      * Gets the offset applicable at the specified local date-time in these rules.
167      * <p>
168      * The mapping from a local date-time to an offset is not straightforward.
169      * There are three cases:
170      * <p><ul>
171      * <li>Normal, with one valid offset. For the vast majority of the year, the normal
172      *  case applies, where there is a single valid offset for the local date-time.</li>
173      * <li>Gap, with zero valid offsets. This is when clocks jump forward typically
174      *  due to the spring daylight savings change from "winter" to "summer".
175      *  In a gap there are local date-time values with no valid offset.</li>
176      * <li>Overlap, with two valid offsets. This is when clocks are set back typically
177      *  due to the autumn daylight savings change from "summer" to "winter".
178      *  In an overlap there are local date-time values with two valid offsets.</li>
179      * </ul><p>
180      * Thus, for any given local date-time there can be zero, one or two valid offsets.
181      * This method returns that list of valid offsets, which is a list of size 0, 1 or 2.
182      * In the case where there are two offsets, the earlier offset is returned at index 0
183      * and the later offset at index 1.
184      * <p>
185      * There are various ways to handle the conversion from a {@code LocalDateTime}.
186      * One technique, using this method, would be:
187      * <pre>
188      *  List<ZoneOffset> validOffsets = rules.getOffset(localDT);
189      *  if (validOffsets.size() == 1) {
190      *    // Normal case: only one valid offset
191      *    zoneOffset = validOffsets.get(0);
192      *  } else {
193      *    // Gap or Overlap: determine what to do from transition (which will be non-null)
194      *    ZoneOffsetTransition trans = rules.getTransition(localDT);
195      *  }
196      * </pre>
197      * <p>
198      * In theory, it is possible for there to be more than two valid offsets.
199      * This would happen if clocks to be put back more than once in quick succession.
200      * This has never happened in the history of time-zones and thus has no special handling.
201      * However, if it were to happen, then the list would return more than 2 entries.
202      *
203      * @param localDateTime  the local date-time to query for valid offsets, not null, but null
204      *  may be ignored if the rules have a single offset for all instants
205      * @return the list of valid offsets, may be immutable, not null
206      */
getValidOffsets(LocalDateTime localDateTime)207     public abstract List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime);
208 
209     /**
210      * Gets the offset transition applicable at the specified local date-time in these rules.
211      * <p>
212      * The mapping from a local date-time to an offset is not straightforward.
213      * There are three cases:
214      * <p><ul>
215      * <li>Normal, with one valid offset. For the vast majority of the year, the normal
216      *  case applies, where there is a single valid offset for the local date-time.</li>
217      * <li>Gap, with zero valid offsets. This is when clocks jump forward typically
218      *  due to the spring daylight savings change from "winter" to "summer".
219      *  In a gap there are local date-time values with no valid offset.</li>
220      * <li>Overlap, with two valid offsets. This is when clocks are set back typically
221      *  due to the autumn daylight savings change from "summer" to "winter".
222      *  In an overlap there are local date-time values with two valid offsets.</li>
223      * </ul><p>
224      * A transition is used to model the cases of a Gap or Overlap.
225      * The Normal case will return null.
226      * <p>
227      * There are various ways to handle the conversion from a {@code LocalDateTime}.
228      * One technique, using this method, would be:
229      * <pre>
230      *  ZoneOffsetTransition trans = rules.getTransition(localDT);
231      *  if (trans == null) {
232      *    // Gap or Overlap: determine what to do from transition
233      *  } else {
234      *    // Normal case: only one valid offset
235      *    zoneOffset = rule.getOffset(localDT);
236      *  }
237      * </pre>
238      *
239      * @param localDateTime  the local date-time to query for offset transition, not null, but null
240      *  may be ignored if the rules have a single offset for all instants
241      * @return the offset transition, null if the local date-time is not in transition
242      */
getTransition(LocalDateTime localDateTime)243     public abstract ZoneOffsetTransition getTransition(LocalDateTime localDateTime);
244 
245     //-----------------------------------------------------------------------
246     /**
247      * Gets the standard offset for the specified instant in this zone.
248      * <p>
249      * This provides access to historic information on how the standard offset
250      * has changed over time.
251      * The standard offset is the offset before any daylight saving time is applied.
252      * This is typically the offset applicable during winter.
253      *
254      * @param instant  the instant to find the offset information for, not null, but null
255      *  may be ignored if the rules have a single offset for all instants
256      * @return the standard offset, not null
257      */
getStandardOffset(Instant instant)258     public abstract ZoneOffset getStandardOffset(Instant instant);
259 
260     /**
261      * Gets the amount of daylight savings in use for the specified instant in this zone.
262      * <p>
263      * This provides access to historic information on how the amount of daylight
264      * savings has changed over time.
265      * This is the difference between the standard offset and the actual offset.
266      * Typically the amount is zero during winter and one hour during summer.
267      * Time-zones are second-based, so the nanosecond part of the duration will be zero.
268      *
269      * @param instant  the instant to find the daylight savings for, not null, but null
270      *  may be ignored if the rules have a single offset for all instants
271      * @return the difference between the standard and actual offset, not null
272      */
getDaylightSavings(Instant instant)273     public abstract Duration getDaylightSavings(Instant instant);
274     //    default {
275     //        ZoneOffset standardOffset = getStandardOffset(instant);
276     //        ZoneOffset actualOffset = getOffset(instant);
277     //        return actualOffset.toDuration().minus(standardOffset.toDuration()).normalized();
278     //    }
279 
280     /**
281      * Checks if the specified instant is in daylight savings.
282      * <p>
283      * This checks if the standard and actual offsets are the same at the specified instant.
284      *
285      * @param instant  the instant to find the offset information for, not null, but null
286      *  may be ignored if the rules have a single offset for all instants
287      * @return the standard offset, not null
288      */
isDaylightSavings(Instant instant)289     public abstract boolean isDaylightSavings(Instant instant);
290     //    default {
291     //        return (getStandardOffset(instant).equals(getOffset(instant)) == false);
292     //    }
293 
294     /**
295      * Checks if the offset date-time is valid for these rules.
296      * <p>
297      * To be valid, the local date-time must not be in a gap and the offset
298      * must match the valid offsets.
299      *
300      * @param localDateTime  the date-time to check, not null, but null
301      *  may be ignored if the rules have a single offset for all instants
302      * @param offset  the offset to check, null returns false
303      * @return true if the offset date-time is valid for these rules
304      */
isValidOffset(LocalDateTime localDateTime, ZoneOffset offset)305     public abstract boolean isValidOffset(LocalDateTime localDateTime, ZoneOffset offset);
306     //    default {
307     //        return getValidOffsets(dateTime).contains(offset);
308     //    }
309 
310     //-----------------------------------------------------------------------
311     /**
312      * Gets the next transition after the specified instant.
313      * <p>
314      * This returns details of the next transition after the specified instant.
315      * For example, if the instant represents a point where "Summer" daylight savings time
316      * applies, then the method will return the transition to the next "Winter" time.
317      *
318      * @param instant  the instant to get the next transition after, not null, but null
319      *  may be ignored if the rules have a single offset for all instants
320      * @return the next transition after the specified instant, null if this is after the last transition
321      */
nextTransition(Instant instant)322     public abstract ZoneOffsetTransition nextTransition(Instant instant);
323 
324     /**
325      * Gets the previous transition before the specified instant.
326      * <p>
327      * This returns details of the previous transition after the specified instant.
328      * For example, if the instant represents a point where "summer" daylight saving time
329      * applies, then the method will return the transition from the previous "winter" time.
330      *
331      * @param instant  the instant to get the previous transition after, not null, but null
332      *  may be ignored if the rules have a single offset for all instants
333      * @return the previous transition after the specified instant, null if this is before the first transition
334      */
previousTransition(Instant instant)335     public abstract ZoneOffsetTransition previousTransition(Instant instant);
336 
337     /**
338      * Gets the complete list of fully defined transitions.
339      * <p>
340      * The complete set of transitions for this rules instance is defined by this method
341      * and {@link #getTransitionRules()}. This method returns those transitions that have
342      * been fully defined. These are typically historical, but may be in the future.
343      * <p>
344      * The list will be empty for fixed offset rules and for any time-zone where there has
345      * only ever been a single offset. The list will also be empty if the transition rules are unknown.
346      *
347      * @return an immutable list of fully defined transitions, not null
348      */
getTransitions()349     public abstract List<ZoneOffsetTransition> getTransitions();
350 
351     /**
352      * Gets the list of transition rules for years beyond those defined in the transition list.
353      * <p>
354      * The complete set of transitions for this rules instance is defined by this method
355      * and {@link #getTransitions()}. This method returns instances of {@link ZoneOffsetTransitionRule}
356      * that define an algorithm for when transitions will occur.
357      * <p>
358      * For any given {@code ZoneRules}, this list contains the transition rules for years
359      * beyond those years that have been fully defined. These rules typically refer to future
360      * daylight saving time rule changes.
361      * <p>
362      * If the zone defines daylight savings into the future, then the list will normally
363      * be of size two and hold information about entering and exiting daylight savings.
364      * If the zone does not have daylight savings, or information about future changes
365      * is uncertain, then the list will be empty.
366      * <p>
367      * The list will be empty for fixed offset rules and for any time-zone where there is no
368      * daylight saving time. The list will also be empty if the transition rules are unknown.
369      *
370      * @return an immutable list of transition rules, not null
371      */
getTransitionRules()372     public abstract List<ZoneOffsetTransitionRule> getTransitionRules();
373 
374     //-----------------------------------------------------------------------
375     /**
376      * Checks if this set of rules equals another.
377      * <p>
378      * Two rule sets are equal if they will always result in the same output
379      * for any given input instant or local date-time.
380      * Rules from two different groups may return false even if they are in fact the same.
381      * <p>
382      * This definition should result in implementations comparing their entire state.
383      *
384      * @param otherRules  the other rules, null returns false
385      * @return true if this rules is the same as that specified
386      */
387     @Override
equals(Object otherRules)388     public abstract boolean equals(Object otherRules);
389 
390     /**
391      * Returns a suitable hash code given the definition of {@code #equals}.
392      *
393      * @return the hash code
394      */
395     @Override
hashCode()396     public abstract int hashCode();
397 
398     //-----------------------------------------------------------------------
399     /**
400      * Fixed time-zone.
401      */
402     static final class Fixed extends ZoneRules implements Serializable {
403         /** A serialization identifier for this class. */
404         private static final long serialVersionUID = -8733721350312276297L;
405         /** The offset. */
406         private final ZoneOffset offset;
407 
408         /**
409          * Constructor.
410          *
411          * @param offset  the offset, not null
412          */
Fixed(ZoneOffset offset)413         Fixed(ZoneOffset offset) {
414             this.offset = offset;
415         }
416 
417         //-------------------------------------------------------------------------
418         @Override
isFixedOffset()419         public boolean isFixedOffset() {
420             return true;
421         }
422 
423         @Override
getOffset(Instant instant)424         public ZoneOffset getOffset(Instant instant) {
425             return offset;
426         }
427 
428         @Override
getOffset(LocalDateTime localDateTime)429         public ZoneOffset getOffset(LocalDateTime localDateTime) {
430             return offset;
431         }
432 
433         @Override
getValidOffsets(LocalDateTime localDateTime)434         public List<ZoneOffset> getValidOffsets(LocalDateTime localDateTime) {
435             return Collections.singletonList(offset);
436         }
437 
438         @Override
getTransition(LocalDateTime localDateTime)439         public ZoneOffsetTransition getTransition(LocalDateTime localDateTime) {
440             return null;
441         }
442 
443         @Override
isValidOffset(LocalDateTime dateTime, ZoneOffset offset)444         public boolean isValidOffset(LocalDateTime dateTime, ZoneOffset offset) {
445             return this.offset.equals(offset);
446         }
447 
448         //-------------------------------------------------------------------------
449         @Override
getStandardOffset(Instant instant)450         public ZoneOffset getStandardOffset(Instant instant) {
451             return offset;
452         }
453 
454         @Override
getDaylightSavings(Instant instant)455         public Duration getDaylightSavings(Instant instant) {
456             return Duration.ZERO;
457         }
458 
459         @Override
isDaylightSavings(Instant instant)460         public boolean isDaylightSavings(Instant instant) {
461             return false;
462         }
463 
464         //-------------------------------------------------------------------------
465         @Override
nextTransition(Instant instant)466         public ZoneOffsetTransition nextTransition(Instant instant) {
467             return null;
468         }
469 
470         @Override
previousTransition(Instant instant)471         public ZoneOffsetTransition previousTransition(Instant instant) {
472             return null;
473         }
474 
475         @Override
getTransitions()476         public List<ZoneOffsetTransition> getTransitions() {
477             return Collections.emptyList();
478         }
479 
480         @Override
getTransitionRules()481         public List<ZoneOffsetTransitionRule> getTransitionRules() {
482             return Collections.emptyList();
483         }
484 
485         //-----------------------------------------------------------------------
486         @Override
equals(Object obj)487         public boolean equals(Object obj) {
488             if (this == obj) {
489                return true;
490             }
491             if (obj instanceof Fixed) {
492                 return offset.equals(((Fixed) obj).offset);
493             }
494             if (obj instanceof StandardZoneRules) {
495                 StandardZoneRules szr = (StandardZoneRules) obj;
496                 return szr.isFixedOffset() && offset.equals(szr.getOffset(Instant.EPOCH));
497             }
498             return false;
499         }
500 
501         @Override
hashCode()502         public int hashCode() {
503             return 1 ^
504                     (31 + offset.hashCode()) ^
505                     1 ^
506                     (31 + offset.hashCode()) ^
507                     1;
508         }
509 
510         @Override
toString()511         public String toString() {
512             return "FixedRules:" + offset;
513         }
514     }
515 
516 }
517