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 */ of( Month month, int dayOfMonthIndicator, DayOfWeek dayOfWeek, LocalTime time, boolean timeEndOfDay, TimeDefinition timeDefnition, ZoneOffset standardOffset, ZoneOffset offsetBefore, ZoneOffset offsetAfter)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 */ ZoneOffsetTransitionRule( Month month, int dayOfMonthIndicator, DayOfWeek dayOfWeek, LocalTime time, boolean timeEndOfDay, TimeDefinition timeDefnition, ZoneOffset standardOffset, ZoneOffset offsetBefore, ZoneOffset offsetAfter)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 */ readObject(ObjectInputStream s)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 */ writeReplace()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 */ writeExternal(DataOutput out)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 */ readExternal(DataInput in)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 */ getMonth()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 */ getDayOfMonthIndicator()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 */ getDayOfWeek()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 */ getLocalTime()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 */ isMidnightEndOfDay()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 */ getTimeDefinition()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 */ getStandardOffset()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 */ getOffsetBefore()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 */ getOffsetAfter()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 */ createTransition(int year)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 equals(Object otherRule)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 hashCode()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 toString()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 */ createDateTime(LocalDateTime dateTime, ZoneOffset standardOffset, ZoneOffset wallOffset)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