001/*
002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos
003 *
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions are met:
008 *
009 *  * Redistributions of source code must retain the above copyright notice,
010 *    this list of conditions and the following disclaimer.
011 *
012 *  * Redistributions in binary form must reproduce the above copyright notice,
013 *    this list of conditions and the following disclaimer in the documentation
014 *    and/or other materials provided with the distribution.
015 *
016 *  * Neither the name of JSR-310 nor the names of its contributors
017 *    may be used to endorse or promote products derived from this software
018 *    without specific prior written permission.
019 *
020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031 */
032package org.threeten.bp.zone;
033
034import static org.threeten.bp.temporal.TemporalAdjusters.nextOrSame;
035import static org.threeten.bp.temporal.TemporalAdjusters.previousOrSame;
036
037import java.io.DataInput;
038import java.io.DataOutput;
039import java.io.IOException;
040import java.io.Serializable;
041import java.util.Objects;
042
043import org.threeten.bp.DayOfWeek;
044import org.threeten.bp.LocalDate;
045import org.threeten.bp.LocalDateTime;
046import org.threeten.bp.LocalTime;
047import org.threeten.bp.Month;
048import org.threeten.bp.ZoneOffset;
049import org.threeten.bp.temporal.ISOChrono;
050
051/**
052 * A rule expressing how to create a transition.
053 * <p>
054 * This class allows rules for identifying future transitions to be expressed.
055 * A rule might be written in many forms:
056 * <p><ul>
057 * <li>the 16th March
058 * <li>the Sunday on or after the 16th March
059 * <li>the Sunday on or before the 16th March
060 * <li>the last Sunday in February
061 * </ul><p>
062 * These different rule types can be expressed and queried.
063 *
064 * <h3>Specification for implementors</h3>
065 * This class is immutable and thread-safe.
066 */
067public final class ZoneOffsetTransitionRule implements Serializable {
068
069    /**
070     * Serialization version.
071     */
072    private static final long serialVersionUID = 6889046316657758795L;
073
074    /**
075     * The month of the month-day of the first day of the cutover week.
076     * The actual date will be adjusted by the dowChange field.
077     */
078    private final Month month;
079    /**
080     * The day-of-month of the month-day of the cutover week.
081     * If positive, it is the start of the week where the cutover can occur.
082     * If negative, it represents the end of the week where cutover can occur.
083     * The value is the number of days from the end of the month, such that
084     * {@code -1} is the last day of the month, {@code -2} is the second
085     * to last day, and so on.
086     */
087    private final byte dom;
088    /**
089     * The cutover day-of-week, null to retain the day-of-month.
090     */
091    private final DayOfWeek dow;
092    /**
093     * The cutover time in the 'before' offset.
094     */
095    private final LocalTime time;
096    /**
097     * Whether the cutover time is midnight at the end of day.
098     */
099    private final boolean timeEndOfDay;
100    /**
101     * The definition of how the local time should be interpreted.
102     */
103    private final TimeDefinition timeDefinition;
104    /**
105     * The standard offset at the cutover.
106     */
107    private final ZoneOffset standardOffset;
108    /**
109     * The offset before the cutover.
110     */
111    private final ZoneOffset offsetBefore;
112    /**
113     * The offset after the cutover.
114     */
115    private final ZoneOffset offsetAfter;
116
117    /**
118     * Obtains an instance defining the yearly rule to create transitions between two offsets.
119     * <p>
120     * Applications should normally obtain an instance from {@link ZoneRules}.
121     * This factory is only intended for use when creating {@link ZoneRules}.
122     *
123     * @param month  the month of the month-day of the first day of the cutover week, not null
124     * @param dayOfMonthIndicator  the day of the month-day of the cutover week, positive if the week is that
125     *  day or later, negative if the week is that day or earlier, counting from the last day of the month,
126     *  from -28 to 31 excluding 0
127     * @param dayOfWeek  the required day-of-week, null if the month-day should not be changed
128     * @param time  the cutover time in the 'before' offset, not null
129     * @param timeEndOfDay  whether the time is midnight at the end of day
130     * @param timeDefnition  how to interpret the cutover
131     * @param standardOffset  the standard offset in force at the cutover, not null
132     * @param offsetBefore  the offset before the cutover, not null
133     * @param offsetAfter  the offset after the cutover, not null
134     * @return the rule, not null
135     * @throws IllegalArgumentException if the day of month indicator is invalid
136     * @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
137     */
138    public static ZoneOffsetTransitionRule of(
139            Month month,
140            int dayOfMonthIndicator,
141            DayOfWeek dayOfWeek,
142            LocalTime time,
143            boolean timeEndOfDay,
144            TimeDefinition timeDefnition,
145            ZoneOffset standardOffset,
146            ZoneOffset offsetBefore,
147            ZoneOffset offsetAfter) {
148        Objects.requireNonNull(month, "month");
149        Objects.requireNonNull(time, "time");
150        Objects.requireNonNull(timeDefnition, "timeDefnition");
151        Objects.requireNonNull(standardOffset, "standardOffset");
152        Objects.requireNonNull(offsetBefore, "offsetBefore");
153        Objects.requireNonNull(offsetAfter, "offsetAfter");
154        if (dayOfMonthIndicator < -28 || dayOfMonthIndicator > 31 || dayOfMonthIndicator == 0) {
155            throw new IllegalArgumentException("Day of month indicator must be between -28 and 31 inclusive excluding zero");
156        }
157        if (timeEndOfDay && time.equals(LocalTime.MIDNIGHT) == false) {
158            throw new IllegalArgumentException("Time must be midnight when end of day flag is true");
159        }
160        return new ZoneOffsetTransitionRule(month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefnition, standardOffset, offsetBefore, offsetAfter);
161    }
162
163    /**
164     * Creates an instance defining the yearly rule to create transitions between two offsets.
165     *
166     * @param month  the month of the month-day of the first day of the cutover week, not null
167     * @param dayOfMonthIndicator  the day of the month-day of the cutover week, positive if the week is that
168     *  day or later, negative if the week is that day or earlier, counting from the last day of the month,
169     *  from -28 to 31 excluding 0
170     * @param dayOfWeek  the required day-of-week, null if the month-day should not be changed
171     * @param time  the cutover time in the 'before' offset, not null
172     * @param timeEndOfDay  whether the time is midnight at the end of day
173     * @param timeDefnition  how to interpret the cutover
174     * @param standardOffset  the standard offset in force at the cutover, not null
175     * @param offsetBefore  the offset before the cutover, not null
176     * @param offsetAfter  the offset after the cutover, not null
177     * @throws IllegalArgumentException if the day of month indicator is invalid
178     * @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
179     */
180    ZoneOffsetTransitionRule(
181            Month month,
182            int dayOfMonthIndicator,
183            DayOfWeek dayOfWeek,
184            LocalTime time,
185            boolean timeEndOfDay,
186            TimeDefinition timeDefnition,
187            ZoneOffset standardOffset,
188            ZoneOffset offsetBefore,
189            ZoneOffset offsetAfter) {
190        this.month = month;
191        this.dom = (byte) dayOfMonthIndicator;
192        this.dow = dayOfWeek;
193        this.time = time;
194        this.timeEndOfDay = timeEndOfDay;
195        this.timeDefinition = timeDefnition;
196        this.standardOffset = standardOffset;
197        this.offsetBefore = offsetBefore;
198        this.offsetAfter = offsetAfter;
199    }
200
201    //-----------------------------------------------------------------------
202    /**
203     * Uses a serialization delegate.
204     *
205     * @return the replacing object, not null
206     */
207    private Object writeReplace() {
208        return new Ser(Ser.ZOTRULE, this);
209    }
210
211    /**
212     * Writes the state to the stream.
213     *
214     * @param out  the output stream, not null
215     * @throws IOException if an error occurs
216     */
217    void writeExternal(DataOutput out) throws IOException {
218        final int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay());
219        final int stdOffset = standardOffset.getTotalSeconds();
220        final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset;
221        final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset;
222        final int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31);
223        final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255);
224        final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3);
225        final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3);
226        final int dowByte = (dow == null ? 0 : dow.getValue());
227        int b = (month.getValue() << 28) +          // 4 bits
228                ((dom + 32) << 22) +                // 6 bits
229                (dowByte << 19) +                   // 3 bits
230                (timeByte << 14) +                  // 5 bits
231                (timeDefinition.ordinal() << 12) +  // 2 bits
232                (stdOffsetByte << 4) +              // 8 bits
233                (beforeByte << 2) +                 // 2 bits
234                afterByte;                          // 2 bits
235        out.writeInt(b);
236        if (timeByte == 31) {
237            out.writeInt(timeSecs);
238        }
239        if (stdOffsetByte == 255) {
240            out.writeInt(stdOffset);
241        }
242        if (beforeByte == 3) {
243            out.writeInt(offsetBefore.getTotalSeconds());
244        }
245        if (afterByte == 3) {
246            out.writeInt(offsetAfter.getTotalSeconds());
247        }
248    }
249
250    /**
251     * Reads the state from the stream.
252     *
253     * @param in  the input stream, not null
254     * @return the created object, not null
255     * @throws IOException if an error occurs
256     */
257    static ZoneOffsetTransitionRule readExternal(DataInput in) throws IOException {
258        int data = in.readInt();
259        Month month = Month.of(data >>> 28);
260        int dom = ((data & (63 << 22)) >>> 22) - 32;
261        int dowByte = (data & (7 << 19)) >>> 19;
262        DayOfWeek dow = dowByte == 0 ? null : DayOfWeek.of(dowByte);
263        int timeByte = (data & (31 << 14)) >>> 14;
264        TimeDefinition defn = TimeDefinition.values()[(data & (3 << 12)) >>> 12];
265        int stdByte = (data & (255 << 4)) >>> 4;
266        int beforeByte = (data & (3 << 2)) >>> 2;
267        int afterByte = (data & 3);
268        LocalTime time = (timeByte == 31 ? LocalTime.ofSecondOfDay(in.readInt()) : LocalTime.of(timeByte % 24, 0));
269        ZoneOffset std = (stdByte == 255 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds((stdByte - 128) * 900));
270        ZoneOffset before = (beforeByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + beforeByte * 1800));
271        ZoneOffset after = (afterByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + afterByte * 1800));
272        return ZoneOffsetTransitionRule.of(month, dom, dow, time, timeByte == 24, defn, std, before, after);
273    }
274
275    //-----------------------------------------------------------------------
276    /**
277     * Gets the month of the transition.
278     * <p>
279     * If the rule defines an exact date then the month is the month of that date.
280     * <p>
281     * If the rule defines a week where the transition might occur, then the month
282     * if the month of either the earliest or latest possible date of the cutover.
283     *
284     * @return the month of the transition, not null
285     */
286    public Month getMonth() {
287        return month;
288    }
289
290    /**
291     * Gets the indicator of the day-of-month of the transition.
292     * <p>
293     * If the rule defines an exact date then the day is the month of that date.
294     * <p>
295     * If the rule defines a week where the transition might occur, then the day
296     * defines either the start of the end of the transition week.
297     * <p>
298     * If the value is positive, then it represents a normal day-of-month, and is the
299     * earliest possible date that the transition can be.
300     * The date may refer to 29th February which should be treated as 1st March in non-leap years.
301     * <p>
302     * If the value is negative, then it represents the number of days back from the
303     * end of the month where {@code -1} is the last day of the month.
304     * In this case, the day identified is the latest possible date that the transition can be.
305     *
306     * @return the day-of-month indicator, from -28 to 31 excluding 0
307     */
308    public int getDayOfMonthIndicator() {
309        return dom;
310    }
311
312    /**
313     * Gets the day-of-week of the transition.
314     * <p>
315     * If the rule defines an exact date then this returns null.
316     * <p>
317     * If the rule defines a week where the cutover might occur, then this method
318     * returns the day-of-week that the month-day will be adjusted to.
319     * If the day is positive then the adjustment is later.
320     * If the day is negative then the adjustment is earlier.
321     *
322     * @return the day-of-week that the transition occurs, null if the rule defines an exact date
323     */
324    public DayOfWeek getDayOfWeek() {
325        return dow;
326    }
327
328    /**
329     * Gets the local time of day of the transition which must be checked with
330     * {@link #isMidnightEndOfDay()}.
331     * <p>
332     * The time is converted into an instant using the time definition.
333     *
334     * @return the local time of day of the transition, not null
335     */
336    public LocalTime getLocalTime() {
337        return time;
338    }
339
340    /**
341     * Is the transition local time midnight at the end of day.
342     * <p>
343     * The transition may be represented as occurring at 24:00.
344     *
345     * @return whether a local time of midnight is at the start or end of the day
346     */
347    public boolean isMidnightEndOfDay() {
348        return timeEndOfDay;
349    }
350
351    /**
352     * Gets the time definition, specifying how to convert the time to an instant.
353     * <p>
354     * The local time can be converted to an instant using the standard offset,
355     * the wall offset or UTC.
356     *
357     * @return the time definition, not null
358     */
359    public TimeDefinition getTimeDefinition() {
360        return timeDefinition;
361    }
362
363    /**
364     * Gets the standard offset in force at the transition.
365     *
366     * @return the standard offset, not null
367     */
368    public ZoneOffset getStandardOffset() {
369        return standardOffset;
370    }
371
372    /**
373     * Gets the offset before the transition.
374     *
375     * @return the offset before, not null
376     */
377    public ZoneOffset getOffsetBefore() {
378        return offsetBefore;
379    }
380
381    /**
382     * Gets the offset after the transition.
383     *
384     * @return the offset after, not null
385     */
386    public ZoneOffset getOffsetAfter() {
387        return offsetAfter;
388    }
389
390    //-----------------------------------------------------------------------
391    /**
392     * Creates a transition instance for the specified year.
393     * <p>
394     * Calculations are performed using the ISO-8601 chronology.
395     *
396     * @param year  the year to create a transition for, not null
397     * @return the transition instance, not null
398     */
399    public ZoneOffsetTransition createTransition(int year) {
400        LocalDate date;
401        if (dom < 0) {
402            date = LocalDate.of(year, month, month.length(ISOChrono.INSTANCE.isLeapYear(year)) + 1 + dom);
403            if (dow != null) {
404                date = date.with(previousOrSame(dow));
405            }
406        } else {
407            date = LocalDate.of(year, month, dom);
408            if (dow != null) {
409                date = date.with(nextOrSame(dow));
410            }
411        }
412        if (timeEndOfDay) {
413            date = date.plusDays(1);
414        }
415        LocalDateTime localDT = LocalDateTime.of(date, time);
416        LocalDateTime transition = timeDefinition.createDateTime(localDT, standardOffset, offsetBefore);
417        return new ZoneOffsetTransition(transition, offsetBefore, offsetAfter);
418    }
419
420    //-----------------------------------------------------------------------
421    /**
422     * Checks if this object equals another.
423     * <p>
424     * The entire state of the object is compared.
425     *
426     * @param otherRule  the other object to compare to, null returns false
427     * @return true if equal
428     */
429    @Override
430    public boolean equals(Object otherRule) {
431        if (otherRule == this) {
432            return true;
433        }
434        if (otherRule instanceof ZoneOffsetTransitionRule) {
435            ZoneOffsetTransitionRule other = (ZoneOffsetTransitionRule) otherRule;
436            return month == other.month && dom == other.dom && dow == other.dow &&
437                timeDefinition == other.timeDefinition &&
438                time.equals(other.time) &&
439                timeEndOfDay == other.timeEndOfDay &&
440                standardOffset.equals(other.standardOffset) &&
441                offsetBefore.equals(other.offsetBefore) &&
442                offsetAfter.equals(other.offsetAfter);
443        }
444        return false;
445    }
446
447    /**
448     * Returns a suitable hash code.
449     *
450     * @return the hash code
451     */
452    @Override
453    public int hashCode() {
454        int hash = ((time.toSecondOfDay() + (timeEndOfDay ? 1 : 0)) << 15) +
455                (month.ordinal() << 11) + ((dom + 32) << 5) +
456                ((dow == null ? 7 : dow.ordinal()) << 2) + (timeDefinition.ordinal());
457        return hash ^ standardOffset.hashCode() ^
458                offsetBefore.hashCode() ^ offsetAfter.hashCode();
459    }
460
461    //-----------------------------------------------------------------------
462    /**
463     * Returns a string describing this object.
464     *
465     * @return a string for debugging, not null
466     */
467    @Override
468    public String toString() {
469        StringBuilder buf = new StringBuilder();
470        buf.append("TransitionRule[")
471            .append(offsetBefore.compareTo(offsetAfter) > 0 ? "Gap " : "Overlap ")
472            .append(offsetBefore).append(" to ").append(offsetAfter).append(", ");
473        if (dow != null) {
474            if (dom == -1) {
475                buf.append(dow.name()).append(" on or before last day of ").append(month.name());
476            } else if (dom < 0) {
477                buf.append(dow.name()).append(" on or before last day minus ").append(-dom - 1).append(" of ").append(month.name());
478            } else {
479                buf.append(dow.name()).append(" on or after ").append(month.name()).append(' ').append(dom);
480            }
481        } else {
482            buf.append(month.name()).append(' ').append(dom);
483        }
484        buf.append(" at ").append(timeEndOfDay ? "24:00" : time.toString())
485            .append(" ").append(timeDefinition)
486            .append(", standard offset ").append(standardOffset)
487            .append(']');
488        return buf.toString();
489    }
490
491    //-----------------------------------------------------------------------
492    /**
493     * A definition of the way a local time can be converted to the actual
494     * transition date-time.
495     * <p>
496     * Time zone rules are expressed in one of three ways:
497     * <p><ul>
498     * <li>Relative to UTC</li>
499     * <li>Relative to the standard offset in force</li>
500     * <li>Relative to the wall offset (what you would see on a clock on the wall)</li>
501     * </ul><p>
502     */
503    public static enum TimeDefinition {
504        /** The local date-time is expressed in terms of the UTC offset. */
505        UTC,
506        /** The local date-time is expressed in terms of the wall offset. */
507        WALL,
508        /** The local date-time is expressed in terms of the standard offset. */
509        STANDARD;
510
511        /**
512         * Converts the specified local date-time to the local date-time actually
513         * seen on a wall clock.
514         * <p>
515         * This method converts using the type of this enum.
516         * The output is defined relative to the 'before' offset of the transition.
517         * <p>
518         * The UTC type uses the UTC offset.
519         * The STANDARD type uses the standard offset.
520         * The WALL type returns the input date-time.
521         * The result is intended for use with the wall-offset.
522         *
523         * @param dateTime  the local date-time, not null
524         * @param standardOffset  the standard offset, not null
525         * @param wallOffset  the wall offset, not null
526         * @return the date-time relative to the wall/before offset, not null
527         */
528        public LocalDateTime createDateTime(LocalDateTime dateTime, ZoneOffset standardOffset, ZoneOffset wallOffset) {
529            switch (this) {
530                case UTC: {
531                    int difference = wallOffset.getTotalSeconds() - ZoneOffset.UTC.getTotalSeconds();
532                    return dateTime.plusSeconds(difference);
533                }
534                case STANDARD: {
535                    int difference = wallOffset.getTotalSeconds() - standardOffset.getTotalSeconds();
536                    return dateTime.plusSeconds(difference);
537                }
538                default:  // WALL
539                    return dateTime;
540            }
541        }
542    }
543
544}