1 /*
   2  * Copyright (c) 2012, 2018, 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      * @throws IllegalArgumentException if {@code time.getNano()} returns non-zero value
 171      */
 172     public static ZoneOffsetTransitionRule of(
 173             Month month,
 174             int dayOfMonthIndicator,
 175             DayOfWeek dayOfWeek,
 176             LocalTime time,
 177             boolean timeEndOfDay,
 178             TimeDefinition timeDefnition,
 179             ZoneOffset standardOffset,
 180             ZoneOffset offsetBefore,
 181             ZoneOffset offsetAfter) {
 182         Objects.requireNonNull(month, "month");
 183         Objects.requireNonNull(time, "time");
 184         Objects.requireNonNull(timeDefnition, "timeDefnition");
 185         Objects.requireNonNull(standardOffset, "standardOffset");
 186         Objects.requireNonNull(offsetBefore, "offsetBefore");
 187         Objects.requireNonNull(offsetAfter, "offsetAfter");
 188         if (dayOfMonthIndicator < -28 || dayOfMonthIndicator > 31 || dayOfMonthIndicator == 0) {
 189             throw new IllegalArgumentException("Day of month indicator must be between -28 and 31 inclusive excluding zero");
 190         }
 191         if (timeEndOfDay && time.equals(LocalTime.MIDNIGHT) == false) {
 192             throw new IllegalArgumentException("Time must be midnight when end of day flag is true");
 193         }
 194         if (time.getNano() != 0) {
 195             throw new IllegalArgumentException("Time's nano-of-second must be zero");
 196         }
 197         return new ZoneOffsetTransitionRule(month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefnition, standardOffset, offsetBefore, offsetAfter);
 198     }
 199 
 200     /**
 201      * Creates an instance defining the yearly rule to create transitions between two offsets.
 202      *
 203      * @param month  the month of the month-day of the first day of the cutover week, not null
 204      * @param dayOfMonthIndicator  the day of the month-day of the cutover week, positive if the week is that
 205      *  day or later, negative if the week is that day or earlier, counting from the last day of the month,
 206      *  from -28 to 31 excluding 0
 207      * @param dayOfWeek  the required day-of-week, null if the month-day should not be changed
 208      * @param time  the cutover time in the 'before' offset, not null
 209      * @param timeEndOfDay  whether the time is midnight at the end of day
 210      * @param timeDefnition  how to interpret the cutover
 211      * @param standardOffset  the standard offset in force at the cutover, not null
 212      * @param offsetBefore  the offset before the cutover, not null
 213      * @param offsetAfter  the offset after the cutover, not null
 214      * @throws IllegalArgumentException if the day of month indicator is invalid
 215      * @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
 216      */
 217     ZoneOffsetTransitionRule(
 218             Month month,
 219             int dayOfMonthIndicator,
 220             DayOfWeek dayOfWeek,
 221             LocalTime time,
 222             boolean timeEndOfDay,
 223             TimeDefinition timeDefnition,
 224             ZoneOffset standardOffset,
 225             ZoneOffset offsetBefore,
 226             ZoneOffset offsetAfter) {
 227         assert time.getNano() == 0;
 228         this.month = month;
 229         this.dom = (byte) dayOfMonthIndicator;
 230         this.dow = dayOfWeek;
 231         this.time = time;
 232         this.timeEndOfDay = timeEndOfDay;
 233         this.timeDefinition = timeDefnition;
 234         this.standardOffset = standardOffset;
 235         this.offsetBefore = offsetBefore;
 236         this.offsetAfter = offsetAfter;
 237     }
 238 
 239     //-----------------------------------------------------------------------
 240     /**
 241      * Defend against malicious streams.
 242      *
 243      * @param s the stream to read
 244      * @throws InvalidObjectException always
 245      */
 246     private void readObject(ObjectInputStream s) throws InvalidObjectException {
 247         throw new InvalidObjectException("Deserialization via serialization delegate");
 248     }
 249 
 250     /**
 251      * Writes the object using a
 252      * <a href="{@docRoot}/serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.
 253      * @serialData
 254      * Refer to the serialized form of
 255      * <a href="{@docRoot}/serialized-form.html#java.time.zone.ZoneRules">ZoneRules.writeReplace</a>
 256      * for the encoding of epoch seconds and offsets.
 257      * <pre style="font-size:1.0em">{@code
 258      *
 259      *      out.writeByte(3);                // identifies a ZoneOffsetTransition
 260      *      final int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay());
 261      *      final int stdOffset = standardOffset.getTotalSeconds();
 262      *      final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset;
 263      *      final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset;
 264      *      final int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31);
 265      *      final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255);
 266      *      final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3);
 267      *      final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3);
 268      *      final int dowByte = (dow == null ? 0 : dow.getValue());
 269      *      int b = (month.getValue() << 28) +          // 4 bits
 270      *              ((dom + 32) << 22) +                // 6 bits
 271      *              (dowByte << 19) +                   // 3 bits
 272      *              (timeByte << 14) +                  // 5 bits
 273      *              (timeDefinition.ordinal() << 12) +  // 2 bits
 274      *              (stdOffsetByte << 4) +              // 8 bits
 275      *              (beforeByte << 2) +                 // 2 bits
 276      *              afterByte;                          // 2 bits
 277      *      out.writeInt(b);
 278      *      if (timeByte == 31) {
 279      *          out.writeInt(timeSecs);
 280      *      }
 281      *      if (stdOffsetByte == 255) {
 282      *          out.writeInt(stdOffset);
 283      *      }
 284      *      if (beforeByte == 3) {
 285      *          out.writeInt(offsetBefore.getTotalSeconds());
 286      *      }
 287      *      if (afterByte == 3) {
 288      *          out.writeInt(offsetAfter.getTotalSeconds());
 289      *      }
 290      * }
 291      * </pre>
 292      *
 293      * @return the replacing object, not null
 294      */
 295     private Object writeReplace() {
 296         return new Ser(Ser.ZOTRULE, this);
 297     }
 298 
 299     /**
 300      * Writes the state to the stream.
 301      *
 302      * @param out  the output stream, not null
 303      * @throws IOException if an error occurs
 304      */
 305     void writeExternal(DataOutput out) throws IOException {
 306         final int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay());
 307         final int stdOffset = standardOffset.getTotalSeconds();
 308         final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset;
 309         final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset;
 310         final int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31);
 311         final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255);
 312         final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3);
 313         final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3);
 314         final int dowByte = (dow == null ? 0 : dow.getValue());
 315         int b = (month.getValue() << 28) +          // 4 bits
 316                 ((dom + 32) << 22) +                // 6 bits
 317                 (dowByte << 19) +                   // 3 bits
 318                 (timeByte << 14) +                  // 5 bits
 319                 (timeDefinition.ordinal() << 12) +  // 2 bits
 320                 (stdOffsetByte << 4) +              // 8 bits
 321                 (beforeByte << 2) +                 // 2 bits
 322                 afterByte;                          // 2 bits
 323         out.writeInt(b);
 324         if (timeByte == 31) {
 325             out.writeInt(timeSecs);
 326         }
 327         if (stdOffsetByte == 255) {
 328             out.writeInt(stdOffset);
 329         }
 330         if (beforeByte == 3) {
 331             out.writeInt(offsetBefore.getTotalSeconds());
 332         }
 333         if (afterByte == 3) {
 334             out.writeInt(offsetAfter.getTotalSeconds());
 335         }
 336     }
 337 
 338     /**
 339      * Reads the state from the stream.
 340      *
 341      * @param in  the input stream, not null
 342      * @return the created object, not null
 343      * @throws IOException if an error occurs
 344      */
 345     static ZoneOffsetTransitionRule readExternal(DataInput in) throws IOException {
 346         int data = in.readInt();
 347         Month month = Month.of(data >>> 28);
 348         int dom = ((data & (63 << 22)) >>> 22) - 32;
 349         int dowByte = (data & (7 << 19)) >>> 19;
 350         DayOfWeek dow = dowByte == 0 ? null : DayOfWeek.of(dowByte);
 351         int timeByte = (data & (31 << 14)) >>> 14;
 352         TimeDefinition defn = TimeDefinition.values()[(data & (3 << 12)) >>> 12];
 353         int stdByte = (data & (255 << 4)) >>> 4;
 354         int beforeByte = (data & (3 << 2)) >>> 2;
 355         int afterByte = (data & 3);
 356         LocalTime time = (timeByte == 31 ? LocalTime.ofSecondOfDay(in.readInt()) : LocalTime.of(timeByte % 24, 0));
 357         ZoneOffset std = (stdByte == 255 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds((stdByte - 128) * 900));
 358         ZoneOffset before = (beforeByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + beforeByte * 1800));
 359         ZoneOffset after = (afterByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + afterByte * 1800));
 360         return ZoneOffsetTransitionRule.of(month, dom, dow, time, timeByte == 24, defn, std, before, after);
 361     }
 362 
 363     //-----------------------------------------------------------------------
 364     /**
 365      * Gets the month of the transition.
 366      * <p>
 367      * If the rule defines an exact date then the month is the month of that date.
 368      * <p>
 369      * If the rule defines a week where the transition might occur, then the month
 370      * if the month of either the earliest or latest possible date of the cutover.
 371      *
 372      * @return the month of the transition, not null
 373      */
 374     public Month getMonth() {
 375         return month;
 376     }
 377 
 378     /**
 379      * Gets the indicator of the day-of-month of the transition.
 380      * <p>
 381      * If the rule defines an exact date then the day is the month of that date.
 382      * <p>
 383      * If the rule defines a week where the transition might occur, then the day
 384      * defines either the start of the end of the transition week.
 385      * <p>
 386      * If the value is positive, then it represents a normal day-of-month, and is the
 387      * earliest possible date that the transition can be.
 388      * The date may refer to 29th February which should be treated as 1st March in non-leap years.
 389      * <p>
 390      * If the value is negative, then it represents the number of days back from the
 391      * end of the month where {@code -1} is the last day of the month.
 392      * In this case, the day identified is the latest possible date that the transition can be.
 393      *
 394      * @return the day-of-month indicator, from -28 to 31 excluding 0
 395      */
 396     public int getDayOfMonthIndicator() {
 397         return dom;
 398     }
 399 
 400     /**
 401      * Gets the day-of-week of the transition.
 402      * <p>
 403      * If the rule defines an exact date then this returns null.
 404      * <p>
 405      * If the rule defines a week where the cutover might occur, then this method
 406      * returns the day-of-week that the month-day will be adjusted to.
 407      * If the day is positive then the adjustment is later.
 408      * If the day is negative then the adjustment is earlier.
 409      *
 410      * @return the day-of-week that the transition occurs, null if the rule defines an exact date
 411      */
 412     public DayOfWeek getDayOfWeek() {
 413         return dow;
 414     }
 415 
 416     /**
 417      * Gets the local time of day of the transition which must be checked with
 418      * {@link #isMidnightEndOfDay()}.
 419      * <p>
 420      * The time is converted into an instant using the time definition.
 421      *
 422      * @return the local time of day of the transition, not null
 423      */
 424     public LocalTime getLocalTime() {
 425         return time;
 426     }
 427 
 428     /**
 429      * Is the transition local time midnight at the end of day.
 430      * <p>
 431      * The transition may be represented as occurring at 24:00.
 432      *
 433      * @return whether a local time of midnight is at the start or end of the day
 434      */
 435     public boolean isMidnightEndOfDay() {
 436         return timeEndOfDay;
 437     }
 438 
 439     /**
 440      * Gets the time definition, specifying how to convert the time to an instant.
 441      * <p>
 442      * The local time can be converted to an instant using the standard offset,
 443      * the wall offset or UTC.
 444      *
 445      * @return the time definition, not null
 446      */
 447     public TimeDefinition getTimeDefinition() {
 448         return timeDefinition;
 449     }
 450 
 451     /**
 452      * Gets the standard offset in force at the transition.
 453      *
 454      * @return the standard offset, not null
 455      */
 456     public ZoneOffset getStandardOffset() {
 457         return standardOffset;
 458     }
 459 
 460     /**
 461      * Gets the offset before the transition.
 462      *
 463      * @return the offset before, not null
 464      */
 465     public ZoneOffset getOffsetBefore() {
 466         return offsetBefore;
 467     }
 468 
 469     /**
 470      * Gets the offset after the transition.
 471      *
 472      * @return the offset after, not null
 473      */
 474     public ZoneOffset getOffsetAfter() {
 475         return offsetAfter;
 476     }
 477 
 478     //-----------------------------------------------------------------------
 479     /**
 480      * Creates a transition instance for the specified year.
 481      * <p>
 482      * Calculations are performed using the ISO-8601 chronology.
 483      *
 484      * @param year  the year to create a transition for, not null
 485      * @return the transition instance, not null
 486      */
 487     public ZoneOffsetTransition createTransition(int year) {
 488         LocalDate date;
 489         if (dom < 0) {
 490             date = LocalDate.of(year, month, month.length(IsoChronology.INSTANCE.isLeapYear(year)) + 1 + dom);
 491             if (dow != null) {
 492                 date = date.with(previousOrSame(dow));
 493             }
 494         } else {
 495             date = LocalDate.of(year, month, dom);
 496             if (dow != null) {
 497                 date = date.with(nextOrSame(dow));
 498             }
 499         }
 500         if (timeEndOfDay) {
 501             date = date.plusDays(1);
 502         }
 503         LocalDateTime localDT = LocalDateTime.of(date, time);
 504         LocalDateTime transition = timeDefinition.createDateTime(localDT, standardOffset, offsetBefore);
 505         return new ZoneOffsetTransition(transition, offsetBefore, offsetAfter);
 506     }
 507 
 508     //-----------------------------------------------------------------------
 509     /**
 510      * Checks if this object equals another.
 511      * <p>
 512      * The entire state of the object is compared.
 513      *
 514      * @param otherRule  the other object to compare to, null returns false
 515      * @return true if equal
 516      */
 517     @Override
 518     public boolean equals(Object otherRule) {
 519         if (otherRule == this) {
 520             return true;
 521         }
 522         if (otherRule instanceof ZoneOffsetTransitionRule) {
 523             ZoneOffsetTransitionRule other = (ZoneOffsetTransitionRule) otherRule;
 524             return month == other.month && dom == other.dom && dow == other.dow &&
 525                 timeDefinition == other.timeDefinition &&
 526                 time.equals(other.time) &&
 527                 timeEndOfDay == other.timeEndOfDay &&
 528                 standardOffset.equals(other.standardOffset) &&
 529                 offsetBefore.equals(other.offsetBefore) &&
 530                 offsetAfter.equals(other.offsetAfter);
 531         }
 532         return false;
 533     }
 534 
 535     /**
 536      * Returns a suitable hash code.
 537      *
 538      * @return the hash code
 539      */
 540     @Override
 541     public int hashCode() {
 542         int hash = ((time.toSecondOfDay() + (timeEndOfDay ? 1 : 0)) << 15) +
 543                 (month.ordinal() << 11) + ((dom + 32) << 5) +
 544                 ((dow == null ? 7 : dow.ordinal()) << 2) + (timeDefinition.ordinal());
 545         return hash ^ standardOffset.hashCode() ^
 546                 offsetBefore.hashCode() ^ offsetAfter.hashCode();
 547     }
 548 
 549     //-----------------------------------------------------------------------
 550     /**
 551      * Returns a string describing this object.
 552      *
 553      * @return a string for debugging, not null
 554      */
 555     @Override
 556     public String toString() {
 557         StringBuilder buf = new StringBuilder();
 558         buf.append("TransitionRule[")
 559             .append(offsetBefore.compareTo(offsetAfter) > 0 ? "Gap " : "Overlap ")
 560             .append(offsetBefore).append(" to ").append(offsetAfter).append(", ");
 561         if (dow != null) {
 562             if (dom == -1) {
 563                 buf.append(dow.name()).append(" on or before last day of ").append(month.name());
 564             } else if (dom < 0) {
 565                 buf.append(dow.name()).append(" on or before last day minus ").append(-dom - 1).append(" of ").append(month.name());
 566             } else {
 567                 buf.append(dow.name()).append(" on or after ").append(month.name()).append(' ').append(dom);
 568             }
 569         } else {
 570             buf.append(month.name()).append(' ').append(dom);
 571         }
 572         buf.append(" at ").append(timeEndOfDay ? "24:00" : time.toString())
 573             .append(" ").append(timeDefinition)
 574             .append(", standard offset ").append(standardOffset)
 575             .append(']');
 576         return buf.toString();
 577     }
 578 
 579     //-----------------------------------------------------------------------
 580     /**
 581      * A definition of the way a local time can be converted to the actual
 582      * transition date-time.
 583      * <p>
 584      * Time zone rules are expressed in one of three ways:
 585      * <ul>
 586      * <li>Relative to UTC</li>
 587      * <li>Relative to the standard offset in force</li>
 588      * <li>Relative to the wall offset (what you would see on a clock on the wall)</li>
 589      * </ul>
 590      */
 591     public static enum TimeDefinition {
 592         /** The local date-time is expressed in terms of the UTC offset. */
 593         UTC,
 594         /** The local date-time is expressed in terms of the wall offset. */
 595         WALL,
 596         /** The local date-time is expressed in terms of the standard offset. */
 597         STANDARD;
 598 
 599         /**
 600          * Converts the specified local date-time to the local date-time actually
 601          * seen on a wall clock.
 602          * <p>
 603          * This method converts using the type of this enum.
 604          * The output is defined relative to the 'before' offset of the transition.
 605          * <p>
 606          * The UTC type uses the UTC offset.
 607          * The STANDARD type uses the standard offset.
 608          * The WALL type returns the input date-time.
 609          * The result is intended for use with the wall-offset.
 610          *
 611          * @param dateTime  the local date-time, not null
 612          * @param standardOffset  the standard offset, not null
 613          * @param wallOffset  the wall offset, not null
 614          * @return the date-time relative to the wall/before offset, not null
 615          */
 616         public LocalDateTime createDateTime(LocalDateTime dateTime, ZoneOffset standardOffset, ZoneOffset wallOffset) {
 617             switch (this) {
 618                 case UTC: {
 619                     int difference = wallOffset.getTotalSeconds() - ZoneOffset.UTC.getTotalSeconds();
 620                     return dateTime.plusSeconds(difference);
 621                 }
 622                 case STANDARD: {
 623                     int difference = wallOffset.getTotalSeconds() - standardOffset.getTotalSeconds();
 624                     return dateTime.plusSeconds(difference);
 625                 }
 626                 default:  // WALL
 627                     return dateTime;
 628             }
 629         }
 630     }
 631 
 632 }