UNPKG

@js-joda/core

Version:

a date and time library for javascript

290 lines (267 loc) 11 kB
/* * @copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper * @copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos * @license BSD-3-Clause (see LICENSE in the root directory of this source tree) */ import { requireNonNull } from '../assert'; import { IllegalArgumentException } from '../errors'; import { Duration } from '../Duration'; import { LocalDateTime } from '../LocalDateTime'; /** * A transition between two offsets caused by a discontinuity in the local time-line. * * A transition between two offsets is normally the result of a daylight savings cutover. * The discontinuity is normally a gap in spring and an overlap in autumn. * {@link ZoneOffsetTransition} models the transition between the two offsets. * * Gaps occur where there are local date-times that simply do not not exist. * An example would be when the offset changes from `+03:00` to `+04:00`. * This might be described as 'the clocks will move forward one hour tonight at 1am'. * * Overlaps occur where there are local date-times that exist twice. * An example would be when the offset changes from `+04:00` to `+03:00`. * This might be described as 'the clocks will move back one hour tonight at 2am'. * */ export class ZoneOffsetTransition { //----------------------------------------------------------------------- /** * Obtains an instance defining a transition between two offsets. * * Applications should normally obtain an instance from {@link ZoneRules}. * This factory is only intended for use when creating {@link ZoneRules}. * * @param {LocalDateTime} transition - the transition date-time at the transition, which never * actually occurs, expressed local to the before offset, not null * @param {ZoneOffset} offsetBefore - the offset before the transition, not null * @param {ZoneOffset} offsetAfter - the offset at and after the transition, not null * @return {ZoneOffsetTransition} the transition, not null * @throws IllegalArgumentException if {@link offsetBefore} and {@link offsetAfter} * are equal, or {@link transition.getNano} returns non-zero value */ static of(transition, offsetBefore, offsetAfter) { return new ZoneOffsetTransition(transition, offsetBefore, offsetAfter); } /** * Creates an instance defining a transition between two offsets. * Creates an instance from epoch-second if transition is not a LocalDateTimeInstance * * @param {(LocalDateTime \ number)} transition - the transition date-time with the offset before the transition, not null * @param {ZoneOffset} offsetBefore - the offset before the transition, not null * @param {ZoneOffset} offsetAfter - the offset at and after the transition, not null * @private */ constructor(transition, offsetBefore, offsetAfter) { requireNonNull(transition, 'transition'); requireNonNull(offsetBefore, 'offsetBefore'); requireNonNull(offsetAfter, 'offsetAfter'); if (offsetBefore.equals(offsetAfter)) { throw new IllegalArgumentException('Offsets must not be equal'); } if (transition.nano() !== 0) { throw new IllegalArgumentException('Nano-of-second must be zero'); } if(transition instanceof LocalDateTime) { this._transition = transition; } else { this._transition = LocalDateTime.ofEpochSecond(transition, 0, offsetBefore); } this._offsetBefore = offsetBefore; this._offsetAfter = offsetAfter; } //----------------------------------------------------------------------- /** * Gets the transition instant. * * This is the instant of the discontinuity, which is defined as the first * instant that the 'after' offset applies. * * The methods {@link getInstant}, {@link getDateTimeBefore} and {@link getDateTimeAfter} * all represent the same instant. * * @return {Instant} the transition instant, not null */ instant() { return this._transition.toInstant(this._offsetBefore); } /** * Gets the transition instant as an epoch second. * * @return {number} the transition epoch second */ toEpochSecond() { return this._transition.toEpochSecond(this._offsetBefore); } //------------------------------------------------------------------------- /** * Gets the local transition date-time, as would be expressed with the 'before' offset. * * This is the date-time where the discontinuity begins expressed with the 'before' offset. * At this instant, the 'after' offset is actually used, therefore the combination of this * date-time and the 'before' offset will never occur. * * The combination of the 'before' date-time and offset represents the same instant * as the 'after' date-time and offset. * * @return {LocalDateTime} the transition date-time expressed with the before offset, not null */ dateTimeBefore(){ return this._transition; } /** * Gets the local transition date-time, as would be expressed with the 'after' offset. * * This is the first date-time after the discontinuity, when the new offset applies. * * The combination of the 'before' date-time and offset represents the same instant * as the 'after' date-time and offset. * * @return {LocalDateTime} the transition date-time expressed with the after offset, not null */ dateTimeAfter() { return this._transition.plusSeconds(this.durationSeconds()); } /** * Gets the offset before the transition. * * This is the offset in use before the instant of the transition. * * @return {ZoneOffset} the offset before the transition, not null */ offsetBefore() { return this._offsetBefore; } /** * Gets the offset after the transition. * * This is the offset in use on and after the instant of the transition. * * @return {ZoneOffset} the offset after the transition, not null */ offsetAfter() { return this._offsetAfter; } /** * Gets the duration of the transition. * * In most cases, the transition duration is one hour, however this is not always the case. * The duration will be positive for a gap and negative for an overlap. * Time-zones are second-based, so the nanosecond part of the duration will be zero. * * @return {Duration} the duration of the transition, positive for gaps, negative for overlaps */ duration() { return Duration.ofSeconds(this.durationSeconds()); } /** * Gets the duration of the transition in seconds. * * @return {number} the duration in seconds */ durationSeconds() { return this._offsetAfter.totalSeconds() - this._offsetBefore.totalSeconds(); } /** * Does this transition represent a gap in the local time-line. * * Gaps occur where there are local date-times that simply do not not exist. * An example would be when the offset changes from `+01:00` to `+02:00`. * This might be described as 'the clocks will move forward one hour tonight at 1am'. * * @return {boolean} true if this transition is a gap, false if it is an overlap */ isGap() { return this._offsetAfter.totalSeconds() > this._offsetBefore.totalSeconds(); } /** * Does this transition represent a gap in the local time-line. * * Overlaps occur where there are local date-times that exist twice. * An example would be when the offset changes from `+02:00` to `+01:00`. * This might be described as 'the clocks will move back one hour tonight at 2am'. * * @return {boolean} true if this transition is an overlap, false if it is a gap */ isOverlap() { return this._offsetAfter.totalSeconds() < this._offsetBefore.totalSeconds(); } /** * Checks if the specified offset is valid during this transition. * * This checks to see if the given offset will be valid at some point in the transition. * A gap will always return false. * An overlap will return true if the offset is either the before or after offset. * * @param {ZoneOffset} offset - the offset to check, null returns false * @return {boolean} true if the offset is valid during the transition */ isValidOffset(offset) { return this.isGap() ? false : (this._offsetBefore.equals(offset) || this._offsetAfter.equals(offset)); } /** * Gets the valid offsets during this transition. * * A gap will return an empty list, while an overlap will return both offsets. * * @return {ZoneOffset[]} the list of valid offsets */ validOffsets() { if (this.isGap()){ return []; } else { return [this._offsetBefore, this._offsetAfter]; } } //----------------------------------------------------------------------- /** * Compares this transition to another based on the transition instant. * * This compares the instants of each transition. * The offsets are ignored, making this order inconsistent with equals. * * @param {ZoneOffsetTransition} transition - the transition to compare to, not null * @return {number} the comparator value, negative if less, positive if greater */ compareTo(transition) { return this.instant().compareTo(transition.instant()); } //----------------------------------------------------------------------- /** * Checks if this object equals another. * * The entire state of the object is compared. * * @param {*} other - the other object to compare to, null returns false * @return true if equal */ equals(other) { if (other === this) { return true; } if (other instanceof ZoneOffsetTransition) { const d = other; return this._transition.equals(d._transition) && this._offsetBefore.equals(d.offsetBefore()) && this._offsetAfter.equals(d.offsetAfter()); } return false; } /** * Returns a suitable hash code. * * @return {number} the hash code */ hashCode() { return this._transition.hashCode() ^ this._offsetBefore.hashCode() ^ (this._offsetAfter.hashCode()>>>16); } //----------------------------------------------------------------------- /** * Returns a string describing this object. * * @return {string} a string for debugging, not null */ toString() { return `Transition[${this.isGap() ? 'Gap' : 'Overlap' } at ${this._transition.toString()}${this._offsetBefore.toString() } to ${this._offsetAfter}]`; } }