Home | History | Annotate | Download | only in zone
      1 /*
      2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 /*
     27  * This file is available under and governed by the GNU General Public
     28  * License version 2 only, as published by the Free Software Foundation.
     29  * However, the following notice accompanied the original version of this
     30  * file:
     31  *
     32  * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
     33  *
     34  * All rights reserved.
     35  *
     36  * Redistribution and use in source and binary forms, with or without
     37  * modification, are permitted provided that the following conditions are met:
     38  *
     39  *  * Redistributions of source code must retain the above copyright notice,
     40  *    this list of conditions and the following disclaimer.
     41  *
     42  *  * Redistributions in binary form must reproduce the above copyright notice,
     43  *    this list of conditions and the following disclaimer in the documentation
     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.zone;
     63 
     64 import static java.time.temporal.TemporalAdjusters.nextOrSame;
     65 import static java.time.temporal.TemporalAdjusters.previousOrSame;
     66 
     67 import java.io.DataInput;
     68 import java.io.DataOutput;
     69 import java.io.IOException;
     70 import java.io.InvalidObjectException;
     71 import java.io.ObjectInputStream;
     72 import java.io.Serializable;
     73 import java.time.DayOfWeek;
     74 import java.time.LocalDate;
     75 import java.time.LocalDateTime;
     76 import java.time.LocalTime;
     77 import java.time.Month;
     78 import java.time.ZoneOffset;
     79 import java.time.chrono.IsoChronology;
     80 import java.util.Objects;
     81 
     82 /**
     83  * A rule expressing how to create a transition.
     84  * <p>
     85  * This class allows rules for identifying future transitions to be expressed.
     86  * A rule might be written in many forms:
     87  * <ul>
     88  * <li>the 16th March
     89  * <li>the Sunday on or after the 16th March
     90  * <li>the Sunday on or before the 16th March
     91  * <li>the last Sunday in February
     92  * </ul>
     93  * These different rule types can be expressed and queried.
     94  *
     95  * @implSpec
     96  * This class is immutable and thread-safe.
     97  *
     98  * @since 1.8
     99  */
    100 public final class ZoneOffsetTransitionRule implements Serializable {
    101 
    102     /**
    103      * Serialization version.
    104      */
    105     private static final long serialVersionUID = 6889046316657758795L;
    106 
    107     /**
    108      * The month of the month-day of the first day of the cutover week.
    109      * The actual date will be adjusted by the dowChange field.
    110      */
    111     private final Month month;
    112     /**
    113      * The day-of-month of the month-day of the cutover week.
    114      * If positive, it is the start of the week where the cutover can occur.
    115      * If negative, it represents the end of the week where cutover can occur.
    116      * The value is the number of days from the end of the month, such that
    117      * {@code -1} is the last day of the month, {@code -2} is the second
    118      * to last day, and so on.
    119      */
    120     private final byte dom;
    121     /**
    122      * The cutover day-of-week, null to retain the day-of-month.
    123      */
    124     private final DayOfWeek dow;
    125     /**
    126      * The cutover time in the 'before' offset.
    127      */
    128     private final LocalTime time;
    129     /**
    130      * Whether the cutover time is midnight at the end of day.
    131      */
    132     private final boolean timeEndOfDay;
    133     /**
    134      * The definition of how the local time should be interpreted.
    135      */
    136     private final TimeDefinition timeDefinition;
    137     /**
    138      * The standard offset at the cutover.
    139      */
    140     private final ZoneOffset standardOffset;
    141     /**
    142      * The offset before the cutover.
    143      */
    144     private final ZoneOffset offsetBefore;
    145     /**
    146      * The offset after the cutover.
    147      */
    148     private final ZoneOffset offsetAfter;
    149 
    150     /**
    151      * Obtains an instance defining the yearly rule to create transitions between two offsets.
    152      * <p>
    153      * Applications should normally obtain an instance from {@link ZoneRules}.
    154      * This factory is only intended for use when creating {@link ZoneRules}.
    155      *
    156      * @param month  the month of the month-day of the first day of the cutover week, not null
    157      * @param dayOfMonthIndicator  the day of the month-day of the cutover week, positive if the week is that
    158      *  day or later, negative if the week is that day or earlier, counting from the last day of the month,
    159      *  from -28 to 31 excluding 0
    160      * @param dayOfWeek  the required day-of-week, null if the month-day should not be changed
    161      * @param time  the cutover time in the 'before' offset, not null
    162      * @param timeEndOfDay  whether the time is midnight at the end of day
    163      * @param timeDefnition  how to interpret the cutover
    164      * @param standardOffset  the standard offset in force at the cutover, not null
    165      * @param offsetBefore  the offset before the cutover, not null
    166      * @param offsetAfter  the offset after the cutover, not null
    167      * @return the rule, not null
    168      * @throws IllegalArgumentException if the day of month indicator is invalid
    169      * @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
    170      */
    171     public static ZoneOffsetTransitionRule of(
    172             Month month,
    173             int dayOfMonthIndicator,
    174             DayOfWeek dayOfWeek,
    175             LocalTime time,
    176             boolean timeEndOfDay,
    177             TimeDefinition timeDefnition,
    178             ZoneOffset standardOffset,
    179             ZoneOffset offsetBefore,
    180             ZoneOffset offsetAfter) {
    181         Objects.requireNonNull(month, "month");
    182         Objects.requireNonNull(time, "time");
    183         Objects.requireNonNull(timeDefnition, "timeDefnition");
    184         Objects.requireNonNull(standardOffset, "standardOffset");
    185         Objects.requireNonNull(offsetBefore, "offsetBefore");
    186         Objects.requireNonNull(offsetAfter, "offsetAfter");
    187         if (dayOfMonthIndicator < -28 || dayOfMonthIndicator > 31 || dayOfMonthIndicator == 0) {
    188             throw new IllegalArgumentException("Day of month indicator must be between -28 and 31 inclusive excluding zero");
    189         }
    190         if (timeEndOfDay && time.equals(LocalTime.MIDNIGHT) == false) {
    191             throw new IllegalArgumentException("Time must be midnight when end of day flag is true");
    192         }
    193         return new ZoneOffsetTransitionRule(month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefnition, standardOffset, offsetBefore, offsetAfter);
    194     }
    195 
    196     /**
    197      * Creates an instance defining the yearly rule to create transitions between two offsets.
    198      *
    199      * @param month  the month of the month-day of the first day of the cutover week, not null
    200      * @param dayOfMonthIndicator  the day of the month-day of the cutover week, positive if the week is that
    201      *  day or later, negative if the week is that day or earlier, counting from the last day of the month,
    202      *  from -28 to 31 excluding 0
    203      * @param dayOfWeek  the required day-of-week, null if the month-day should not be changed
    204      * @param time  the cutover time in the 'before' offset, not null
    205      * @param timeEndOfDay  whether the time is midnight at the end of day
    206      * @param timeDefnition  how to interpret the cutover
    207      * @param standardOffset  the standard offset in force at the cutover, not null
    208      * @param offsetBefore  the offset before the cutover, not null
    209      * @param offsetAfter  the offset after the cutover, not null
    210      * @throws IllegalArgumentException if the day of month indicator is invalid
    211      * @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
    212      */
    213     ZoneOffsetTransitionRule(
    214             Month month,
    215             int dayOfMonthIndicator,
    216             DayOfWeek dayOfWeek,
    217             LocalTime time,
    218             boolean timeEndOfDay,
    219             TimeDefinition timeDefnition,
    220             ZoneOffset standardOffset,
    221             ZoneOffset offsetBefore,
    222             ZoneOffset offsetAfter) {
    223         this.month = month;
    224         this.dom = (byte) dayOfMonthIndicator;
    225         this.dow = dayOfWeek;
    226         this.time = time;
    227         this.timeEndOfDay = timeEndOfDay;
    228         this.timeDefinition = timeDefnition;
    229         this.standardOffset = standardOffset;
    230         this.offsetBefore = offsetBefore;
    231         this.offsetAfter = offsetAfter;
    232     }
    233 
    234     //-----------------------------------------------------------------------
    235     /**
    236      * Defend against malicious streams.
    237      *
    238      * @param s the stream to read
    239      * @throws InvalidObjectException always
    240      */
    241     private void readObject(ObjectInputStream s) throws InvalidObjectException {
    242         throw new InvalidObjectException("Deserialization via serialization delegate");
    243     }
    244 
    245     /**
    246      * Writes the object using a
    247      * <a href="../../../serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.
    248      * @serialData
    249      * Refer to the serialized form of
    250      * <a href="../../../serialized-form.html#java.time.zone.ZoneRules">ZoneRules.writeReplace</a>
    251      * for the encoding of epoch seconds and offsets.
    252      * <pre style="font-size:1.0em">{@code
    253      *
    254      *      out.writeByte(3);                // identifies a ZoneOffsetTransition
    255      *      final int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay());
    256      *      final int stdOffset = standardOffset.getTotalSeconds();
    257      *      final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset;
    258      *      final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset;
    259      *      final int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31);
    260      *      final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255);
    261      *      final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3);
    262      *      final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3);
    263      *      final int dowByte = (dow == null ? 0 : dow.getValue());
    264      *      int b = (month.getValue() << 28) +          // 4 bits
    265      *              ((dom + 32) << 22) +                // 6 bits
    266      *              (dowByte << 19) +                   // 3 bits
    267      *              (timeByte << 14) +                  // 5 bits
    268      *              (timeDefinition.ordinal() << 12) +  // 2 bits
    269      *              (stdOffsetByte << 4) +              // 8 bits
    270      *              (beforeByte << 2) +                 // 2 bits
    271      *              afterByte;                          // 2 bits
    272      *      out.writeInt(b);
    273      *      if (timeByte == 31) {
    274      *          out.writeInt(timeSecs);
    275      *      }
    276      *      if (stdOffsetByte == 255) {
    277      *          out.writeInt(stdOffset);
    278      *      }
    279      *      if (beforeByte == 3) {
    280      *          out.writeInt(offsetBefore.getTotalSeconds());
    281      *      }
    282      *      if (afterByte == 3) {
    283      *          out.writeInt(offsetAfter.getTotalSeconds());
    284      *      }
    285      * }
    286      * </pre>
    287      *
    288      * @return the replacing object, not null
    289      */
    290     private Object writeReplace() {
    291         return new Ser(Ser.ZOTRULE, this);
    292     }
    293 
    294     /**
    295      * Writes the state to the stream.
    296      *
    297      * @param out  the output stream, not null
    298      * @throws IOException if an error occurs
    299      */
    300     void writeExternal(DataOutput out) throws IOException {
    301         final int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay());
    302         final int stdOffset = standardOffset.getTotalSeconds();
    303         final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset;
    304         final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset;
    305         final int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31);
    306         final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255);
    307         final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3);
    308         final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3);
    309         final int dowByte = (dow == null ? 0 : dow.getValue());
    310         int b = (month.getValue() << 28) +          // 4 bits
    311                 ((dom + 32) << 22) +                // 6 bits
    312                 (dowByte << 19) +                   // 3 bits
    313                 (timeByte << 14) +                  // 5 bits
    314                 (timeDefinition.ordinal() << 12) +  // 2 bits
    315                 (stdOffsetByte << 4) +              // 8 bits
    316                 (beforeByte << 2) +                 // 2 bits
    317                 afterByte;                          // 2 bits
    318         out.writeInt(b);
    319         if (timeByte == 31) {
    320             out.writeInt(timeSecs);
    321         }
    322         if (stdOffsetByte == 255) {
    323             out.writeInt(stdOffset);
    324         }
    325         if (beforeByte == 3) {
    326             out.writeInt(offsetBefore.getTotalSeconds());
    327         }
    328         if (afterByte == 3) {
    329             out.writeInt(offsetAfter.getTotalSeconds());
    330         }
    331     }
    332 
    333     /**
    334      * Reads the state from the stream.
    335      *
    336      * @param in  the input stream, not null
    337      * @return the created object, not null
    338      * @throws IOException if an error occurs
    339      */
    340     static ZoneOffsetTransitionRule readExternal(DataInput in) throws IOException {
    341         int data = in.readInt();
    342         Month month = Month.of(data >>> 28);
    343         int dom = ((data & (63 << 22)) >>> 22) - 32;
    344         int dowByte = (data & (7 << 19)) >>> 19;
    345         DayOfWeek dow = dowByte == 0 ? null : DayOfWeek.of(dowByte);
    346         int timeByte = (data & (31 << 14)) >>> 14;
    347         TimeDefinition defn = TimeDefinition.values()[(data & (3 << 12)) >>> 12];
    348         int stdByte = (data & (255 << 4)) >>> 4;
    349         int beforeByte = (data & (3 << 2)) >>> 2;
    350         int afterByte = (data & 3);
    351         LocalTime time = (timeByte == 31 ? LocalTime.ofSecondOfDay(in.readInt()) : LocalTime.of(timeByte % 24, 0));
    352         ZoneOffset std = (stdByte == 255 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds((stdByte - 128) * 900));
    353         ZoneOffset before = (beforeByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + beforeByte * 1800));
    354         ZoneOffset after = (afterByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + afterByte * 1800));
    355         return ZoneOffsetTransitionRule.of(month, dom, dow, time, timeByte == 24, defn, std, before, after);
    356     }
    357 
    358     //-----------------------------------------------------------------------
    359     /**
    360      * Gets the month of the transition.
    361      * <p>
    362      * If the rule defines an exact date then the month is the month of that date.
    363      * <p>
    364      * If the rule defines a week where the transition might occur, then the month
    365      * if the month of either the earliest or latest possible date of the cutover.
    366      *
    367      * @return the month of the transition, not null
    368      */
    369     public Month getMonth() {
    370         return month;
    371     }
    372 
    373     /**
    374      * Gets the indicator of the day-of-month of the transition.
    375      * <p>
    376      * If the rule defines an exact date then the day is the month of that date.
    377      * <p>
    378      * If the rule defines a week where the transition might occur, then the day
    379      * defines either the start of the end of the transition week.
    380      * <p>
    381      * If the value is positive, then it represents a normal day-of-month, and is the
    382      * earliest possible date that the transition can be.
    383      * The date may refer to 29th February which should be treated as 1st March in non-leap years.
    384      * <p>
    385      * If the value is negative, then it represents the number of days back from the
    386      * end of the month where {@code -1} is the last day of the month.
    387      * In this case, the day identified is the latest possible date that the transition can be.
    388      *
    389      * @return the day-of-month indicator, from -28 to 31 excluding 0
    390      */
    391     public int getDayOfMonthIndicator() {
    392         return dom;
    393     }
    394 
    395     /**
    396      * Gets the day-of-week of the transition.
    397      * <p>
    398      * If the rule defines an exact date then this returns null.
    399      * <p>
    400      * If the rule defines a week where the cutover might occur, then this method
    401      * returns the day-of-week that the month-day will be adjusted to.
    402      * If the day is positive then the adjustment is later.
    403      * If the day is negative then the adjustment is earlier.
    404      *
    405      * @return the day-of-week that the transition occurs, null if the rule defines an exact date
    406      */
    407     public DayOfWeek getDayOfWeek() {
    408         return dow;
    409     }
    410 
    411     /**
    412      * Gets the local time of day of the transition which must be checked with
    413      * {@link #isMidnightEndOfDay()}.
    414      * <p>
    415      * The time is converted into an instant using the time definition.
    416      *
    417      * @return the local time of day of the transition, not null
    418      */
    419     public LocalTime getLocalTime() {
    420         return time;
    421     }
    422 
    423     /**
    424      * Is the transition local time midnight at the end of day.
    425      * <p>
    426      * The transition may be represented as occurring at 24:00.
    427      *
    428      * @return whether a local time of midnight is at the start or end of the day
    429      */
    430     public boolean isMidnightEndOfDay() {
    431         return timeEndOfDay;
    432     }
    433 
    434     /**
    435      * Gets the time definition, specifying how to convert the time to an instant.
    436      * <p>
    437      * The local time can be converted to an instant using the standard offset,
    438      * the wall offset or UTC.
    439      *
    440      * @return the time definition, not null
    441      */
    442     public TimeDefinition getTimeDefinition() {
    443         return timeDefinition;
    444     }
    445 
    446     /**
    447      * Gets the standard offset in force at the transition.
    448      *
    449      * @return the standard offset, not null
    450      */
    451     public ZoneOffset getStandardOffset() {
    452         return standardOffset;
    453     }
    454 
    455     /**
    456      * Gets the offset before the transition.
    457      *
    458      * @return the offset before, not null
    459      */
    460     public ZoneOffset getOffsetBefore() {
    461         return offsetBefore;
    462     }
    463 
    464     /**
    465      * Gets the offset after the transition.
    466      *
    467      * @return the offset after, not null
    468      */
    469     public ZoneOffset getOffsetAfter() {
    470         return offsetAfter;
    471     }
    472 
    473     //-----------------------------------------------------------------------
    474     /**
    475      * Creates a transition instance for the specified year.
    476      * <p>
    477      * Calculations are performed using the ISO-8601 chronology.
    478      *
    479      * @param year  the year to create a transition for, not null
    480      * @return the transition instance, not null
    481      */
    482     public ZoneOffsetTransition createTransition(int year) {
    483         LocalDate date;
    484         if (dom < 0) {
    485             date = LocalDate.of(year, month, month.length(IsoChronology.INSTANCE.isLeapYear(year)) + 1 + dom);
    486             if (dow != null) {
    487                 date = date.with(previousOrSame(dow));
    488             }
    489         } else {
    490             date = LocalDate.of(year, month, dom);
    491             if (dow != null) {
    492                 date = date.with(nextOrSame(dow));
    493             }
    494         }
    495         if (timeEndOfDay) {
    496             date = date.plusDays(1);
    497         }
    498         LocalDateTime localDT = LocalDateTime.of(date, time);
    499         LocalDateTime transition = timeDefinition.createDateTime(localDT, standardOffset, offsetBefore);
    500         return new ZoneOffsetTransition(transition, offsetBefore, offsetAfter);
    501     }
    502 
    503     //-----------------------------------------------------------------------
    504     /**
    505      * Checks if this object equals another.
    506      * <p>
    507      * The entire state of the object is compared.
    508      *
    509      * @param otherRule  the other object to compare to, null returns false
    510      * @return true if equal
    511      */
    512     @Override
    513     public boolean equals(Object otherRule) {
    514         if (otherRule == this) {
    515             return true;
    516         }
    517         if (otherRule instanceof ZoneOffsetTransitionRule) {
    518             ZoneOffsetTransitionRule other = (ZoneOffsetTransitionRule) otherRule;
    519             return month == other.month && dom == other.dom && dow == other.dow &&
    520                 timeDefinition == other.timeDefinition &&
    521                 time.equals(other.time) &&
    522                 timeEndOfDay == other.timeEndOfDay &&
    523                 standardOffset.equals(other.standardOffset) &&
    524                 offsetBefore.equals(other.offsetBefore) &&
    525                 offsetAfter.equals(other.offsetAfter);
    526         }
    527         return false;
    528     }
    529 
    530     /**
    531      * Returns a suitable hash code.
    532      *
    533      * @return the hash code
    534      */
    535     @Override
    536     public int hashCode() {
    537         int hash = ((time.toSecondOfDay() + (timeEndOfDay ? 1 : 0)) << 15) +
    538                 (month.ordinal() << 11) + ((dom + 32) << 5) +
    539                 ((dow == null ? 7 : dow.ordinal()) << 2) + (timeDefinition.ordinal());
    540         return hash ^ standardOffset.hashCode() ^
    541                 offsetBefore.hashCode() ^ offsetAfter.hashCode();
    542     }
    543 
    544     //-----------------------------------------------------------------------
    545     /**
    546      * Returns a string describing this object.
    547      *
    548      * @return a string for debugging, not null
    549      */
    550     @Override
    551     public String toString() {
    552         StringBuilder buf = new StringBuilder();
    553         buf.append("TransitionRule[")
    554             .append(offsetBefore.compareTo(offsetAfter) > 0 ? "Gap " : "Overlap ")
    555             .append(offsetBefore).append(" to ").append(offsetAfter).append(", ");
    556         if (dow != null) {
    557             if (dom == -1) {
    558                 buf.append(dow.name()).append(" on or before last day of ").append(month.name());
    559             } else if (dom < 0) {
    560                 buf.append(dow.name()).append(" on or before last day minus ").append(-dom - 1).append(" of ").append(month.name());
    561             } else {
    562                 buf.append(dow.name()).append(" on or after ").append(month.name()).append(' ').append(dom);
    563             }
    564         } else {
    565             buf.append(month.name()).append(' ').append(dom);
    566         }
    567         buf.append(" at ").append(timeEndOfDay ? "24:00" : time.toString())
    568             .append(" ").append(timeDefinition)
    569             .append(", standard offset ").append(standardOffset)
    570             .append(']');
    571         return buf.toString();
    572     }
    573 
    574     //-----------------------------------------------------------------------
    575     /**
    576      * A definition of the way a local time can be converted to the actual
    577      * transition date-time.
    578      * <p>
    579      * Time zone rules are expressed in one of three ways:
    580      * <ul>
    581      * <li>Relative to UTC</li>
    582      * <li>Relative to the standard offset in force</li>
    583      * <li>Relative to the wall offset (what you would see on a clock on the wall)</li>
    584      * </ul>
    585      */
    586     public static enum TimeDefinition {
    587         /** The local date-time is expressed in terms of the UTC offset. */
    588         UTC,
    589         /** The local date-time is expressed in terms of the wall offset. */
    590         WALL,
    591         /** The local date-time is expressed in terms of the standard offset. */
    592         STANDARD;
    593 
    594         /**
    595          * Converts the specified local date-time to the local date-time actually
    596          * seen on a wall clock.
    597          * <p>
    598          * This method converts using the type of this enum.
    599          * The output is defined relative to the 'before' offset of the transition.
    600          * <p>
    601          * The UTC type uses the UTC offset.
    602          * The STANDARD type uses the standard offset.
    603          * The WALL type returns the input date-time.
    604          * The result is intended for use with the wall-offset.
    605          *
    606          * @param dateTime  the local date-time, not null
    607          * @param standardOffset  the standard offset, not null
    608          * @param wallOffset  the wall offset, not null
    609          * @return the date-time relative to the wall/before offset, not null
    610          */
    611         public LocalDateTime createDateTime(LocalDateTime dateTime, ZoneOffset standardOffset, ZoneOffset wallOffset) {
    612             switch (this) {
    613                 case UTC: {
    614                     int difference = wallOffset.getTotalSeconds() - ZoneOffset.UTC.getTotalSeconds();
    615                     return dateTime.plusSeconds(difference);
    616                 }
    617                 case STANDARD: {
    618                     int difference = wallOffset.getTotalSeconds() - standardOffset.getTotalSeconds();
    619                     return dateTime.plusSeconds(difference);
    620                 }
    621                 default:  // WALL
    622                     return dateTime;
    623             }
    624         }
    625     }
    626 
    627 }
    628