net.time4j.scale.LeapSeconds Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2013-2015 Meno Hochschild,
* -----------------------------------------------------------------------
* This file (LeapSeconds.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 .
* -----------------------------------------------------------------------
*/
package net.time4j.scale;
import net.time4j.base.GregorianDate;
import net.time4j.base.GregorianMath;
import net.time4j.base.MathUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Holds all leap seconds occurred since the official start of UTC in
* 1972.
*
* The source is either an implementation of the SPI-interface
* {@code Provider} loaded by a {@code ServiceLoader} or an internal
* standard implementation of {@code Provider} which accesses the file
* "leapseconds.data". This resource file must be in the
* classpath (in folder data). It has the format of a CSV-ASCII-text
* which has two columns separated by comma. The first column denotes
* the calendar day after the leap second shift in ISO-8601-format (for
* example 1972-07-01). The second column determines the sign of the
* leap second (+/-).
*
* The source will mainly be loaded by the context classloader else
* by application classloader. If there is no source at all then Time4J
* assumes that leap seconds shall not be used.
*
* The system property "time4j.scale.leapseconds.suppressed"
* determines if leap seconds shall be active at all. If this system
* property has the value {@code true} then this class will never
* register any leap seconds equal if the underlying sources are filled
* or not. Furthermore, the system property
* "time4j.scale.leapseconds.final" determines if leap seconds
* are only registered at system start or if new ones can be lazily
* registered at runtime using the methods {@code registerXYZ()}.
* Setting one of both properties can improve the performance.
*
* @author Meno Hochschild
* @doctags.concurrency
*/
/*[deutsch]
* Ermittelt alle seit dem offiziellen Start von UTC 1972 aufgetretenen
* Schaltsekunden.
*
* Als Quelle dient entweder eine über einen {@code ServiceLoader}
* gefundene Implementierung des SPI-Interface {@code Provider} oder
* bei Nichtvorhandensein eine interne Standard-Implementierung, die auf
* die Datei "leapseconds.data" zugreift. Diese Datei muß
* im Klassenpfad liegen (im data-Ordner). Sie hat das Format einer
* CSV-ASCII-Textdatei, worin zwei Spalten mit Komma getrennt vorkommen.
* Die erste Spalte definiert den Tag nach der Umstellung im ISO-8601-Format
* als reines Datum ohne Uhrzeitanteil (z.B. 1972-07-01). Die zweite Spalte
* repräsentiert das Vorzeichen der Schaltsekunde (+/-).
*
* Geladen wird die Quelle bevorzugt über den Kontext-ClassLoader.
* Wird die Quelle nicht gefunden, so wird angenommen, daß keine
* Schaltsekunden verwendet werden sollen.
*
* Die System-Property "time4j.scale.leapseconds.suppressed"
* entscheidet, ob Schaltsekunden überhaupt aktiviert sind. Wenn diese
* System-Property den Wert {@code true} hat, wird diese Klasse niemals
* Schaltsekunden registrieren, gleichgültig, ob die zugrundeliegenden
* Quellen gefüllt sind. Daneben gibt es noch die System-Property
* "time4j.scale.leapseconds.final", die festlegt, ob Schaltsekunden
* nur zum Systemstart registriert werden oder auch nachträglich zur
* Laufzeit mittels {@code registerXYZ()} registriert werden können. Das
* Setzen einer der beiden Properties kann die Performance verbessern.
*
* @author Meno Hochschild
* @doctags.concurrency
*/
public final class LeapSeconds
implements Iterable, Comparator {
//~ Statische Felder/Initialisierungen --------------------------------
/**
* System property "net.time4j.scale.leapseconds.suppressed"
* which determines that no leap seconds shall be loaded and used.
*
* Defined values: "true" (suppressed) or "false"
* (active - default).
*/
/*[deutsch]
* System-Property "net.time4j.scale.leapseconds.suppressed",
* die regelt, daß keine Schaltsekunden geladen werden.
*
* Definierte Werte: "true" (unterdrückt) oder
& quot;false" (aktiv - Standard).
*/
public static final boolean SUPPRESS_UTC_LEAPSECONDS =
Boolean.getBoolean("net.time4j.scale.leapseconds.suppressed");
/**
* System property "net.time4j.scale.leapseconds.final"
* which determines that leap seconds can be loaded only one time at
* system start.
*
* Defined values: "true" (final) or "false"
* (enables lazy regisration - default).
*/
/*[deutsch]
* System-Property "net.time4j.scale.leapseconds.final", die
* regelt, daß Schaltsekunden nur einmalig zum Systemstart festgelegt
* werden können.
*
* Definierte Werte: "true" (final) oder "false"
* (nachträgliche Registrierung m&oumL;glich - Standard).
*/
public static final boolean FINAL_UTC_LEAPSECONDS =
Boolean.getBoolean("net.time4j.scale.leapseconds.final");
/**
* System property "net.time4j.scale.leapseconds.path"
* which determines the path of the leap second file.
*
* Setting this property will usually suppress other leap second
* providers even if they have registered more leap seconds. The path
* is an URL which must be understood by
* {@link ClassLoader#getResourceAsStream(String)}. The default
* value is: "data/leapseconds.data" (relative to
* class path).
*
* @since 2.1.2
*/
/*[deutsch]
* System-Property "net.time4j.scale.leapseconds.path", die
* den Pfad der Schaltsekundendatei festlegt.
*
* Das Setzen dieser Property wird gewöhnlich andere
* {@code LeapSecondProvider} ausschalten, selbst wenn sie mehr
* Schaltsekunden registriert haben. Der Pfad ist ein URL, der von
* {@link ClassLoader#getResourceAsStream(String)} verstanden werden
* muß. Standardwert ist: "data/leapseconds.data"
* (relativ zum Klassenpfad).
*
* @since 2.1.2
*/
public static final String PATH_TO_LEAPSECONDS =
System.getProperty(
"net.time4j.scale.leapseconds.path",
"data/leapseconds.data");
private static final ExtendedLSE[] EMPTY_ARRAY = new ExtendedLSE[0];
private static final LeapSeconds INSTANCE = new LeapSeconds();
private static final long UNIX_OFFSET = 2 * 365 * 86400;
private static final long MJD_OFFSET = 40587;
//~ Instanzvariablen --------------------------------------------------
private final LeapSecondProvider provider;
private final List list;
private final ExtendedLSE[] reverseFinal;
private volatile ExtendedLSE[] reverseVolatile;
private final boolean supportsNegativeLS;
//~ Konstruktoren -----------------------------------------------------
private LeapSeconds() {
super();
LeapSecondProvider loaded = null;
int leapCount = 0;
if (!SUPPRESS_UTC_LEAPSECONDS) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = LeapSecondProvider.class.getClassLoader();
}
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
ServiceLoader sl =
ServiceLoader.load(LeapSecondProvider.class, cl);
// Provider mit den meisten Schaltsekunden wählen
for (LeapSecondProvider temp : sl) {
int currentCount = temp.getLeapSecondTable().size();
if (currentCount > leapCount) {
loaded = temp;
leapCount = currentCount;
}
}
}
if (
(loaded == null)
|| (leapCount == 0)
) {
this.provider = null;
this.list = Collections.emptyList();
this.reverseFinal = EMPTY_ARRAY;
this.reverseVolatile = EMPTY_ARRAY;
this.supportsNegativeLS = false;
} else {
SortedSet sortedLS = new TreeSet(this);
for (
Map.Entry entry
: loaded.getLeapSecondTable().entrySet()
) {
GregorianDate date = entry.getKey();
long unixTime = toPosix(date);
sortedLS.add(
new SimpleLeapSecondEvent(
date,
Long.MIN_VALUE,
unixTime + (1 - 2 * 365) * 86400 - 1,
entry.getValue().intValue()
)
);
}
extend(sortedLS);
if (FINAL_UTC_LEAPSECONDS) {
this.list =
Collections.unmodifiableList(
new ArrayList(sortedLS));
} else {
this.list = new CopyOnWriteArrayList(sortedLS);
}
this.reverseFinal = this.initReverse();
this.reverseVolatile = this.reverseFinal;
this.provider = loaded;
if (FINAL_UTC_LEAPSECONDS) {
boolean snls = loaded.supportsNegativeLS();
if (snls) {
boolean hasNegativeLS = false;
for (ExtendedLSE event : this.list) {
if (event.getShift() < 0) {
hasNegativeLS = true;
break;
}
}
snls = hasNegativeLS;
}
this.supportsNegativeLS = snls;
} else {
this.supportsNegativeLS = true;
}
}
}
//~ Methoden ----------------------------------------------------------
/**
* Returns the singleton instance.
*
* @return singleton instance
*/
/*[deutsch]
* Liefert die Singleton-Instanz.
*
* @return singleton instance
*/
public static LeapSeconds getInstance() {
return INSTANCE;
}
/**
* Queries if the leap second support is activated.
*
* @return {@code true} if leap seconds are supported and are also
* registered else {@code false}
* @see #SUPPRESS_UTC_LEAPSECONDS
*/
/*[deutsch]
* Ist die Schaltsekundenunterstützung aktiviert?
*
* @return {@code true} if leap seconds are supported and are also
* registered else {@code false}
* @see #SUPPRESS_UTC_LEAPSECONDS
*/
public boolean isEnabled() {
return !this.list.isEmpty();
}
/**
* Queries if a lazy registration of leap seconds is possible.
*
* If the leap second support is switched off then a registration of
* leap seconds is never possible so this method will be ignored.
*
* @return {@code true} if the method {@code registerXYZ()} can be
* called without exception else {@code false}
* @see #registerPositiveLS(int, int, int)
* @see #registerNegativeLS(int, int, int)
* @see #FINAL_UTC_LEAPSECONDS
* @see #isEnabled()
*/
/*[deutsch]
* Können nachträglich UTC-Schaltsekunden registriert
* werden?
*
* Ist die Schaltsekundenunterstützung abgeschaltet, dann ist
* eine Registrierung niemals möglich, und diese Methode wird dann
* de facto ignoriert.
*
* @return {@code true} if the method {@code registerXYZ()} can be
* called without exception else {@code false}
* @see #registerPositiveLS(int, int, int)
* @see #registerNegativeLS(int, int, int)
* @see #FINAL_UTC_LEAPSECONDS
* @see #isEnabled()
*/
public boolean isExtensible() {
return (!FINAL_UTC_LEAPSECONDS && this.isEnabled());
}
/**
* Yields the count of all registered leap seconds.
*
* @return count of registered leap seconds
*/
/*[deutsch]
* Ermittelt die Anzahl aller registrierten Schaltsekunden.
*
* @return count of registered leap seconds
*/
public int getCount() {
return this.getEventsInDescendingOrder().length;
}
/**
* Registers a new positive leap second by defining the
* switch-over-day.
*
* @param year proleptic iso year
* @param month gregorian month in range (1-12)
* @param dayOfMonth day of month in range (1-31)
* @throws IllegalStateException if support of leap seconds is switched
* off by configuration or if the value of system property
* "net.time4j.utc.leapseconds.final" is {@code true}
* @throws IllegalArgumentException if the new event is not after the
* last stored event or if the date is invalid
* @see #isExtensible()
* @see #isEnabled()
* @see #SUPPRESS_UTC_LEAPSECONDS
* @see #FINAL_UTC_LEAPSECONDS
*/
/*[deutsch]
* Registriert eine neue positive Schaltsekunde, indem als Datum
* der Tag der Umstellung definiert wird.
*
* @param year proleptic iso year
* @param month gregorian month in range (1-12)
* @param dayOfMonth day of month in range (1-31)
* @throws IllegalStateException if support of leap seconds is switched
* off by configuration or if the value of system property
* "net.time4j.utc.leapseconds.final" is {@code true}
* @throws IllegalArgumentException if the new event is not after the
* last stored event or if the date is invalid
* @see #isExtensible()
* @see #isEnabled()
* @see #SUPPRESS_UTC_LEAPSECONDS
* @see #FINAL_UTC_LEAPSECONDS
*/
public void registerPositiveLS(
int year,
int month,
int dayOfMonth
) {
this.register(year, month, dayOfMonth, false);
}
/**
* Registers a new negative leap second by defining the
* switch-over-day.
*
* @param year proleptic iso year
* @param month gregorian month in range (1-12)
* @param dayOfMonth day of month in range (1-31)
* @throws IllegalStateException if support of leap seconds is switched
* off by configuration or if the value of system property
* "net.time4j.utc.leapseconds.final" is {@code true}
* @throws IllegalArgumentException if the new event is not after the
* last stored event or if the date is invalid
* @see #isExtensible()
* @see #isEnabled()
* @see #SUPPRESS_UTC_LEAPSECONDS
* @see #FINAL_UTC_LEAPSECONDS
*/
/*[deutsch]
* Registriert eine neue negative Schaltsekunde, indem als Datum
* der Tag der Umstellung definiert wird.
*
* @param year proleptic iso year
* @param month gregorian month in range (1-12)
* @param dayOfMonth day of month in range (1-31)
* @throws IllegalStateException if support of leap seconds is switched
* off by configuration or if the value of system property
* "net.time4j.utc.leapseconds.final" is {@code true}
* @throws IllegalArgumentException if the new event is not after the
* last stored event or if the date is invalid
* @see #isExtensible()
* @see #isEnabled()
* @see #SUPPRESS_UTC_LEAPSECONDS
* @see #FINAL_UTC_LEAPSECONDS
*/
public void registerNegativeLS(
int year,
int month,
int dayOfMonth
) {
this.register(year, month, dayOfMonth, true);
}
/**
* Queries if negative leap seconds are supported.
*
* @return {@code true} if negative leap seconds are supported
* else {@code false}
* @see LeapSecondProvider#supportsNegativeLS()
*/
/*[deutsch]
* Werden auch negative Schaltsekunden unterstützt?
*
* @return {@code true} if negative leap seconds are supported
* else {@code false}
* @see LeapSecondProvider#supportsNegativeLS()
*/
public boolean supportsNegativeLS() {
return this.supportsNegativeLS;
}
/**
* Iterates over all leap second events in descending temporal
* order.
*
* @return {@code Iterator} over all stored leap second events
* which enables for-each-support
*/
/*[deutsch]
* Iteriert über alle Schaltsekundenereignisse in zeitlich
* absteigender Reihenfolge.
*
* @return {@code Iterator} over all stored leap second events
* which enables for-each-support
*/
@Override
public Iterator iterator() {
final LeapSecondEvent[] events = this.getEventsInDescendingOrder();
return Collections.unmodifiableList(Arrays.asList(events)).iterator();
}
/**
* Yields the shift in seconds suitable for the last minute
* of given calendar date.
*
* The result of this method can be added to the second value
* {@code 59} in order to calculate the maximum of the element
* SECOND_OF_MINUTE in given time context. The behaviour of the
* method is undefined if given calendar date is undefined.
*
* @param date day of possible leap second event in the last minute
* @return shift of second element (most of the times just {@code 0})
*/
/*[deutsch]
* Ermittelt die Verschiebung in Sekunden passend zur letzten Minute
* des angegebenen Datums.
*
* Das Ergebnis der Methode kann zum Sekundenwert {@code 59} addiert
* werden, um das Maximum des Elements SECOND_OF_MINUTE im angegebenen
* Zeitkontext zu erhalten. Das Verhalten der Methode ist undefiniert,
* wenn die angegebenen Bereichsgrenzen der Argumentwerte nicht beachtet
* werden.
*
* @param date day of possible leap second event in the last minute
* @return shift of second element (most of the times just {@code 0})
*/
public int getShift(GregorianDate date) {
int year = date.getYear();
// Schaltsekundenereignisse gibt es erst seit Juni 1972
if (year >= 1972) {
ExtendedLSE[] events = this.getEventsInDescendingOrder();
for (int i = 0; i < events.length; i++) {
ExtendedLSE event = events[i];
GregorianDate lsDate = event.getDate();
// Ist es der Umstellungstag?
if (
(year == lsDate.getYear())
&& (date.getMonth() == lsDate.getMonth())
&& (date.getDayOfMonth() == lsDate.getDayOfMonth())
) {
return event.getShift();
}
}
}
return 0;
}
/**
* Yields the shift in seconds dependent on if given UTC time point
* represents a leap second or not.
*
* @param utc elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @return {@code 1, 0, -1} if the argument denotes a positive leap second,
no leap second or a negative leap second
*/
/*[deutsch]
* Ermittelt die Verschiebung in Sekunden, wenn dieser Zeitpunkt
* überhaupt eine Schaltsekunde repräsentiert.
*
* @param utc elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @return {@code 1, 0, -1} if the argument denotes a positive leap second,
no leap second or a negative leap second
*/
public int getShift(long utc) {
if (utc <= 0) {
return 0;
}
ExtendedLSE[] events = this.getEventsInDescendingOrder();
for (int i = 0; i < events.length; i++) {
ExtendedLSE lse = events[i];
if (utc > lse.utc()) {
return 0;
} else {
long start = lse.utc() - lse.getShift();
if (utc > start) { // Schaltbereich
return (int) (utc - start);
}
}
}
return 0;
}
/**
* Yields the next leap second event after given UTC time point.
*
* @param utc elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @return following leap second event or {@code null} if not known
* @since 2.1
*/
/*[deutsch]
* Ermittelt das zum angegebenen UTC-Zeitstempel nächste
* Schaltsekundenereignis.
*
* @param utc elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @return following leap second event or {@code null} if not known
* @since 2.1
*/
public LeapSecondEvent getNextEvent(long utc) {
ExtendedLSE[] events = this.getEventsInDescendingOrder();
LeapSecondEvent result = null;
for (int i = 0; i < events.length; i++) {
ExtendedLSE lse = events[i];
if (utc >= lse.utc()) {
break;
} else {
result = lse;
}
}
return result;
}
/**
* Enhances an UNIX-timestamp with leap seconds and converts it to an
* UTC-timestamp.
*
* Note: A leap second itself cannot be restored because the mapping
* between UNIX- and UTC-time is not bijective. Hence the result of this
* method can not represent a leap second.
*
* @param unixTime elapsed time in seconds relative to UNIX epoch
* [1970-01-01T00:00:00Z] without leap seconds
* @return elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @see #strip(long)
*/
/*[deutsch]
* Reichert einen UNIX-Zeitstempel mit Schaltsekunden an und wandelt
* ihn in einen UTC-Zeitstempel um.
*
* Notiz: Eine Schaltsekunde kann selbst nicht wiederhergestellt werden,
* da die Abbildung zwischen der UNIX- und UTC-Zeit nicht bijektiv ist.
* Das Ergebnis dieser Methode stellt also keine aktuelle Schaltsekunde
* dar.
*
* @param unixTime elapsed time in seconds relative to UNIX epoch
* [1970-01-01T00:00:00Z] without leap seconds
* @return elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @see #strip(long)
*/
public long enhance(long unixTime) {
long epochTime = unixTime - UNIX_OFFSET;
if (unixTime <= 0) {
return unixTime;
}
// Lineare Suche hier besser als binäre Suche, weil in der
// Praxis meistens mit aktuellen Datumswerten gesucht wird
final ExtendedLSE[] events = this.getEventsInDescendingOrder();
for (int i = 0; i < events.length; i++) {
ExtendedLSE lse = events[i];
if (lse.raw() < epochTime) {
return MathUtils.safeAdd(epochTime, lse.utc() - lse.raw());
}
}
return epochTime;
}
/**
* Converts given UTC-timestamp to an UNIX-timestamp without
* leap seconds.
*
* This method is the reversal of {@code enhance()}. Note that
* there is no bijective mapping, that is sometimes the expression
* {@code enhance(strip(val)) != val} is {@code true}.
*
* @param utc elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @return elapsed time in seconds relative to UNIX epoch
* [1970-01-01T00:00:00Z] without leap seconds
* @see #enhance(long)
*/
/*[deutsch]
* Konvertiert die UTC-Angabe zu einem UNIX-Zeitstempel ohne
* Schaltsekunden.
*
* Diese Methode ist die Umkehrung zu {@code enhance()}. Zu
* beachten ist, daß keine bijektive Abbildung besteht, d.h. es gilt
* manchmal: {@code enhance(strip(val)) != val}.
*
* @param utc elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @return elapsed time in seconds relative to UNIX epoch
* [1970-01-01T00:00:00Z] without leap seconds
* @see #enhance(long)
*/
public long strip(long utc) {
if (utc <= 0) {
return utc + UNIX_OFFSET;
}
// Lineare Suche hier besser als binäre Suche, weil in der
// Praxis meistens mit aktuellen Datumswerten gesucht wird
final ExtendedLSE[] events = this.getEventsInDescendingOrder();
boolean snls = this.supportsNegativeLS;
for (int i = 0; i < events.length; i++) {
ExtendedLSE lse = events[i];
if (
(lse.utc() - lse.getShift() < utc)
|| (snls && (lse.getShift() < 0) && (lse.utc() < utc))
) {
utc = MathUtils.safeAdd(utc, lse.raw() - lse.utc());
break;
}
}
return utc + UNIX_OFFSET;
}
/**
* Queries if given UTC-timestamp represents a registered
* positive leap second.
*
* @param utc elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @return {@code true} if the argument represents a registered
* positive leap second else {@code false}
*/
/*[deutsch]
* Ist die angegebene UTC-Zeit eine registrierte positive
* Schaltsekunde?
*
* @param utc elapsed SI-seconds relative to UTC epoch
* [1972-01-01T00:00:00Z] including leap seconds
* @return {@code true} if the argument represents a registered
* positive leap second else {@code false}
*/
public boolean isPositiveLS(long utc) {
if (utc <= 0) {
return false;
}
final ExtendedLSE[] events = this.getEventsInDescendingOrder();
for (int i = 0; i < events.length; i++) {
long comp = events[i].utc();
if (comp == utc) {
return (events[i].getShift() == 1);
} else if (comp < utc) {
break;
}
}
return false;
}
/**
* Determines the expiration date of underlying data.
*
* @return immutable date of expiration
* @since 2.3
*/
/*[deutsch]
* Bestimmt das Verfallsdatum der zugrundeliegenden Daten.
*
* @return immutable date of expiration
* @since 2.3
*/
public GregorianDate getDateOfExpiration() {
return this.provider.getDateOfExpiration();
}
/**
* Compares two leap second events by their date in ascending order.
*
* @param o1 first leap second event
* @param o2 second leap second event
* @return {@code -1}, {@code 0} or {@code 1} if first event is before,
* equal to or later than second event
* @since 2.3
*/
/*[deutsch]
* Vergleicht zwei Schaltsekundenereignisse nach ihrem Datum in
* aufsteigender Reihenfolge.
*
* @param o1 first leap second event
* @param o2 second leap second event
* @return {@code -1}, {@code 0} or {@code 1} if first event is before,
* equal to or later than second event
* @since 2.3
*/
@Override
public int compare(
LeapSecondEvent o1,
LeapSecondEvent o2
) {
GregorianDate d1 = o1.getDate();
GregorianDate d2 = o2.getDate();
int y1 = d1.getYear();
int y2 = d2.getYear();
if (y1 < y2) {
return -1;
} else if (y1 > y2) {
return 1;
}
int m1 = d1.getMonth();
int m2 = d2.getMonth();
if (m1 < m2) {
return -1;
} else if (m1 > m2) {
return 1;
}
int dom1 = d1.getDayOfMonth();
int dom2 = d2.getDayOfMonth();
return (dom1 < dom2 ? -1 : (dom1 == dom2 ? 0 : 1));
}
/**
* For debugging purposes.
*
* @return table of leap seconds as String
*/
/*[deutsch]
* Für Debugging-Zwecke.
*
* @return table of leap seconds as String
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(2048);
sb.append("[PROVIDER=");
sb.append(this.provider);
sb.append(",EXPIRES=");
sb.append(format(this.getDateOfExpiration()));
sb.append(",EVENTS=[");
if (this.isEnabled()) {
boolean first = true;
for (Object event : this.list) {
if (first) {
first = false;
} else {
sb.append('|');
}
sb.append(event);
}
} else {
sb.append("NOT SUPPORTED");
}
return sb.append("]]").toString();
}
private void register(
int year,
int month,
int dayOfMonth,
boolean negativeLS
) {
if (FINAL_UTC_LEAPSECONDS) {
throw new IllegalStateException(
"Leap seconds are final, "
+ "change requires edit of system property "
+ "\"time4j.utc.leapseconds.final\" "
+ "and reboot of JVM.");
} else if (SUPPRESS_UTC_LEAPSECONDS) {
throw new IllegalStateException(
"Leap seconds are not supported, "
+ "change requires edit of system property "
+ "\"time4j.utc.leapseconds.suppressed\" "
+ "and reboot of JVM.");
}
synchronized (this) {
GregorianMath.checkDate(year, month, dayOfMonth);
if (!this.isEnabled()) {
throw new IllegalStateException("Leap seconds not activated.");
}
ExtendedLSE last = this.reverseVolatile[0];
GregorianDate date = last.getDate();
boolean ok = false;
if (year > date.getYear()) {
ok = true;
} else if (year == date.getYear()) {
if (month > date.getMonth()) {
ok = true;
} else if (month == date.getMonth()) {
if (dayOfMonth > date.getDayOfMonth()) {
ok = true;
}
}
}
if (!ok) {
throw new IllegalArgumentException(
"New leap second must be after last leap second.");
}
int shift = (negativeLS ? -1 : 1);
GregorianDate newDate =
this.provider.getDateOfEvent(year, month, dayOfMonth);
this.list.add(createLSE(newDate, shift, last));
this.reverseVolatile = this.initReverse();
}
}
// Ereignisse in zeitlich absteigender Reihenfolge auf (das neueste zuerst)
private ExtendedLSE[] getEventsInDescendingOrder() {
if (SUPPRESS_UTC_LEAPSECONDS || FINAL_UTC_LEAPSECONDS) {
return this.reverseFinal;
} else {
return this.reverseVolatile;
}
}
private static void extend(SortedSet sortedColl) {
List tmp = new ArrayList(sortedColl.size());
int diff = 0;
for (ExtendedLSE lse : sortedColl) {
if (lse.utc() == Long.MIN_VALUE) {
diff += lse.getShift();
tmp.add(new SimpleLeapSecondEvent(lse, diff));
} else {
tmp.add(lse);
}
}
sortedColl.clear();
sortedColl.addAll(tmp);
}
private static ExtendedLSE createLSE(
final GregorianDate date,
final int shift,
ExtendedLSE last
) {
long raw = toPosix(date) + (1 - 2 * 365) * 86400 - 1;
int diff = (int) (last.utc() - last.raw() + shift);
return new SimpleLeapSecondEvent(
date,
raw + diff,
raw,
shift);
}
private static long toPosix(GregorianDate date) {
return MathUtils.safeMultiply(
MathUtils.safeSubtract(
GregorianMath.toMJD(date),
MJD_OFFSET
),
86400
);
}
private ExtendedLSE[] initReverse() {
List tmp =
new ArrayList(this.list.size());
tmp.addAll(this.list);
Collections.reverse(tmp);
return tmp.toArray(new ExtendedLSE[tmp.size()]);
}
private static String format(GregorianDate date) {
return String.format(
"%1$04d-%2$02d-%3$02d",
date.getYear(),
date.getMonth(),
date.getDayOfMonth());
}
//~ Innere Klassen ----------------------------------------------------
private static class SimpleLeapSecondEvent
implements ExtendedLSE, Serializable {
//~ Statische Felder/Initialisierungen ----------------------------
private static final long serialVersionUID = 5986185471610524587L;
//~ Instanzvariablen ----------------------------------------------
/**
* @serial date of leap second event
*/
private final GregorianDate date;
/**
* @serial shift in seconds
*/
private final int shift;
/**
* @serial UTC time including leap seconds
*/
private final long _utc;
/**
* @serial UTC time without leap seconds
*/
private final long _raw;
//~ Konstruktoren -------------------------------------------------
SimpleLeapSecondEvent(
GregorianDate date,
long utcTime,
long rawTime,
int shift
) {
super();
this.date = date;
this.shift = shift;
this._utc = utcTime;
this._raw = rawTime;
}
// Anreicherung mit der UTC-Zeit
SimpleLeapSecondEvent(
ExtendedLSE lse,
int diff
) {
super();
this.date = lse.getDate();
this.shift = lse.getShift();
this._utc = lse.raw() + diff;
this._raw = lse.raw();
}
//~ Methoden ------------------------------------------------------
@Override
public GregorianDate getDate() {
return this.date;
}
@Override
public int getShift() {
return this.shift;
}
@Override
public long utc() {
return this._utc;
}
@Override
public long raw() {
return this._raw;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append(LeapSecondEvent.class.getName());
sb.append('[');
sb.append(format(this.date));
sb.append(": utc=");
sb.append(this._utc);
sb.append(", raw=");
sb.append(this._raw);
sb.append(" (shift=");
sb.append(this.shift);
sb.append(")]");
return sb.toString();
}
}
}