
net.time4j.scale.LeapSeconds Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2013-2014 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.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
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
* @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
* @concurrency
*/
public final class LeapSeconds
implements Iterable {
//~ 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");
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 String provider;
private final List list;
private final ExtendedLSE[] reverseFinal;
private volatile ExtendedLSE[] reverseVolatile;
private final boolean supportsNegativeLS;
//~ Konstruktoren -----------------------------------------------------
private LeapSeconds() {
super();
if (SUPPRESS_UTC_LEAPSECONDS) {
this.provider = "";
this.list = Collections.emptyList();
this.reverseFinal = EMPTY_ARRAY;
this.reverseVolatile = EMPTY_ARRAY;
this.supportsNegativeLS = false;
} else {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = LeapSecondProvider.class.getClassLoader();
}
ServiceLoader sl =
ServiceLoader.load(LeapSecondProvider.class, cl);
LeapSecondProvider loaded = null;
int leapCount = 0;
// 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) {
loaded = new DefaultLeapSecondService(); // leapseconds.data
}
SortedSet sortedLS =
new TreeSet(new EventComparator());
for (
Map.Entry entry
: loaded.getLeapSecondTable().entrySet()
) {
GregorianDate date = entry.getKey();
long unixTime = toPosix(date);
sortedLS.add(
new SimpleLeapSecondEvent(
date,
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.toString();
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 new Iterator() {
private int index = 0;
@Override
public boolean hasNext() {
return (this.index < events.length);
}
@Override
public LeapSecondEvent next() {
if (this.index >= events.length) {
throw new NoSuchElementException();
}
LeapSecondEvent event = events[this.index];
this.index++;
return event;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* 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;
}
/**
* 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(",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.");
}
GregorianDate newLS = new IsoDate(year, month, dayOfMonth);
int shift = (negativeLS ? -1 : 1);
this.list.add(createLSE(newLS, 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
) {
ExtendedLSE lse =
new ExtendedLSE() {
@Override
public GregorianDate getDate() {
return date;
}
@Override
public int getShift() {
return shift;
}
@Override
public long utc() {
return Long.MIN_VALUE;
}
@Override
public long raw() {
return toPosix(date) + (1 - 2 * 365) * 86400 - 1;
}
};
int diff = (int) (last.utc() - last.raw() + shift);
return new SimpleLeapSecondEvent(lse, diff);
}
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()]);
}
//~ Innere Klassen ----------------------------------------------------
private static class IsoDate
implements GregorianDate, Serializable {
//~ Statische Felder/Initialisierungen ----------------------------
private static final long serialVersionUID = 786391662682108754L;
//~ Instanzvariablen ----------------------------------------------
/**
* @serial proleptic iso year
*/
private final int year;
/**
* @serial gregorian month in range (1-12)
*/
private final int month;
/**
* @serial day of month in range (1-31)
*/
private final int dayOfMonth;
//~ Konstruktoren -------------------------------------------------
IsoDate(
int year,
int month,
int dayOfMonth
) {
super();
GregorianMath.checkDate(year, month, dayOfMonth);
this.year = year;
this.month = month;
this.dayOfMonth = dayOfMonth;
}
//~ Methoden ----------------------------------------------------------
@Override
public int getYear() {
return this.year;
}
@Override
public int getMonth() {
return this.month;
}
@Override
public int getDayOfMonth() {
return this.dayOfMonth;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof IsoDate) {
IsoDate that = (IsoDate) obj;
return (
(this.dayOfMonth == that.dayOfMonth)
&& (this.month == that.month)
&& (this.year == that.year)
);
} else {
return false;
}
}
@Override
public int hashCode() {
return this.year + 31 * this.month + 37 * this.dayOfMonth;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (this.year < 0) {
sb.append('-');
}
int y = Math.abs(this.year);
if (y < 1000) {
sb.append('0');
if (y < 100) {
sb.append('0');
if (y < 10) {
sb.append('0');
}
}
}
sb.append(y);
sb.append('-');
if (this.month < 10) {
sb.append('0');
}
sb.append(this.month);
sb.append('-');
if (this.dayOfMonth < 10) {
sb.append('0');
}
sb.append(this.dayOfMonth);
return sb.toString();
}
/**
* @serialData Checks the consistency.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
int y = this.year;
int m = this.month;
int d = this.dayOfMonth;
if (!GregorianMath.isValid(y, m, d)) {
throw new InvalidObjectException("Corrupt date.");
}
}
}
private static class SimpleLeapSecondEvent
implements ExtendedLSE, Serializable {
//~ Statische Felder/Initialisierungen ----------------------------
private static final long serialVersionUID = 5986185471610524587L;
//~ Instanzvariablen ----------------------------------------------
/**
* @serial date of leap second day
*/
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 -------------------------------------------------
// Standard-Konstruktor
SimpleLeapSecondEvent(
GregorianDate date,
long rawTime,
int shift
) {
super();
this.date =
new IsoDate( // defensives Kopieren
date.getYear(), date.getMonth(), date.getDayOfMonth());
this.shift = shift;
this._utc = Long.MIN_VALUE;
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(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();
}
}
private static class DefaultLeapSecondService
implements LeapSecondProvider {
//~ Instanzvariablen ----------------------------------------------
private final String source;
private final Map table;
//~ Konstruktoren -------------------------------------------------
DefaultLeapSecondService() {
super();
this.table = new LinkedHashMap(50);
InputStream is = null;
String name = "data/leapseconds.data";
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl != null) {
is = cl.getResourceAsStream(name);
}
if (is == null) {
cl = LeapSecondProvider.class.getClassLoader();
is = cl.getResourceAsStream(name);
}
if (is != null) {
this.source = cl.getResource(name).toString();
try {
BufferedReader br =
new BufferedReader(
new InputStreamReader(is, "US-ASCII"));
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith("#")) {
continue; // Kommentarzeile überspringen
}
int comma = line.indexOf(',');
String date;
Boolean sign = null;
if (comma == -1) {
date = line.trim();
sign = Boolean.TRUE;
} else {
date = line.substring(0, comma).trim();
String s = line.substring(comma + 1).trim();
if (s.length() == 1) {
char c = s.charAt(0);
if (c == '+') {
sign = Boolean.TRUE;
} else if (c == '-') {
sign = Boolean.FALSE;
}
}
if (sign == null) {
throw new IllegalStateException(
"Missing leap second sign.");
}
}
int year = Integer.parseInt(date.substring(0, 4));
int month = Integer.parseInt(date.substring(5, 7));
int dom = Integer.parseInt(date.substring(8, 10));
Object old =
this.table.put(
new IsoDate(year, month, dom),
Integer.valueOf(sign.booleanValue() ? 1 : -1)
);
if (old != null) {
throw new IllegalStateException(
"Duplicate leap second event found.");
}
}
} catch (UnsupportedEncodingException uee) {
throw new AssertionError(uee);
} catch (IllegalStateException ise) {
throw ise;
} catch (Exception ex) {
throw new IllegalStateException(ex);
} finally {
try {
is.close();
} catch (IOException ioe) {
ioe.printStackTrace(System.err);
}
}
} else {
this.source = "";
System.out.println("Warning: File \"" + name + "\" not found.");
}
}
//~ Methoden ------------------------------------------------------
@Override
public Map getLeapSecondTable() {
return Collections.unmodifiableMap(this.table);
}
@Override
public boolean supportsNegativeLS() {
return true;
}
@Override
public String toString() {
return this.source;
}
}
private static class EventComparator
implements Comparator {
//~ Methoden ------------------------------------------------------
@Override
public int compare(
ExtendedLSE o1,
ExtendedLSE 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));
}
}
}