2012-05-09 21 views
6

Tengo un objeto java.util.Date, y tengo que insertarlo en un campo de fecha y hora en MySQL en el formato UTC.Java MySQL Timestamp huso horario problemas

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()); 

Lo que me da la serie de sentencia SQL preparada:

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

La marca de tiempo devuelto es el mismo sello de tiempo, independientemente de la zona horaria especifico. Está ignorando el objeto Calendario con la zona horaria que especifico. ¿Qué está pasando y qué estoy haciendo mal?

+2

Las zonas horarias no cambian la marca de tiempo. Simplemente cambian lo que se muestra cuando se representa la fecha. – Jeremy

+0

Sí, entiendo que una marca de tiempo es solo un número de milisegundos desde época GMT, independientemente de la zona horaria. Mi problema es que incluso especificando la zona horaria, está representando exactamente la misma fecha de visualización (ver que aunque he especificado diferentes zonas horarias, la fecha fue exactamente igual en el último comando SQL). – Jordan

+0

Creo que esto es lo que está buscando: http://puretech.paawak.com/2010/11/02/how-to-handle-oracle-timestamp-with-timezone-from-java/ – Chris

Respuesta

2

Las zonas horarias son solo formas diferentes de ver una fecha (que es un punto fijo en el tiempo). Escribí un pequeño ejemplo aquí (prestar mucha atención a la aserción):

// 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); 

La salida de este fragmento es (el resultado será diferente dependiendo de la fecha de ejecución/hora):

8:18 BST 
9:18 CEST 

Su fragmento de la pregunta simplemente no está haciendo nada con respecto a la fecha de almacenamiento. Por lo general, las bases de datos están configuradas para un TimeZone nativo. Recomiendo almacenar un campo adicional que represente el TimeZone que se utilizará al interpretar la fecha.

No es (generalmente) una buena idea modificar fechas (que son esencialmente solo milisegundos antes/después de un punto fijo en el tiempo) ya que sería una modificación con pérdidas que se interpretaría de manera diferente en diferentes puntos del año (debido al horario de verano).

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

+0

En realidad, él pudo recibir diferentes valores en la base de datos, si su base de datos era Oracle y utilizó el tipo de columna 'TIMESTAMP WITH TIME ZONE'. Pero donde no hay soporte de zona horaria para fechas en MySQL. –

+0

Sí, puse un enlace en la parte inferior para una implementación específica de Oracle para almacenar/recuperar TimeZones junto con las fechas. Creo que es más genérico almacenar la zona horaria por separado, ya que la hace más portátil para las bases de datos que no son de Oracle. Lo bueno de las fechas, incluso sin un contexto TimeZone es que se pueden comparar entre sí sin problemas. – Chris

+0

Si realmente necesita persistir con la información de la zona horaria, es mejor utilizar la característica específica de la base de datos (como en Oracle y PostgreSQL) y olvidarse de la portabilidad. De lo contrario, tendrá problemas con la comparación y la clasificación. –

2

Comprobar this link para la explicación de MySQL (y usted no debe tratar de aplicar consejos acerca de Oracle a MySQL).

El tipo de datos TIMESTAMP se utiliza para valores que contienen partes de fecha y hora. TIMESTAMP tiene un rango de '1970-01-01 00:00:01' UTC a '2038-01-19 03:14:07' UTC.

MySQL convierte los valores TIMESTAMP de la zona horaria actual a UTC para el almacenamiento y de regreso de UTC a la zona horaria actual para la recuperación. (Esto no ocurre con otros tipos, como DATETIME.) De forma predeterminada, la zona horaria actual para cada conexión es la hora del servidor.

9

Jordan, en realidad usted tenía la idea correcta. El problema es que hay un error en el controlador JDBC de MySQL y el argumento Calendario se ignora por completo de forma predeterminada. Mire el código fuente de PreparedStatement para ver realmente qué está pasando.

Observe que el formato es la marca de tiempo que utiliza la zona horaria de la JVM. Esto solo funcionará si su JVM usa la zona horaria UTC. El objeto Calendario se ignora por completo.

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

Con el fin de MySQL a usar el argumento Calendario, usted tiene que desactivar el código de fecha legado/hora con la siguiente opción de conexión:

useLegacyDatetimeCode=false 

lo que es posible utilizarlo cuando se conecta a la base de datos de esta manera:

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

Si deshabilita el código heredado de fecha y hora utilizando la línea de arriba, entonces será hacer que su marca de hora en la zona horaria del destino 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); 
} 

Es bastante fácil ver lo que está sucediendo aquí. Si transfiere un objeto de calendario, lo usará al formatear los datos. De lo contrario, usará la zona horaria de la base de datos para formatear los datos. Extrañamente, si pasas un Calendario, también establecerá el tiempo para el valor de Marca de tiempo dado (que parece ser inútil).

Cuestiones relacionadas