Ist die Zeitzone von java.sql.Timestamp spezifisch?

84

Ich muss UTC dateTime in DB speichern.
Ich habe die in einer bestimmten Zeitzone angegebene dateTime in UTC konvertiert. dafür folgte ich dem folgenden Code.
Mein EingabedatumZeit ist "20121225 10:00:00 Z" Zeitzone ist "Asien / Kalkutta"
Mein Server / DB (Orakel) läuft in derselben Zeitzone (IST) "Asien / Kalkutta"

Holen Sie sich das Date-Objekt in dieser bestimmten Zeitzone

        String date = "20121225 10:00:00 Z";
        String timeZoneId = "Asia/Calcutta";
        TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);

        DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss z");
                    //This date object is given time and given timezone
        java.util.Date parsedDate = dateFormatLocal.parse(date + " "  
                         + timeZone.getDisplayName(false, TimeZone.SHORT));

        if (timeZone.inDaylightTime(parsedDate)) {
            // We need to re-parse because we don't know if the date
            // is DST until it is parsed...
            parsedDate = dateFormatLocal.parse(date + " "
                    + timeZone.getDisplayName(true, TimeZone.SHORT));
        }

       //assigning to the java.sql.TimeStamp instace variable
        obj.setTsSchedStartTime(new java.sql.Timestamp(parsedDate.getTime()));

In DB speichern

        if (tsSchedStartTime != null) {
            stmt.setTimestamp(11, tsSchedStartTime);
        } else {
            stmt.setNull(11, java.sql.Types.DATE);
        }

AUSGABE

DB (Orakel) hat das gleiche dateTime: "20121225 10:00:00nicht in UTC angegeben gespeichert .

Ich habe aus dem unten stehenden SQL bestätigt.

     select to_char(sched_start_time, 'yyyy/mm/dd hh24:mi:ss') from myTable

Mein DB-Server läuft auch in derselben Zeitzone "Asia / Calcutta"

Es gibt mir die folgenden Erscheinungen

  1. Date.getTime() ist nicht in UTC
  2. Oder der Zeitstempel hat Auswirkungen auf die Zeitzone beim Speichern in DB. Was mache ich hier falsch?

Noch eine Frage:

Wird timeStamp.toString()wie in der lokalen Zeitzone gedruckt java.util.date? Nicht UTC?

Kanagavelu Sugumar
quelle
4
Um die Frage in Ihrem Titel zu beantworten, nein, java.sql.Timestamp basiert auf UTC. Die Anzeige des Zeitstempels ist zeitzonenspezifisch.
Gilbert Le Blanc
@ Gilbert Le Blanc Danke!. Könnten Sie mir bitte antworten, dass "new java.sql.Timestamp (parsedDate.getTime ())" das GMT-Zeitstempelobjekt für meine Eingabe zurückgibt?
Kanagavelu Sugumar
1
@ GilbertLeBlanc Obwohl java.sql.Timestampin UTC, wenn in einem TIMESTAMP-Feld ohne Zeitzoneninformationen gespeichert, verwendet es das entsprechende Datum / die entsprechende Uhrzeit in der aktuellen Zeitzone der virtuellen Maschine
Mark Rotteveel
@ Kanagavelu Sugumar: Ja. Die getTime-Methode der Date-Klasse gibt die Anzahl der Millisekunden seit dem 1. Januar 1970, 00:00:00 GMT, zurück.
Gilbert Le Blanc
@MarkRotteveel Mark Ich habe nicht verstanden, dass "Speichern in einem TIMESTAMP-Feld ohne Zeitzoneninformationen das entsprechende Datum / die entsprechende Uhrzeit in der aktuellen Zeitzone der virtuellen Maschine verwendet". Könnten Sie bitte Ihre Antwort näher erläutern? Es wird mir hilfreicher sein.
Kanagavelu Sugumar

Antworten:

105

Obwohl es nicht explizit für setTimestamp(int parameterIndex, Timestamp x)Fahrer angegeben ist , müssen die vom setTimestamp(int parameterIndex, Timestamp x, Calendar cal)Javadoc festgelegten Regeln befolgt werden :

Setzt den angegebenen Parameter java.sql.Timestampunter Verwendung des angegebenen CalendarObjekts auf den angegebenen Wert . Der Treiber verwendet das CalendarObjekt, um einen SQL- TIMESTAMPWert zu erstellen , den der Treiber dann an die Datenbank sendet. Mit einem CalendarObjekt kann der Fahrer den Zeitstempel unter Berücksichtigung einer benutzerdefinierten Zeitzone berechnen. Wenn kein CalendarObjekt angegeben ist, verwendet der Treiber die Standardzeitzone, die der virtuellen Maschine entspricht, auf der die Anwendung ausgeführt wird.

Wenn Sie mit setTimestamp(int parameterIndex, Timestamp x)dem JDBC-Treiber anrufen , wird anhand der Zeitzone der virtuellen Maschine das Datum und die Uhrzeit des Zeitstempels in dieser Zeitzone berechnet. Dieses Datum und diese Uhrzeit werden in der Datenbank gespeichert. Wenn in der Datenbankspalte keine Zeitzoneninformationen gespeichert sind, gehen alle Informationen über die Zone verloren (was bedeutet, dass es an den Anwendungen liegt, die die Datenbank verwenden, um die zu verwenden dieselbe Zeitzone konsistent oder mit einem anderen Schema zur Erkennung der Zeitzone (dh in einer separaten Spalte speichern).

Beispiel: Ihre lokale Zeitzone ist GMT + 2. Sie speichern "2012-12-25 10:00:00 UTC". Der in der Datenbank gespeicherte tatsächliche Wert lautet "2012-12-25 12:00:00". Sie rufen es erneut ab: Sie erhalten es erneut als "2012-12-25 10:00:00 UTC" zurück (jedoch nur, wenn Sie es mit abrufen getTimestamp(..)). Wenn jedoch eine andere Anwendung in der Zeitzone GMT + 0 auf die Datenbank zugreift, wird dies der Fall sein Rufen Sie den Zeitstempel als "2012-12-25 12:00:00 UTC" ab.

Wenn Sie es in einer anderen Zeitzone speichern möchten, müssen Sie die setTimestamp(int parameterIndex, Timestamp x, Calendar cal)mit einer Kalenderinstanz in der erforderlichen Zeitzone verwenden. Stellen Sie einfach sicher, dass Sie beim Abrufen von Werten auch den entsprechenden Getter mit derselben Zeitzone verwenden (wenn Sie eine TIMESTAMPInformation ohne Zeitzone in Ihrer Datenbank verwenden).

Angenommen, Sie möchten die tatsächliche GMT-Zeitzone speichern, müssen Sie Folgendes verwenden:

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
stmt.setTimestamp(11, tsSchedStartTime, cal);

Mit JDBC 4.2 sollte ein kompatibler Treiber java.time.LocalDateTime(und java.time.LocalTime) für TIMESTAMP(und TIME) durch unterstützen get/set/updateObject. Die java.time.Local*Klassen haben keine Zeitzonen, daher muss keine Konvertierung angewendet werden (obwohl dies zu neuen Problemen führen kann, wenn Ihr Code eine bestimmte Zeitzone angenommen hat).

Mark Rotteveel
quelle
Ich möchte meine Zeit in GMT speichern. Ich glaube mein Zeitstempel in GMT. Also was soll ich tun? ist es setTimestamp (int parameterIndex, Timestamp tzGmtObj, Calendar calGmtObj)?
Kanagavelu Sugumar
Wenn "setTimestamp (int parameterIndex, Timestamp x)" die Zeitzone der virtuellen Maschine verwendet. Welche Rolle spielt x im Code?
Kanagavelu Sugumar
1
@KanagaveluSugumar Dies ist der tatsächlich einzustellende Zeitstempelwert. Ein Treiber verwendet das Kalenderobjekt nur für die Zeitzoneninformationen, nicht für seinen Wert!
Mark Rotteveel
1
Großartig, es funktioniert :) Ich verbringe viel Zeit damit zu beweisen, dass (Datum / Zeitstempel) .getTime () die zeitzonenspezifische Zeit in Millisekunden hält. Jetzt habe ich verstanden, dass es nicht ist. Es gibt immer UTC-Millisekunden, unabhängig von der Zeitzone. Aber (Datum / Zeitstempel) .toString () ist zeitzonenspezifisch. Vielen Dank.
Kanagavelu Sugumar
4
Die toString()auf java.lang.Date(und java.lang.Timestamp) immer präsentieren sie in der lokalen Zeitzone.
Mark Rotteveel
35

Ich denke, die richtige Antwort sollte java.sql sein. Der Zeitstempel ist NICHT zeitzonenspezifisch. Der Zeitstempel setzt sich aus java.util.Date und einem separaten Nanosekundenwert zusammen. In dieser Klasse gibt es keine Zeitzoneninformationen. Genau wie Datum enthält diese Klasse einfach die Anzahl der Millisekunden seit dem 1. Januar 1970, 00:00:00 GMT + Nanos.

In PreparedStatement.setTimestamp (int parameterIndex, Timestamp x, Calendar cal) wird der Kalender vom Treiber verwendet, um die Standardzeitzone zu ändern. Timestamp hält jedoch immer noch Millisekunden in GMT.

Die API ist unklar, wie genau der JDBC-Treiber den Kalender verwenden soll. Anbieter scheinen sich frei zu fühlen, wie sie es interpretieren sollen, z. B. als ich das letzte Mal mit MySQL 5.5 Calendar gearbeitet habe, hat der Treiber den Kalender sowohl in PreparedStatement.setTimestamp als auch in ResultSet.getTimestamp einfach ignoriert.

Evgeniy Dorofeev
quelle
Interessierter Kommentar zu MySQL 5.5
Alex
2
Ich glaube, dass dies ab Java 8+ falsch ist. Innerhalb des Zeitstempels (da er sich von java.util.Date aus erstreckt) befindet sich ein Kalenderattribut, das ein zoneinfo-Attribut enthält, das tatsächlich die Zeitzone enthält. Sie können die UTC-Zeit weiterhin mit .getTime () abrufen, da der Zeitstempel sie ebenfalls speichert, sie jedoch Zeitzoneninformationen enthält.
Jorge.V
1
Sie meinen, es enthält die Anzahl der Millisekunden seit dem 1. Januar 1970, 00:00:00 UTC und nicht GMT. GMT hat Sommerzeit. Die UNIX-Epoche beträgt Millisekunden seit dem 1. Januar 1970, 00:00 UTC.
Kirby
6

Für MySQL haben wir eine Einschränkung. Im Treiber-MySQL-Dokument haben wir:

Im Folgenden sind einige bekannte Probleme und Einschränkungen für MySQL Connector / J aufgeführt: Wenn Connector / J mithilfe der Methode getTimeStamp () für die Ergebnismenge Zeitstempel für einen Sommerzeit-Umschalttag abruft, sind möglicherweise einige der zurückgegebenen Werte falsch. Die Fehler können vermieden werden, indem beim Herstellen einer Verbindung zu einer Datenbank die folgenden Verbindungsoptionen verwendet werden:

useTimezone=true
useLegacyDatetimeCode=false
serverTimezone=UTC

Wenn wir diese Parameter nicht verwenden und setTimestamp or getTimestampmit oder ohne Kalender aufrufen , haben wir den Zeitstempel in der JVM-Zeitzone.

Beispiel:

Die JVM-Zeitzone ist GMT + 2. In der Datenbank haben wir einen Zeitstempel: 1461100256 = 19/04/16 21: 10: 56,000000000 GMT

Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "");
props.setProperty("useTimezone", "true");
props.setProperty("useLegacyDatetimeCode", "false");
props.setProperty("serverTimezone", "UTC");
Connection con = DriverManager.getConnection(conString, props);
......
Calendar nowGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar nowGMTPlus4 = Calendar.getInstance(TimeZone.getTimeZone("GMT+4"));
......
rs.getTimestamp("timestampColumn");//Oracle driver convert date to jvm timezone and Mysql convert date to GMT (specified in the parameter)
rs.getTimestamp("timestampColumn", nowGMT);//convert date to GMT 
rs.getTimestamp("timestampColumn", nowGMTPlus4);//convert date to GMT+4 timezone

Die erste Methode gibt Folgendes zurück: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Die zweite Methode gibt Folgendes zurück: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Die dritte Methode gibt Folgendes zurück: 1461085856000 = 19/04/2016 - 17:10:56 GMT

Wenn wir anstelle von Oracle dieselben Aufrufe verwenden, haben wir:

Die erste Methode gibt Folgendes zurück: 1461093056000 = 19/04/2016 - 19:10:56 GMT

Die zweite Methode gibt Folgendes zurück: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Die dritte Methode gibt Folgendes zurück: 1461085856000 = 19/04/2016 - 17:10:56 GMT

NB: Es ist nicht erforderlich, die Parameter für Oracle anzugeben.

Abdelhafid
quelle
1
Ich denke, Sie könnten für Oracle die Sitzungszeitzone auf UTC setzen, um die gleiche Konvertierung zu erzielen wie MySQL für den Fall ohne Kalender. " ALTER SESSION SET TIME_ZONE = 'UTC'".
eckes
5

Es ist spezifisch von Ihrem Fahrer. Sie müssen einen Parameter in Ihrem Java-Programm angeben, um ihm die Zeitzone mitzuteilen, die Sie verwenden möchten.

java -Duser.timezone="America/New_York" GetCurrentDateTimeZone

Weiter dies:

to_char(new_time(sched_start_time, 'CURRENT_TIMEZONE', 'NEW_TIMEZONE'), 'MM/DD/YY HH:MI AM')

Kann auch für die ordnungsgemäße Handhabung der Konvertierung von Nutzen sein. Von hier genommen

Woot4Moo
quelle
Wollten Sie, dass ich stackoverflow.com/questions/2858182/… folge, oder möchten Sie meine JVM-Anwendung so einstellen, dass sie in GMT ausgeführt wird?
Kanagavelu Sugumar
@ KanagaveluSugumar gut das kommt darauf an. Wenn Sie den Zeitstempel für JEDE Abfrage ändern möchten, legen Sie ihn beim Start fest. Wenn Sie nur EINIGE (dh weniger als JEDE) ändern möchten, ändern Sie jede Abfrage in der Weise, wie sie in dem von Ihnen referenzierten Beitrag festgelegt ist.
Woot4Moo
4

Die Antwort ist, dass dies java.sql.Timestampein Chaos ist und vermieden werden sollte. Verwenden Sie java.time.LocalDateTimestattdessen.

Warum ist es ein Chaos? Aus dem java.sql.TimestampJavaDoc ist a java.sql.Timestampein "Thin Wrapper" java.util.Date, mit dem die JDBC-API dies als SQL TIMESTAMP-Wert identifizieren kann. Von demjava.util.Date JavaDoc geht hervor, dass "die DateKlasse die koordinierte Weltzeit (UTC) widerspiegeln soll". Aus der ISO SQL-Spezifikation geht hervor, dass ein TIMESTAMP OHNE ZEITZONE "ein Datentyp ist, der datetime ohne Zeitzone ist". TIMESTAMP ist eine Kurzbezeichnung für TIMESTAMP WITHOUT TIME ZONE. Ein java.sql.Timestamp"reflektiert" UTC, während SQL TIMESTAMP "ohne Zeitzone" ist.

weil java.sql.Timestamp UTC seine Methoden widerspiegelt, werden Konvertierungen angewendet. Dies führt zu keinerlei Verwirrung. Aus SQL-Sicht ist es nicht sinnvoll, einen SQL-TIMESTAMP-Wert in eine andere Zeitzone zu konvertieren, da ein TIMESTAMP keine Zeitzone zum Konvertieren hat. Was bedeutet es, 42 in Fahrenheit umzurechnen? Es bedeutet nichts, weil 42 keine Temperatureinheiten hat. Es ist nur eine bloße Zahl. Ebenso können Sie einen TIMESTAMP von 2020-07-22T10: 38: 00 nicht nach Americas / Los Angeles konvertieren, da sich 2020-07-22T10: 30: 00 in keiner Zeitzone befindet. Es ist nicht in UTC oder GMT oder irgendetwas anderem. Es ist eine nackte Datumszeit.

java.time.LocalDateTimeist auch eine bloße Datumszeit. Es hat keine Zeitzone, genau wie SQL TIMESTAMP. Keine seiner Methoden wendet irgendeine Art von Zeitzonenumwandlung an, wodurch das Verhalten viel einfacher vorherzusagen und zu verstehen ist. Also nicht benutzen java.sql.Timestamp. Verwenden Sie java.time.LocalDateTime.

LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);
Douglas Surber
quelle
Also sehr gut gesagt. Vielen Dank.
Ole VV
1

Mit der folgenden Methode können Sie den Zeitstempel in einer Datenbank speichern, die für Ihre gewünschte Zone / Zonen-ID spezifisch ist.

ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Calcutta")) ;
Timestamp timestamp = Timestamp.valueOf(zdt.toLocalDateTime());

Ein häufiger Fehler, den Menschen machen, ist die Verwendung LocaleDateTime darin, den Zeitstempel dieses Augenblicks abzurufen, der alle für Ihre Zone angegebenen Informationen verwirft, selbst wenn Sie später versuchen, sie zu konvertieren. Es versteht die Zone nicht.

Bitte beachten Sie, Timestampist von der Klasse java.sql.Timestamp.

Ayush Kumar
quelle