/*
* -----------------------------------------------------------------------
* Copyright © 2013-2015 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (ZonalTransition.java) is part of project Time4J.
*
* Time4J is free software: You can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* Time4J is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Time4J. If not, see <http://www.gnu.org/licenses/>.
* -----------------------------------------------------------------------
*/
package net.time4j.tz;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
/**
* <p>Represents the change of a shift of the local time relative to
* POSIX-time in any timezone. </p>
*
* <p>This class contains informations about the global timestamp of the
* transition and the shifts/offsets before and after the transitions.
* A change of a zonal shift can either be caused by special historical
* events and political actions (change of standard time) or by establishing
* <i>daylight saving</i>-rules (change from winter time to summer time and
* reverse - DST). Therefore the total shift {@code getTotalOffset()} is
* always the sum of the parts {@code getStandardOffset()} and
* {@code getDaylightSavingOffset()}. </p>
*
* <p>Shifts are described on the local timeline in seconds. Following
* relationship holds between local time and POSIX-time: </p>
*
* <p>{@code getTotalOffset() = [Local Wall Time] - [POSIX Time]}</p>
*
* <p>A zonal transition induces a gap on the local timeline if the new
* shift is greater than the old shift. And an overlap occurs if the new
* shift is smaller than the old shift. A local time is not defined within
* gaps and ambivalent in overlapping regions. </p>
*
* @author Meno Hochschild
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* <p>Beschreibt einen Wechsel der Verschiebung der lokalen Zeit relativ
* zur POSIX-Zeit in einer Zeitzone. </p>
*
* <p>Diese Klasse enthält neben dem Zeitpunkt des Übergangs auch
* Informationen über die Verschiebung vor und nach dem Wechsel. Ein
* Wechsel der Verschiebung kann entweder durch einmalige historische bzw.
* politische Änderungen (Änderung der Standardverschiebung der
* Zeitzone <i>standard time</i>) oder durch <i>daylight saving</i>-Schemata
* bedingt sein, also die Umstellung von Winterzeit auf Sommerzeit und
* umgekehrt (DST). Somit ist die Gesamtverschiebung {@code getTotalOffset()}
* immer die Summe aus den einzelnen Verschiebungsanteilen
* {@code getStandardOffset()} und {@code getDaylightSavingOffset()}. </p>
*
* <p>Verschiebungen werden grundsätzlich auf dem lokalen Zeitstrahl einer
* Zeitzone beschrieben. Es gilt somit folgende Beziehung zwischen einer
* lokalen Zeit und der POSIX-Zeit (alle Angaben in Sekunden): </p>
*
* <p>{@code getTotalOffset() = [Local Wall Time] - [POSIX Time]}</p>
*
* <p>An einem Übergang tritt eine Lücke auf dem lokalen Zeitstrahl
* auf, wenn die neue Verschiebung größer als die alte Verschiebung
* ist. Und eine Überlappung tritt auf, wenn die neue Verschiebung kleiner
* als die alte Verschiebung ist. Eine lokale Zeitangabe ist auf Lücken
* nicht definiert und auf Überlappungen zweideutig. </p>
*
* @author Meno Hochschild
* @doctags.concurrency {immutable}
*/
public final class ZonalTransition
implements Comparable<ZonalTransition>, Serializable {
//~ Statische Felder/Initialisierungen --------------------------------
private static final long serialVersionUID = 4594838367057225304L;
//~ Instanzvariablen --------------------------------------------------
/**
* @serial POSIX time in seconds since 1970-01-01T00:00:00Z
*/
/*[deutsch]
* @serial POSIX-Zeit in Sekunden seit 1970-01-01T00:00:00Z
*/
private final long posix;
/**
* @serial previous total shift in seconds
*/
/*[deutsch]
* @serial alte Gesamtverschiebung in Sekunden
*/
private final int previous;
/**
* @serial new total shift in seconds
*/
/*[deutsch]
* @serial neue Gesamtverschiebung in Sekunden
*/
private final int total;
/**
* @serial new daylight-saving-shift in seconds (DST)
*/
/*[deutsch]
* @serial neue DST-Verschiebung in Sekunden
*/
private final int dst;
//~ Konstruktoren -----------------------------------------------------
/**
* <p>Creates a new transition between two shifts. </p>
*
* @param posixTime POSIX time of transition
* @param previousOffset previous total shift in seconds
* @param totalOffset new total shift in seconds
* @param daylightSavingOffset DST-shift in seconds
* @throws IllegalArgumentException if the DST-shift is negative or if any
* offset is out of range {@code -18 * 3600 <= total <= 18 * 3600}
* @see net.time4j.scale.UniversalTime#getPosixTime()
* @see ZonalOffset#getIntegralAmount()
*/
/*[deutsch]
* <p>Konstruiert einen neuen Übergang zwischen zwei
* Verschiebungen. </p>
*
* @param posixTime POSIX time of transition
* @param previousOffset previous total shift in seconds
* @param totalOffset new total shift in seconds
* @param daylightSavingOffset DST-shift in seconds
* @throws IllegalArgumentException if the DST-shift is negative or if any
* offset is out of range {@code -18 * 3600 <= total <= 18 * 3600}
* @see net.time4j.scale.UniversalTime#getPosixTime()
* @see ZonalOffset#getIntegralAmount()
*/
public ZonalTransition(
long posixTime,
int previousOffset,
int totalOffset,
int daylightSavingOffset
) {
this.posix = posixTime;
this.previous = previousOffset;
this.total = totalOffset;
this.dst = daylightSavingOffset;
checkRange(previousOffset);
checkRange(totalOffset);
checkDST(daylightSavingOffset);
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Returns the global timestamp of this transition from one shift to
* another as POSIX-timestamp. </p>
*
* @return transition time relative to [1970-01-01T00:00:00] in seconds
* (without leap seconds)
* @see net.time4j.scale.TimeScale#POSIX
*/
/*[deutsch]
* <p>Stellt die Zeit des Übergangs von einer Verschiebung zur anderen
* als POSIX-Zeit dar. </p>
*
* @return transition time relative to [1970-01-01T00:00:00] in seconds
* (without leap seconds)
* @see net.time4j.scale.TimeScale#POSIX
*/
public long getPosixTime() {
return this.posix;
}
/**
* <p>Returns the total shift before this transition. </p>
*
* @return previous total shift in seconds
* @see #getTotalOffset()
* @see ZonalOffset#getIntegralAmount()
*/
/*[deutsch]
* <p>Liefert die Gesamtverschiebung vor diesem Übergang. </p>
*
* @return previous total shift in seconds
* @see #getTotalOffset()
* @see ZonalOffset#getIntegralAmount()
*/
public int getPreviousOffset() {
return this.previous;
}
/**
* <p>Returns the total shift after this transition. </p>
*
* @return new total shift in seconds
* @see #getPreviousOffset()
* @see #getStandardOffset()
* @see #getDaylightSavingOffset()
* @see #isDaylightSaving()
* @see ZonalOffset#getIntegralAmount()
*/
/*[deutsch]
* <p>Liefert die Gesamtverschiebung nach diesem Übergang. </p>
*
* @return new total shift in seconds
* @see #getPreviousOffset()
* @see #getStandardOffset()
* @see #getDaylightSavingOffset()
* @see #isDaylightSaving()
* @see ZonalOffset#getIntegralAmount()
*/
public int getTotalOffset() {
return this.total;
}
/**
* <p>Returns the standard shift after this transition as difference
* between total shift and DST-shift (daylight savings). </p>
*
* <p>Negative standard shifts are related to timezones west for
* Greenwich, positive to timezones east for Greenwich. The addition
* of the standard shift to POSIX-time yields the
* <i>standard local time</i> corresponding to winter time. </p>
*
* @return raw shift in seconds after transition
* @see #getTotalOffset()
* @see #getDaylightSavingOffset()
* @see #isDaylightSaving()
*/
/*[deutsch]
* <p>Liefert die aktuelle Standardverschiebung nach diesem Übergang
* als Differenz zwischen Gesamtverschiebung und DST-Verschiebung. </p>
*
* <p>Negative Standardverschiebungen beziehen sich auf Zeitzonen westlich
* des Nullmeridians von Greenwich, positive auf Zeitzonen östlich
* davon. Die Addition dieser Verschiebung zur POSIX-Zeit ergibt die
* <i>standard local time</i>, die der Winterzeit entspricht. </p>
*
* @return raw shift in seconds after transition
* @see #getTotalOffset()
* @see #getDaylightSavingOffset()
* @see #isDaylightSaving()
*/
public int getStandardOffset() {
return (this.total - this.dst);
}
/**
* <p>Returns the DST-shift (daylight savings) after this transition that is
* the shift induced by change to summer time. </p>
*
* <p>If the method {@code isDaylightSaving()} yields the value {@code false}
* then this method will simply yield {@code 0}. </p>
*
* @return daylight-saving-shift in seconds after transition
* @see #getTotalOffset()
* @see #getStandardOffset()
* @see #isDaylightSaving()
*/
/*[deutsch]
* <p>Liefert die DST-Verschiebung nach dem Übergang, also den durch
* die Sommerzeit induzierten Versatz. </p>
*
* <p>Wenn die Methode {@code isDaylightSaving()} den Wert {@code false}
* ergibt, dann liefert diese Methode einfach nur den Wert {@code 0}. </p>
*
* @return daylight-saving-shift in seconds after transition
* @see #getTotalOffset()
* @see #getStandardOffset()
* @see #isDaylightSaving()
*/
public int getDaylightSavingOffset() {
return this.dst;
}
/**
* <p>Queries if there is any daylight savings after this transition. </p>
*
* @return boolean
* @see #getDaylightSavingOffset()
*/
/*[deutsch]
* <p>Liegt nach diesem Übergang Sommerzeit vor? </p>
*
* @return boolean
* @see #getDaylightSavingOffset()
*/
public boolean isDaylightSaving() {
return (this.dst != 0);
}
/**
* <p>Gets the difference between new and old total shift as
* measure for the size of this transition. </p>
*
* @return change of total shift in seconds (negative in case of overlap)
*/
/*[deutsch]
* <p>Liefert die Differenz zwischen neuer und alter Gesamtverschiebung
* als Maß für die Größe des Übergangs. </p>
*
* @return change of total shift in seconds (negative in case of overlap)
*/
public int getSize() {
return (this.total - this.previous);
}
/**
* <p>Queries if this transition represents a gap on the local timeline
* where local timestamps are invalid. </p>
*
* @return {@code true} if this transition represents a gap (by definition
* the new total shift is bigger than the previous one)
* else {@code false}
*/
/*[deutsch]
* <p>Ist dieser Übergang eine Lücke, während der eine
* lokale Zeitangabe ungültig ist? </p>
*
* @return {@code true} if this transition represents a gap (by definition
* the new total shift is bigger than the previous one)
* else {@code false}
*/
public boolean isGap() {
return (this.total > this.previous);
}
/**
* <p>Queries if this transition represents an overlap on the local
* timeline where local timestamps are ambivalent. </p>
*
* @return {@code true} if this transition represents an overlap (by
* definition the new total shift is smaller than the previous
* one) else {@code false}
*/
/*[deutsch]
* <p>Ist dieser Übergang eine Überlappung, während der
* eine lokale Zeitangabe nicht mehr eindeutig definiert ist? </p>
*
* @return {@code true} if this transition represents an overlap (by
* definition the new total shift is smaller than the previous
* one) else {@code false}
*/
public boolean isOverlap() {
return (this.total < this.previous);
}
/**
* <p>Compares preferrably the timeline order based on the global
* timestamps of transitions, otherwise the total shift and finally
* the DST-shift. </p>
*
* <p>The natural order is consistent with {@code equals()}. </p>
*/
/*[deutsch]
* <p>Beruht bevorzugt auf der zeitlichen Reihenfolge des POSIX-Zeitpunkts
* des Übergangs, sonst auf den Gesamtverschiebungen und zuletzt auf
* der DST-Verschiebung. </p>
*
* <p>Die natürliche Ordnung ist konsistent mit {@code equals()}. </p>
*/
@Override
public int compareTo(ZonalTransition other) {
long delta = (this.posix - other.posix);
if (delta == 0) {
delta = (this.previous - other.previous);
if (delta == 0) {
delta = (this.total - other.total);
if (delta == 0) {
delta = (this.dst - other.dst);
if (delta == 0) {
return 0;
}
}
}
}
return ((delta < 0) ? -1 : 1);
}
/**
* <p>Based on the whole state with global POSIX-timestamp and all
* internal shifts. </p>
*/
/*[deutsch]
* <p>Basiert auf der POSIX-Zeit und allen Verschiebungen. </p>
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof ZonalTransition) {
ZonalTransition that = (ZonalTransition) obj;
if (
(this.posix == that.posix)
&& (this.previous == that.previous)
&& (this.total == that.total)
&& (this.dst == that.dst)
) {
return true;
}
}
return false;
}
/**
* <p>Based on the POSIX-timestamp of the transition. </p>
*/
/*[deutsch]
* <p>Basiert auf der POSIX-Zeit des Übergangs. </p>
*/
@Override
public int hashCode() {
return (int) (this.posix ^ (this.posix >>> 32));
}
/**
* <p>Supports debugging. </p>
*
* @return String
*/
/*[deutsch]
* <p>Unterstützt Debugging-Ausgaben. </p>
*
* @return String
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("[POSIX=");
sb.append(this.posix);
sb.append(", previous-offset=");
sb.append(this.previous);
sb.append(", total-offset=");
sb.append(this.total);
sb.append(", dst-offset=");
sb.append(this.dst);
sb.append(']');
return sb.toString();
}
private static void checkRange(int offset) {
if ((offset < -18 * 3600) || (offset > 18 * 3600)) {
throw new IllegalArgumentException(
"Offset out of range: " + offset);
}
}
private static void checkDST(int dst) {
if (dst < 0) {
throw new IllegalArgumentException("Negative DST: " + dst);
} else if (dst > 18 * 3600) {
throw new IllegalArgumentException("DST out of range: " + dst);
}
}
/**
* @serialData Checks the consistency.
* @param in object input stream
* @throws IOException in any case of inconsistencies
* @throws ClassNotFoundException if class-loading fails
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
try {
checkRange(this.previous);
checkRange(this.total);
checkDST(this.dst);
} catch (IllegalArgumentException iae) {
throw new InvalidObjectException(iae.getMessage());
}
}
}