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.Adjusters.nextOrSame;
  65 import static java.time.temporal.Adjusters.previousOrSame;
  66 
  67 import java.io.DataInput;
  68 import java.io.DataOutput;
  69 import java.io.IOException;
  70 import java.io.Serializable;
  71 import java.time.DayOfWeek;
  72 import java.time.LocalDate;
  73 import java.time.LocalDateTime;
  74 import java.time.LocalTime;
  75 import java.time.Month;
  76 import java.time.ZoneOffset;
  77 import java.time.temporal.ISOChrono;
  78 import java.util.Objects;
  79 
  80 /**
  81  * A rule expressing how to create a transition.
  82  * <p>
  83  * This class allows rules for identifying future transitions to be expressed.
  84  * A rule might be written in many forms:
  85  * <p><ul>
  86  * <li>the 16th March
  87  * <li>the Sunday on or after the 16th March
  88  * <li>the Sunday on or before the 16th March
  89  * <li>the last Sunday in February
  90  * </ul><p>
  91  * These different rule types can be expressed and queried.
  92  *
  93  * <h3>Specification for implementors</h3>
  94  * This class is immutable and thread-safe.
  95  *
  96  * @since 1.8
  97  */
  98 public final class ZoneOffsetTransitionRule implements Serializable {
  99 
 100     /**
 101      * Serialization version.
 102      */
 103     private static final long serialVersionUID = 6889046316657758795L;
 104 
 105     /**
 106      * The month of the month-day of the first day of the cutover week.
 107      * The actual date will be adjusted by the dowChange field.
 108      */
 109     private final Month month;
 110     /**
 111      * The day-of-month of the month-day of the cutover week.
 112      * If positive, it is the start of the week where the cutover can occur.
 113      * If negative, it represents the end of the week where cutover can occur.
 114      * The value is the number of days from the end of the month, such that
 115      * {@code -1} is the last day of the month, {@code -2} is the second
 116      * to last day, and so on.
 117      */
 118     private final byte dom;
 119     /**
 120      * The cutover day-of-week, null to retain the day-of-month.
 121      */
 122     private final DayOfWeek dow;
 123     /**
 124      * The cutover time in the 'before' offset.
 125      */
 126     private final LocalTime time;
 127     /**
 128      * Whether the cutover time is midnight at the end of day.
 129      */
 130     private final boolean timeEndOfDay;
 131     /**
 132      * The definition of how the local time should be interpreted.
 133      */
 134     private final TimeDefinition timeDefinition;
 135     /**
 136      * The standard offset at the cutover.
 137      */
 138     private final ZoneOffset standardOffset;
 139     /**
 140      * The offset before the cutover.
 141      */
 142     private final ZoneOffset offsetBefore;
 143     /**
 144      * The offset after the cutover.
 145      */
 146     private final ZoneOffset offsetAfter;
 147 
 148     /**
 149      * Obtains an instance defining the yearly rule to create transitions between two offsets.
 150      * <p>
 151      * Applications should normally obtain an instance from {@link ZoneRules}.
 152      * This factory is only intended for use when creating {@link ZoneRules}.
 153      *
 154      * @param month  the month of the month-day of the first day of the cutover week, not null
 155      * @param dayOfMonthIndicator  the day of the month-day of the cutover week, positive if the week is that
 156      *  day or later, negative if the week is that day or earlier, counting from the last day of the month,
 157      *  from -28 to 31 excluding 0
 158      * @param dayOfWeek  the required day-of-week, null if the month-day should not be changed
 159      * @param time  the cutover time in the 'before' offset, not null
 160      * @param timeEndOfDay  whether the time is midnight at the end of day
 161      * @param timeDefnition  how to interpret the cutover
 162      * @param standardOffset  the standard offset in force at the cutover, not null
 163      * @param offsetBefore  the offset before the cutover, not null
 164      * @param offsetAfter  the offset after the cutover, not null
 165      * @return the rule, not null
 166      * @throws IllegalArgumentException if the day of month indicator is invalid
 167      * @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
 168      */
 169     public static ZoneOffsetTransitionRule of(
 170             Month month,
 171             int dayOfMonthIndicator,
 172             DayOfWeek dayOfWeek,
 173             LocalTime time,
 174             boolean timeEndOfDay,
 175             TimeDefinition timeDefnition,
 176             ZoneOffset standardOffset,
 177             ZoneOffset offsetBefore,
 178             ZoneOffset offsetAfter) {
 179         Objects.requireNonNull(month, "month");
 180         Objects.requireNonNull(time, "time");
 181         Objects.requireNonNull(timeDefnition, "timeDefnition");
 182         Objects.requireNonNull(standardOffset, "standardOffset");
 183         Objects.requireNonNull(offsetBefore, "offsetBefore");
 184         Objects.requireNonNull(offsetAfter, "offsetAfter");
 185         if (dayOfMonthIndicator < -28 || dayOfMonthIndicator > 31 || dayOfMonthIndicator == 0) {
 186             throw new IllegalArgumentException("Day of month indicator must be between -28 and 31 inclusive excluding zero");
 187         }
 188         if (timeEndOfDay && time.equals(LocalTime.MIDNIGHT) == false) {
 189             throw new IllegalArgumentException("Time must be midnight when end of day flag is true");
 190         }
 191         return new ZoneOffsetTransitionRule(month, dayOfMonthIndicator, dayOfWeek, time, timeEndOfDay, timeDefnition, standardOffset, offsetBefore, offsetAfter);
 192     }
 193 
 194     /**
 195      * Creates an instance defining the yearly rule to create transitions between two offsets.
 196      *
 197      * @param month  the month of the month-day of the first day of the cutover week, not null
 198      * @param dayOfMonthIndicator  the day of the month-day of the cutover week, positive if the week is that
 199      *  day or later, negative if the week is that day or earlier, counting from the last day of the month,
 200      *  from -28 to 31 excluding 0
 201      * @param dayOfWeek  the required day-of-week, null if the month-day should not be changed
 202      * @param time  the cutover time in the 'before' offset, not null
 203      * @param timeEndOfDay  whether the time is midnight at the end of day
 204      * @param timeDefnition  how to interpret the cutover
 205      * @param standardOffset  the standard offset in force at the cutover, not null
 206      * @param offsetBefore  the offset before the cutover, not null
 207      * @param offsetAfter  the offset after the cutover, not null
 208      * @throws IllegalArgumentException if the day of month indicator is invalid
 209      * @throws IllegalArgumentException if the end of day flag is true when the time is not midnight
 210      */
 211     ZoneOffsetTransitionRule(
 212             Month month,
 213             int dayOfMonthIndicator,
 214             DayOfWeek dayOfWeek,
 215             LocalTime time,
 216             boolean timeEndOfDay,
 217             TimeDefinition timeDefnition,
 218             ZoneOffset standardOffset,
 219             ZoneOffset offsetBefore,
 220             ZoneOffset offsetAfter) {
 221         this.month = month;
 222         this.dom = (byte) dayOfMonthIndicator;
 223         this.dow = dayOfWeek;
 224         this.time = time;
 225         this.timeEndOfDay = timeEndOfDay;
 226         this.timeDefinition = timeDefnition;
 227         this.standardOffset = standardOffset;
 228         this.offsetBefore = offsetBefore;
 229         this.offsetAfter = offsetAfter;
 230     }
 231 
 232     //-----------------------------------------------------------------------
 233     /**
 234      * Uses a serialization delegate.
 235      *
 236      * @return the replacing object, not null
 237      */
 238     private Object writeReplace() {
 239         return new Ser(Ser.ZOTRULE, this);
 240     }
 241 
 242     /**
 243      * Writes the state to the stream.
 244      *
 245      * @param out  the output stream, not null
 246      * @throws IOException if an error occurs
 247      */
 248     void writeExternal(DataOutput out) throws IOException {
 249         final int timeSecs = (timeEndOfDay ? 86400 : time.toSecondOfDay());
 250         final int stdOffset = standardOffset.getTotalSeconds();
 251         final int beforeDiff = offsetBefore.getTotalSeconds() - stdOffset;
 252         final int afterDiff = offsetAfter.getTotalSeconds() - stdOffset;
 253         final int timeByte = (timeSecs % 3600 == 0 ? (timeEndOfDay ? 24 : time.getHour()) : 31);
 254         final int stdOffsetByte = (stdOffset % 900 == 0 ? stdOffset / 900 + 128 : 255);
 255         final int beforeByte = (beforeDiff == 0 || beforeDiff == 1800 || beforeDiff == 3600 ? beforeDiff / 1800 : 3);
 256         final int afterByte = (afterDiff == 0 || afterDiff == 1800 || afterDiff == 3600 ? afterDiff / 1800 : 3);
 257         final int dowByte = (dow == null ? 0 : dow.getValue());
 258         int b = (month.getValue() << 28) +          // 4 bits
 259                 ((dom + 32) << 22) +                // 6 bits
 260                 (dowByte << 19) +                   // 3 bits
 261                 (timeByte << 14) +                  // 5 bits
 262                 (timeDefinition.ordinal() << 12) +  // 2 bits
 263                 (stdOffsetByte << 4) +              // 8 bits
 264                 (beforeByte << 2) +                 // 2 bits
 265                 afterByte;                          // 2 bits
 266         out.writeInt(b);
 267         if (timeByte == 31) {
 268             out.writeInt(timeSecs);
 269         }
 270         if (stdOffsetByte == 255) {
 271             out.writeInt(stdOffset);
 272         }
 273         if (beforeByte == 3) {
 274             out.writeInt(offsetBefore.getTotalSeconds());
 275         }
 276         if (afterByte == 3) {
 277             out.writeInt(offsetAfter.getTotalSeconds());
 278         }
 279     }
 280 
 281     /**
 282      * Reads the state from the stream.
 283      *
 284      * @param in  the input stream, not null
 285      * @return the created object, not null
 286      * @throws IOException if an error occurs
 287      */
 288     static ZoneOffsetTransitionRule readExternal(DataInput in) throws IOException {
 289         int data = in.readInt();
 290         Month month = Month.of(data >>> 28);
 291         int dom = ((data & (63 << 22)) >>> 22) - 32;
 292         int dowByte = (data & (7 << 19)) >>> 19;
 293         DayOfWeek dow = dowByte == 0 ? null : DayOfWeek.of(dowByte);
 294         int timeByte = (data & (31 << 14)) >>> 14;
 295         TimeDefinition defn = TimeDefinition.values()[(data & (3 << 12)) >>> 12];
 296         int stdByte = (data & (255 << 4)) >>> 4;
 297         int beforeByte = (data & (3 << 2)) >>> 2;
 298         int afterByte = (data & 3);
 299         LocalTime time = (timeByte == 31 ? LocalTime.ofSecondOfDay(in.readInt()) : LocalTime.of(timeByte % 24, 0));
 300         ZoneOffset std = (stdByte == 255 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds((stdByte - 128) * 900));
 301         ZoneOffset before = (beforeByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + beforeByte * 1800));
 302         ZoneOffset after = (afterByte == 3 ? ZoneOffset.ofTotalSeconds(in.readInt()) : ZoneOffset.ofTotalSeconds(std.getTotalSeconds() + afterByte * 1800));
 303         return ZoneOffsetTransitionRule.of(month, dom, dow, time, timeByte == 24, defn, std, before, after);
 304     }
 305 
 306     //-----------------------------------------------------------------------
 307     /**
 308      * Gets the month of the transition.
 309      * <p>
 310      * If the rule defines an exact date then the month is the month of that date.
 311      * <p>
 312      * If the rule defines a week where the transition might occur, then the month
 313      * if the month of either the earliest or latest possible date of the cutover.
 314      *
 315      * @return the month of the transition, not null
 316      */
 317     public Month getMonth() {
 318         return month;
 319     }
 320 
 321     /**
 322      * Gets the indicator of the day-of-month of the transition.
 323      * <p>
 324      * If the rule defines an exact date then the day is the month of that date.
 325      * <p>
 326      * If the rule defines a week where the transition might occur, then the day
 327      * defines either the start of the end of the transition week.
 328      * <p>
 329      * If the value is positive, then it represents a normal day-of-month, and is the
 330      * earliest possible date that the transition can be.
 331      * The date may refer to 29th February which should be treated as 1st March in non-leap years.
 332      * <p>
 333      * If the value is negative, then it represents the number of days back from the
 334      * end of the month where {@code -1} is the last day of the month.
 335      * In this case, the day identified is the latest possible date that the transition can be.
 336      *
 337      * @return the day-of-month indicator, from -28 to 31 excluding 0
 338      */
 339     public int getDayOfMonthIndicator() {
 340         return dom;
 341     }
 342 
 343     /**
 344      * Gets the day-of-week of the transition.
 345      * <p>
 346      * If the rule defines an exact date then this returns null.
 347      * <p>
 348      * If the rule defines a week where the cutover might occur, then this method
 349      * returns the day-of-week that the month-day will be adjusted to.
 350      * If the day is positive then the adjustment is later.
 351      * If the day is negative then the adjustment is earlier.
 352      *
 353      * @return the day-of-week that the transition occurs, null if the rule defines an exact date
 354      */
 355     public DayOfWeek getDayOfWeek() {
 356         return dow;
 357     }
 358 
 359     /**
 360      * Gets the local time of day of the transition which must be checked with
 361      * {@link #isMidnightEndOfDay()}.
 362      * <p>
 363      * The time is converted into an instant using the time definition.
 364      *
 365      * @return the local time of day of the transition, not null
 366      */
 367     public LocalTime getLocalTime() {
 368         return time;
 369     }
 370 
 371     /**
 372      * Is the transition local time midnight at the end of day.
 373      * <p>
 374      * The transition may be represented as occurring at 24:00.
 375      *
 376      * @return whether a local time of midnight is at the start or end of the day
 377      */
 378     public boolean isMidnightEndOfDay() {
 379         return timeEndOfDay;
 380     }
 381 
 382     /**
 383      * Gets the time definition, specifying how to convert the time to an instant.
 384      * <p>
 385      * The local time can be converted to an instant using the standard offset,
 386      * the wall offset or UTC.
 387      *
 388      * @return the time definition, not null
 389      */
 390     public TimeDefinition getTimeDefinition() {
 391         return timeDefinition;
 392     }
 393 
 394     /**
 395      * Gets the standard offset in force at the transition.
 396      *
 397      * @return the standard offset, not null
 398      */
 399     public ZoneOffset getStandardOffset() {
 400         return standardOffset;
 401     }
 402 
 403     /**
 404      * Gets the offset before the transition.
 405      *
 406      * @return the offset before, not null
 407      */
 408     public ZoneOffset getOffsetBefore() {
 409         return offsetBefore;
 410     }
 411 
 412     /**
 413      * Gets the offset after the transition.
 414      *
 415      * @return the offset after, not null
 416      */
 417     public ZoneOffset getOffsetAfter() {
 418         return offsetAfter;
 419     }
 420 
 421     //-----------------------------------------------------------------------
 422     /**
 423      * Creates a transition instance for the specified year.
 424      * <p>
 425      * Calculations are performed using the ISO-8601 chronology.
 426      *
 427      * @param year  the year to create a transition for, not null
 428      * @return the transition instance, not null
 429      */
 430     public ZoneOffsetTransition createTransition(int year) {
 431         LocalDate date;
 432         if (dom < 0) {
 433             date = LocalDate.of(year, month, month.length(ISOChrono.INSTANCE.isLeapYear(year)) + 1 + dom);
 434             if (dow != null) {
 435                 date = date.with(previousOrSame(dow));
 436             }
 437         } else {
 438             date = LocalDate.of(year, month, dom);
 439             if (dow != null) {
 440                 date = date.with(nextOrSame(dow));
 441             }
 442         }
 443         if (timeEndOfDay) {
 444             date = date.plusDays(1);
 445         }
 446         LocalDateTime localDT = LocalDateTime.of(date, time);
 447         LocalDateTime transition = timeDefinition.createDateTime(localDT, standardOffset, offsetBefore);
 448         return new ZoneOffsetTransition(transition, offsetBefore, offsetAfter);
 449     }
 450 
 451     //-----------------------------------------------------------------------
 452     /**
 453      * Checks if this object equals another.
 454      * <p>
 455      * The entire state of the object is compared.
 456      *
 457      * @param otherRule  the other object to compare to, null returns false
 458      * @return true if equal
 459      */
 460     @Override
 461     public boolean equals(Object otherRule) {
 462         if (otherRule == this) {
 463             return true;
 464         }
 465         if (otherRule instanceof ZoneOffsetTransitionRule) {
 466             ZoneOffsetTransitionRule other = (ZoneOffsetTransitionRule) otherRule;
 467             return month == other.month && dom == other.dom && dow == other.dow &&
 468                 timeDefinition == other.timeDefinition &&
 469                 time.equals(other.time) &&
 470                 timeEndOfDay == other.timeEndOfDay &&
 471                 standardOffset.equals(other.standardOffset) &&
 472                 offsetBefore.equals(other.offsetBefore) &&
 473                 offsetAfter.equals(other.offsetAfter);
 474         }
 475         return false;
 476     }
 477 
 478     /**
 479      * Returns a suitable hash code.
 480      *
 481      * @return the hash code
 482      */
 483     @Override
 484     public int hashCode() {
 485         int hash = ((time.toSecondOfDay() + (timeEndOfDay ? 1 : 0)) << 15) +
 486                 (month.ordinal() << 11) + ((dom + 32) << 5) +
 487                 ((dow == null ? 7 : dow.ordinal()) << 2) + (timeDefinition.ordinal());
 488         return hash ^ standardOffset.hashCode() ^
 489                 offsetBefore.hashCode() ^ offsetAfter.hashCode();
 490     }
 491 
 492     //-----------------------------------------------------------------------
 493     /**
 494      * Returns a string describing this object.
 495      *
 496      * @return a string for debugging, not null
 497      */
 498     @Override
 499     public String toString() {
 500         StringBuilder buf = new StringBuilder();
 501         buf.append("TransitionRule[")
 502             .append(offsetBefore.compareTo(offsetAfter) > 0 ? "Gap " : "Overlap ")
 503             .append(offsetBefore).append(" to ").append(offsetAfter).append(", ");
 504         if (dow != null) {
 505             if (dom == -1) {
 506                 buf.append(dow.name()).append(" on or before last day of ").append(month.name());
 507             } else if (dom < 0) {
 508                 buf.append(dow.name()).append(" on or before last day minus ").append(-dom - 1).append(" of ").append(month.name());
 509             } else {
 510                 buf.append(dow.name()).append(" on or after ").append(month.name()).append(' ').append(dom);
 511             }
 512         } else {
 513             buf.append(month.name()).append(' ').append(dom);
 514         }
 515         buf.append(" at ").append(timeEndOfDay ? "24:00" : time.toString())
 516             .append(" ").append(timeDefinition)
 517             .append(", standard offset ").append(standardOffset)
 518             .append(']');
 519         return buf.toString();
 520     }
 521 
 522     //-----------------------------------------------------------------------
 523     /**
 524      * A definition of the way a local time can be converted to the actual
 525      * transition date-time.
 526      * <p>
 527      * Time zone rules are expressed in one of three ways:
 528      * <p><ul>
 529      * <li>Relative to UTC</li>
 530      * <li>Relative to the standard offset in force</li>
 531      * <li>Relative to the wall offset (what you would see on a clock on the wall)</li>
 532      * </ul><p>
 533      */
 534     public static enum TimeDefinition {
 535         /** The local date-time is expressed in terms of the UTC offset. */
 536         UTC,
 537         /** The local date-time is expressed in terms of the wall offset. */
 538         WALL,
 539         /** The local date-time is expressed in terms of the standard offset. */
 540         STANDARD;
 541 
 542         /**
 543          * Converts the specified local date-time to the local date-time actually
 544          * seen on a wall clock.
 545          * <p>
 546          * This method converts using the type of this enum.
 547          * The output is defined relative to the 'before' offset of the transition.
 548          * <p>
 549          * The UTC type uses the UTC offset.
 550          * The STANDARD type uses the standard offset.
 551          * The WALL type returns the input date-time.
 552          * The result is intended for use with the wall-offset.
 553          *
 554          * @param dateTime  the local date-time, not null
 555          * @param standardOffset  the standard offset, not null
 556          * @param wallOffset  the wall offset, not null
 557          * @return the date-time relative to the wall/before offset, not null
 558          */
 559         public LocalDateTime createDateTime(LocalDateTime dateTime, ZoneOffset standardOffset, ZoneOffset wallOffset) {
 560             switch (this) {
 561                 case UTC: {
 562                     int difference = wallOffset.getTotalSeconds() - ZoneOffset.UTC.getTotalSeconds();
 563                     return dateTime.plusSeconds(difference);
 564                 }
 565                 case STANDARD: {
 566                     int difference = wallOffset.getTotalSeconds() - standardOffset.getTotalSeconds();
 567                     return dateTime.plusSeconds(difference);
 568                 }
 569                 default:  // WALL
 570                     return dateTime;
 571             }
 572         }
 573     }
 574 
 575 }