2012-05-09 20 views
6

Ho un oggetto java.util.Date e ho bisogno di inserirlo in un campo datetime in MySQL nel formato UTC.Java MySQL Problemi relativi al fuso orario con timestamp

java.util.Date date = myDateFromSomewhereElse; 
PreparedStatement prep = con.prepareStatement(
    "INSERT INTO table (t1, t2) VALUES (?,?)"); 

java.sql.Timestamp t = new Timestamp(date.getTime()); 
prep.setTimestamp(1, t, Calendar.getInstance(TimeZone.getTimeZone("PST")); 
prep.setTimestamp(2, t, Calendar.getInstance(TimeZone.getTimeZone("UTC")); 
System.out.println(prep.toString()); 

che mi dà la stringa di istruzione SQL preparata:

INSERT INTO table (t1, t2) VALUES ('2012-05-09 11:37:08','2012-05-09 11:37:08'); 

Il timestamp restituito è lo stesso timestamp indipendentemente dal fuso orario specificata. Ignora l'oggetto Calendar con il fuso orario specificato. Cosa sta succedendo e cosa sto sbagliando?

+2

I fusi orari non modificano il timestamp. Cambiano solo ciò che viene mostrato quando viene visualizzata la data. – Jeremy

+0

Sì, capisco che un timestamp è solo un numero di millisecondi da epoca GMT, indipendentemente dal fuso orario. Il mio problema è che, anche specificando il fuso orario, si esegue il rendering della stessa esatta data di visualizzazione (si veda anche se ho specificato diversi fusi orari, la data è stata resa esattamente la stessa nel comando finale SQL). – Jordan

+0

Penso che questo possa essere quello che stai cercando: http://puretech.paawak.com/2010/11/02/how-to-handle-oracle-timestamp-with-timezone-from-java/ – Chris

risposta

2

I fusi orari sono solo modi diversi per visualizzare una data (che è un punto fisso nel tempo). Ho scritto un piccolo esempio qui (prestare molta attenzione alla assert):

// timezone independent date (usually interpreted by the timezone of 
// the default locale of the user machine) 
Date now = new Date(); 

// now lets get explicit with how we wish to interpret the date 
Calendar london = Calendar.getInstance(TimeZone.getTimeZone("Europe/London")); 
Calendar paris = Calendar.getInstance(TimeZone.getTimeZone("Europe/Paris")); 

// now set the same date on two different calendar instance 
london.setTime(now); 
paris.setTime(now); 

// the time is the same 
assert london.getTimeInMillis() == paris.getTimeInMillis(); 

// London is interpreted one hour earlier than Paris (as of post date of 9th May 2012) 
String londonTime = london.get(Calendar.HOUR) + ":" + london.get(Calendar.MINUTE); 
String londonTZ = london.getTimeZone().getDisplayName(london.getTimeZone().inDaylightTime(london.getTime()), TimeZone.SHORT); 
System.out.println(londonTime + " " + londonTZ); 

// Paris is interpreted one hour later than Paris (as of post date of 9th May 2012) 
String parisTime = paris.get(Calendar.HOUR) + ":" + paris.get(Calendar.MINUTE); 
String parisTZ = paris.getTimeZone().getDisplayName(paris.getTimeZone().inDaylightTime(paris.getTime()), TimeZone.SHORT); 
System.out.println(parisTime + " " + parisTZ); 

L'uscita di questo frammento è (il risultato sarà diverso a seconda della data di esecuzione/ora):

8:18 BST 
9:18 CEST 

tuo lo snippet nella domanda semplicemente non sta facendo nulla per quanto riguarda la data che viene memorizzata. Solitamente i database sono configurati per un Timezone nativo. Ti consiglio di memorizzare un campo extra che rappresenta il TimeZone da utilizzare quando si interpreta la data.

Non è (generalmente) una buona idea modificare le date (che sono essenzialmente solo millisecondi prima/dopo un punto fisso nel tempo) poiché si tratterebbe di una modifica con perdita che verrebbe interpretata in modo diverso in diversi punti dell'anno (a causa dell'ora legale).

O questo: http://puretech.paawak.com/2010/11/02/how-to-handle-oracle-timestamp-with-timezone-from-java/

+0

In realtà potrebbe ricevere valori diversi nel database, se il suo database era Oracle e ha usato il tipo di colonna 'TIMESTAMP WITH TIME ZONE'. Ma dove non c'è il supporto del fuso orario per le date in MySQL. –

+0

Sì, ho inserito un collegamento nella parte inferiore per un'implementazione specifica di Oracle per l'archiviazione/il recupero di Fusi orari insieme alle date. Penso che sia più generico archiviare il fuso orario separatamente in quanto lo rende più portabile per i database non Oracle. Il bello delle date anche senza un contesto TimeZone è che possono essere confrontati l'uno con l'altro senza problemi. – Chris

+0

Se è davvero necessario mantenere le informazioni sul fuso orario, è meglio utilizzare funzionalità specifiche del database (come Oracle e PostgreSQL) e dimenticare la portabilità. Altrimenti avrete problemi con il confronto e l'ordinamento. –

2

Controllare this link per la spiegazione per MySQL (e non si dovrebbe cercare di applicare consigli su Oracle a MySQL).

Il tipo di dati TIMESTAMP viene utilizzato per i valori che contengono sia parti di data che di orario. TIMESTAMP ha un intervallo di '1970-01-01 00:00:01' UTC in '2038-01-19 03:14:07' UTC.

MySQL converte i valori TIMESTAMP dal fuso orario corrente a UTC per l'archiviazione e viceversa da UTC al fuso orario corrente per il recupero. (Ciò non si verifica per altri tipi come DATETIME.) Per impostazione predefinita, il fuso orario corrente per ogni connessione è l'ora del server.

9

Giordania, in realtà hai avuto l'idea giusta. Il problema è che c'è un bug nel driver JDBC MySQL e l'argomento Calendar è completamente ignorato per impostazione predefinita. Guarda il codice sorgente di PreparedStatement per vedere veramente cosa sta succedendo.

Si noti che il formato è il Timestamp utilizzando il fuso orario della JVM. Funzionerà solo se la tua JVM sta usando il fuso orario UTC. L'oggetto Calendar è completamente ignorato.

this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss''", Locale.US); 
timestampString = this.tsdf.format(x); 

Affinché MySQL utilizzare l'argomento del calendario, è necessario disabilitare il codice data legacy/ora con la seguente opzione di connessione:

useLegacyDatetimeCode=false 

così si potrebbe utilizzare per la connessione al database in questo modo:

String url = "jdbc:mysql://localhost/tz?useLegacyDatetimeCode=false" 

Se si disattiva il codice legacy datetime utilizzando la linea di cui sopra, allora sarà rendere il timestamp nel fuso orario di destinazione del Calendario:

if (targetCalendar != null) { 
    targetCalendar.setTime(x); 
    this.tsdf.setTimeZone(targetCalendar.getTimeZone()); 

    timestampString = this.tsdf.format(x); 
} else { 
    this.tsdf.setTimeZone(this.connection.getServerTimezoneTZ()); 
    timestampString = this.tsdf.format(x); 
} 

È abbastanza facile vedere cosa sta succedendo qui. Se passi in un oggetto Calendar, lo userà durante la formattazione dei dati. In caso contrario, utilizzerà il fuso orario del database per formattare i dati. Stranamente, se passi in un Calendario, imposta anche il tempo al valore di Timestamp specificato (che sembra essere inutile).

Problemi correlati