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.chrono.IsoChronology; 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(IsoChronology.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 }