1 /*
2 * Copyright (c) 2012, 2019, 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="../../../serialized-form.html#java.time.zone.Ser">dedicated serialized form</a>.
253 * @serialData
254 * Refer to the serialized form of
255 * <a href="../../../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 ZoneOffsetTransitionRule
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 }
633