net.time4j.sql.JDBCAdapter Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------
* Copyright © 2015-2016 Meno Hochschild,
* -----------------------------------------------------------------------
* This file (JDBCAdapter.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.sql;
import net.time4j.ClockUnit;
import net.time4j.Moment;
import net.time4j.PlainDate;
import net.time4j.PlainTime;
import net.time4j.PlainTimestamp;
import net.time4j.TemporalType;
import net.time4j.base.MathUtils;
import net.time4j.engine.ChronoException;
import net.time4j.engine.EpochDays;
import net.time4j.scale.TimeScale;
import net.time4j.tz.Timezone;
import net.time4j.tz.ZonalOffset;
/**
* Serves as bridge to temporal types in JDBC.
*
* All singleton instances are defined as static constants and are
* immutable.
*
* @param source type in JDBC
* @param target type in Time4J
* @author Meno Hochschild
* @since 3.0
*/
/*[deutsch]
* Dient als Brücke zu Datums- und Zeittypen aus der
* JDBC-Bibliothek.
*
* Alle Singleton-Instanzen sind als statische Konstanten definiert und
* unveränderlich (immutable).
*
* @param source type in JDBC
* @param target type in Time4J
* @author Meno Hochschild
* @since 3.0
*/
public abstract class JDBCAdapter
extends TemporalType {
//~ Statische Felder/Initialisierungen --------------------------------
private static final boolean WITH_SQL_UTC_CONVERSION =
Boolean.getBoolean("net.time4j.sql.utc.conversion");
private static final PlainDate UNIX_DATE = PlainDate.of(0, EpochDays.UNIX);
/**
* Bridge between a JDBC-Date and the class {@code PlainDate}.
*
* If the system property "net.time4j.sql.utc.conversion" is
* set to the value "true" then the conversion will not take into
* account the system timezone anticipating that a SQL-DATE was created
* without any timezone calculation on the server side, too. That is more
* or less the case if UTC is the default timezone on the application
* server.
*
* Example (UTC as default timezone):
*
*
* java.sql.Date sqlValue = new java.sql.Date(86400 * 1000);
* PlainDate date = JDBCAdapter.SQL_DATE.translate(sqlValue);
* System.out.println(date);
* // output: 1970-01-02
*
*
* Note: The conversion is only possible if a date has
* a year in the range {@code 1900-9999} because else a JDBC-compatible
* database cannot store the date per SQL-specification. It is strongly
* recommended to interprete a SQL-DATE only as abstract JDBC object
* because its text output via {@code java.sql.Date.toString()}-method
* is not reliable (dependency on the gregorian-julian cutover day
* + possible timezone side effects). The concrete formatting can be
* done by Time4J for example via {@code PlainDate.toString()} or a
* suitable {@code ChronoFormatter}.
*
* @since 3.0
*/
/*[deutsch]
* Brücke zwischen einem JDBC-Date und der Klasse {@code PlainDate}.
*
* Wenn die System-Property "net.time4j.sql.utc.conversion"
* auf den Wert "true" gesetzt ist, dann berücksichtigt die
* Konversion NICHT die Standardzeitzone des Systems und setzt somit voraus,
* daß ein SQL-DATE java-seitig ebenfalls ohne Zeitzonenkalkulation
* erzeugt wurde. Das ist de facto der Fall, wenn auf dem Application-Server
* UTC die Standardzeitzone ist.
*
* Beispiel (UTC als Standardzeitzone):
*
*
* java.sql.Date sqlValue = new java.sql.Date(86400 * 1000);
* PlainDate date = JDBCAdapter.SQL_DATE.translate(sqlValue);
* System.out.println(date);
* // Ausgabe: 1970-01-02
*
*
* Zu beachten: Die Konversion ist nur möglich,
* wenn ein Datum ein Jahr im Bereich {@code 1900-9999} hat, denn sonst
* kann eine JDBC-kompatible Datenbank den Datumswert per SQL-Spezifikation
* nicht speichern. Es wird dringend empfohlen, ein SQL-DATE nur als
* abstraktes JDBC-Objekt zu interpretieren, weil seine Textausgabe via
* {@code java.sql.Date.toString()}-Methode nicht zuverlässig ist
* (Abhängigkeit vom gregorianisch-julianischen Umstellungstag
* + evtl. Zeitzoneneffekte). Die konkrete Formatierung kann von Time4J
* korrekt zum Beispiel via {@code PlainDate.toString()} oder über
* einen geeigneten {@code ChronoFormatter} geleistet werden.
*
* @since 3.0
*/
public static final JDBCAdapter SQL_DATE =
new SqlDateRule();
// min = new java.sql.Date(-2208988800000L), // 1900-01-01
// max = new java.sql.Date(253402214400000L + 86399999), // 9999-12-31
/**
* Bridge between a JDBC-Time and the class {@code PlainTime}.
*
* If the system property "net.time4j.sql.utc.conversion" is
* set to the value "true" then the conversion will NOT take into
* account the system timezone anticipating that a SQL-DATE was created
* without any timezone calculation on the server side, too. That is more
* or less the case if UTC is the default timezone on the application
* server.
*
* Example (UTC as default timezone):
*
*
* java.sql.Time sqlValue = new java.sql.Time(43200 * 1000);
* PlainTime time = JDBCAdapter.SQL_TIME.translate(sqlValue);
* System.out.println(time);
* // output: T12:00:00
*
*
* Note: The conversion only occurs in millisecond
* precision at best not in in nanosecond precision so there is possible
* loss of data. Furthermore, the text output via the method
* {@code java.sql.Time.toString()} can be misinterpreted by timezone
* side effects. Concrete text output should be done by Time4J.
*
* @since 3.0
*/
/*[deutsch]
* Brücke zwischen einem JDBC-Time und der Klasse {@code PlainTime}.
*
* Wenn die System-Property "net.time4j.sql.utc.conversion"
* auf den Wert "true" gesetzt ist, dann berücksichtigt
* die Konversion NICHT die Standardzeitzone des Systems und setzt somit
* voraus, daß ein SQL-TIME java-seitig ebenfalls ohne
* Zeitzonenkalkulation erzeugt wurde. Das ist de facto der Fall, wenn
* auf dem Application-Server UTC die Standardzeitzone ist.
*
* Beispiel (UTC als Standardzeitzone):
*
*
* java.sql.Time sqlValue = new java.sql.Time(43200 * 1000);
* PlainTime time = JDBCAdapter.SQL_TIME.translate(sqlValue);
* System.out.println(time);
* // Ausgabe: T12:00:00
*
*
* Zu beachten: Die Konversion geschieht nur in
* Milli-, nicht in Nanosekundenpräzision, so daß eventuell
* Informationsverluste auftreten können. Auch ist die Textausgabe
* mittels {@code java.sql.Time.toString()} durch Zeitzoneneffekte
* verfälscht. Konkrete Textausgaben sollen daher immer durch Time4J
* erfolgen.
*
* @since 3.0
*/
public static final JDBCAdapter SQL_TIME =
new SqlTimeRule();
/**
* Bridge between a JDBC-Timestamp and the class {@code PlainTimestamp}.
*
* If the system property "net.time4j.sql.utc.conversion"
* is set to the value "true" then the conversion will NOT take
* into account the system timezone anticipating that a SQL-DATE was
* created without any timezone calculation on the server side, too.
* That is more or less the case if UTC is the default timezone on the
* application server.
*
* Example (UTC as default timezone):
*
*
* java.sql.Timestamp sqlValue = new java.sql.Timestamp(86401 * 1000);
* sqlValue.setNanos(1);
* PlainTimestamp ts = JDBCAdapter.SQL_TIMESTAMP.translate(sqlValue);
* System.out.println(ts);
* // output: 1970-01-02T00:00:01,000000001
*
*
* @since 3.0
*/
/*[deutsch]
* Brücke zwischen einem JDBC-Timestamp und der Klasse {@code PlainTimestamp}.
*
* Wenn die System-Property "net.time4j.sql.utc.conversion"
* auf den Wert "true" gesetzt ist, dann berücksichtigt
* die Konversion NICHT die Standardzeitzone des Systems und setzt somit
* voraus, daß ein SQL-TIMESTAMP java-seitig auch ohne
* Zeitzonenkalkulation erzeugt wurde. Das ist de facto der Fall, wenn
* auf dem Application-Server UTC die Standardzeitzone ist.
*
* Beispiel (UTC als Standardzeitzone):
*
*
* java.sql.Timestamp sqlValue = new java.sql.Timestamp(86401 * 1000);
* sqlValue.setNanos(1);
* PlainTimestamp ts = JDBCAdapter.SQL_TIMESTAMP.translate(sqlValue);
* System.out.println(ts);
* // Ausgabe: 1970-01-02T00:00:01,000000001
*
*
* @since 3.0
*/
public static final JDBCAdapter SQL_TIMESTAMP =
new SqlTimestampRule();
/**
* Bridge between a JDBC-Timestamp and the class {@code Moment}.
*
* Notes: Leap seconds are not storable. And the maximum available
* precision is dependent on the database. Despite of the misleading SQL name,
* this conversion does not use a timezone but a timezone offset, finally
* {@link ZonalOffset#UTC}.
*
* @since 3.18/4.14
*/
/*[deutsch]
* Brücke zwischen einem JDBC-Timestamp und der Klasse {@code Moment}.
*
* Hinweise: Schaltsekunden sind so nicht speicherfähig. Und die maximal
* erreichbare Genauigkeit hängt von der konkreten Datenbank ab. Entgegen dem
* SQL-Namen wird nicht eine Zeitzone, sondern ein Zeitzonen-Offset in Betracht
* gezogen. Diese Konversion verwendet letztlich {@link ZonalOffset#UTC}.
*
* @since 3.18/4.14
*/
public static final JDBCAdapter SQL_TIMESTAMP_WITH_ZONE =
new SqlMomentRule();
//~ Konstruktoren -----------------------------------------------------
private JDBCAdapter() {
super();
}
//~ Innere Klassen ----------------------------------------------------
private static class SqlDateRule
extends JDBCAdapter {
//~ Methoden ------------------------------------------------------
@Override
public PlainDate translate(java.sql.Date source) {
long millis = source.getTime(); // UTC zone
if (!WITH_SQL_UTC_CONVERSION) {
Moment unixTime =
Moment.of(
MathUtils.floorDivide(millis, 1000),
TimeScale.POSIX);
ZonalOffset offset = Timezone.ofSystem().getOffset(unixTime);
millis += offset.getIntegralAmount() * 1000;
}
return PlainDate.axis().getCalendarSystem().transform(
MathUtils.floorDivide(millis, 86400 * 1000) - 2 * 365
);
}
@Override
public java.sql.Date from(PlainDate date) {
int year = date.getYear();
if ((year < 1900) || (year > 9999)) {
throw new ChronoException(
"SQL-Date is only defined in year range of 1900-9999.");
}
long millis = // localMillis
MathUtils.safeMultiply(
date.get(EpochDays.UNIX),
86400 * 1000);
if (!WITH_SQL_UTC_CONVERSION) {
ZonalOffset offset =
Timezone.ofSystem().getOffset(date, PlainTime.of(0));
millis -= offset.getIntegralAmount() * 1000;
}
return new java.sql.Date(millis);
}
}
private static class SqlTimeRule
extends JDBCAdapter {
//~ Methoden ------------------------------------------------------
@Override
public PlainTime translate(java.sql.Time source) {
long millis = source.getTime(); // UTC zone
if (!WITH_SQL_UTC_CONVERSION) {
Moment unixTime =
Moment.of(
MathUtils.floorDivide(millis, 1000),
TimeScale.POSIX);
ZonalOffset offset = Timezone.ofSystem().getOffset(unixTime);
millis += offset.getIntegralAmount() * 1000;
}
return PlainTime.midnightAtStartOfDay().with(
PlainTime.MILLI_OF_DAY,
MathUtils.floorModulo(millis, 86400 * 1000)
);
}
@Override
public java.sql.Time from(PlainTime time) {
long millis = time.get(PlainTime.MILLI_OF_DAY);
if (!WITH_SQL_UTC_CONVERSION) {
ZonalOffset offset =
Timezone.ofSystem().getOffset(UNIX_DATE, time);
millis -= offset.getIntegralAmount() * 1000;
}
return new java.sql.Time(millis);
}
}
private static class SqlTimestampRule
extends JDBCAdapter {
//~ Methoden ------------------------------------------------------
@Override
public PlainTimestamp translate(java.sql.Timestamp source) {
long millis = source.getTime(); // UTC zone
if (!WITH_SQL_UTC_CONVERSION) {
Moment unixTime =
Moment.of(
MathUtils.floorDivide(millis, 1000),
TimeScale.POSIX);
ZonalOffset offset = Timezone.ofSystem().getOffset(unixTime);
millis += offset.getIntegralAmount() * 1000;
}
PlainDate date =
PlainDate.of(
MathUtils.floorDivide(millis, 86400 * 1000),
EpochDays.UNIX);
PlainTime time =
PlainTime.of(0).plus(
MathUtils.floorModulo(millis, 86400 * 1000),
ClockUnit.MILLIS);
PlainTimestamp ts = PlainTimestamp.of(date, time);
return ts.with(PlainTime.NANO_OF_SECOND, source.getNanos());
}
@Override
public java.sql.Timestamp from(PlainTimestamp tsp) {
long dateMillis = // local millis
MathUtils.safeMultiply(
tsp.getCalendarDate().get(EpochDays.UNIX),
86400 * 1000
);
long timeMillis = tsp.get(PlainTime.MILLI_OF_DAY);
if (!WITH_SQL_UTC_CONVERSION) {
ZonalOffset offset =
Timezone.ofSystem().getOffset(tsp, tsp);
timeMillis -= offset.getIntegralAmount() * 1000;
}
java.sql.Timestamp ret =
new java.sql.Timestamp(
MathUtils.safeAdd(dateMillis, timeMillis));
ret.setNanos(tsp.get(PlainTime.NANO_OF_SECOND));
return ret;
}
}
private static class SqlMomentRule
extends JDBCAdapter {
//~ Methoden ------------------------------------------------------
@Override
public Moment translate(java.sql.Timestamp source) {
try {
return Moment.of(MathUtils.floorDivide(source.getTime(), 1000), source.getNanos(), TimeScale.POSIX);
} catch (IllegalArgumentException iae) {
throw new ChronoException(iae.getMessage(), iae);
}
}
@Override
public java.sql.Timestamp from(Moment moment) {
java.sql.Timestamp sql = new java.sql.Timestamp(MathUtils.safeMultiply(moment.getPosixTime(), 1000));
sql.setNanos(moment.getNanosecond());
return sql;
}
}
}