2010-11-05 11 views
5

Tenemos una aplicación donde el tiempo es crítico. Estamos usando joda para hacer conversiones de tiempo y almacenar todos los datos en tiempo UTC. Hemos estado en producción por un tiempo y todo ha sido perfecto PERO ...Joda time hace la conversión de tiempo 'demasiado pronto'

¡Ahora notamos que los eventos que ocurren algunas horas antes de que el cambio de horario ya se hayan convertido demasiado temprano! De hecho, los tiempos UTC guardados en la base de datos están desactivados por una hora.

Aquí hay un ejemplo. Mi evento ocurre el 11/6/2010 a las 9 p.m. PDT y normalmente se guardará el 11/7/2010 a las 4 a. M. Sin embargo, debido a que el horario de verano terminó el día 7 (presumiblemente a las 2 a. M), esta vez se desplazó y se almacenó como el 11/7/2010 a las 5 a. M.

Necesitamos que el cambio DST no se grabe hasta que realmente se produzca en la región PST, a las 2 a.m. PST. Supuse que joda manejaría esto, especialmente porque se dice que se mejoró mucho con respecto a la funcionalidad predeterminada de Java.

¡Cualquier comentario que usted tenga sería útil, especialmente si nos lo puede conseguir antes de que la hora cambie mañana! Después de eso será académico, pero aún así una discusión útil.

Este es un código que usamos para realizar un cambio de zona horaria y obtener el resultado como un objeto de fecha java normal.

public Date convertToTimeZone(Date dt, TimeZone from, TimeZone to){ 
    DateTimeZone tzFrom = DateTimeZone.forTimeZone(from); 
    DateTimeZone tzTo = DateTimeZone.forTimeZone(to); 

    Date utc = new Date(tzFrom.convertLocalToUTC(dt.getTime(), false)); 
    Date convertedTime = new Date(tzTo.convertUTCToLocal(utc.getTime())); 
    return convertedTime; 
} 

Editar: Código de ejemplo para los comentarios a continuación

public Date convert(Date dt, TimeZone from, TimeZone to) { 
    long fromOffset = from.getOffset(dt.getTime()); 
    long toOffset = to.getOffset(dt.getTime()); 

    long convertedTime = dt.getTime() - (fromOffset - toOffset); 
    return new Date(convertedTime); 
} 

Prueba de la unidad completa Ejemplo

package com.test.time; 

import java.util.Calendar; 
import java.util.Date; 
import java.util.TimeZone; 

import org.joda.time.DateTime; 
import org.joda.time.DateTimeZone; 
import org.joda.time.Instant; 
import org.junit.Before; 
import org.junit.Test; 

public class TimeTest { 
Calendar nov6; 
Calendar nov1; 
Calendar nov12; 

@Before 
public void doBefore() { 
    // November 1st 2010, 9:00pm (DST is active) 
    nov1 = Calendar.getInstance(); 
    nov1.setTimeZone(TimeZone.getTimeZone("US/Arizona")); 
    nov1.set(Calendar.HOUR_OF_DAY, 21); 
    nov1.set(Calendar.MINUTE, 0); 
    nov1.set(Calendar.SECOND, 0); 
    nov1.set(Calendar.YEAR, 2010); 
    nov1.set(Calendar.MONTH, 10); // November 
    nov1.set(Calendar.DATE, 1); 

    // November 6st 2010, 9:00pm (DST is still active until early AM november 7th) 
    nov6 = Calendar.getInstance(); 
    nov6.setTimeZone(TimeZone.getTimeZone("US/Arizona")); 
    nov6.set(Calendar.HOUR_OF_DAY, 21); 
    nov6.set(Calendar.MINUTE, 0); 
    nov6.set(Calendar.SECOND, 0); 
    nov6.set(Calendar.YEAR, 2010); 
    nov6.set(Calendar.MONTH, 10); // November 
    nov6.set(Calendar.DATE, 6); 

    // November 12th 2010, 9:00pm (DST has ended) 
    nov12 = Calendar.getInstance(); 
    nov12.setTimeZone(TimeZone.getTimeZone("US/Arizona")); 
    nov12.set(Calendar.HOUR_OF_DAY, 21); 
    nov12.set(Calendar.MINUTE, 0); 
    nov12.set(Calendar.SECOND, 0); 
    nov12.set(Calendar.YEAR, 2010); 
    nov12.set(Calendar.MONTH, 10); // November 
    nov12.set(Calendar.DATE, 12); 
} 

@Test 
public void test1() { 
    //  System.out.println("test1"); 
    timeTestJava(nov1.getTime(), "equivalent", "US/Arizona", "US/Pacific"); 
    timeTestJodaOld(nov1.getTime(), "equivalent", "US/Arizona", "US/Pacific"); 
    timeTestJodaNew(nov1.getTime(), "equivalent", "US/Arizona", "US/Pacific"); 

    timeTestJava(nov6.getTime(), "equivalent", "US/Arizona", "US/Pacific"); 
    timeTestJodaOld(nov6.getTime(), "equivalent", "US/Arizona", "US/Pacific"); 
    timeTestJodaNew(nov6.getTime(), "equivalent", "US/Arizona", "US/Pacific"); 

    timeTestJava(nov12.getTime(), "minus1", "US/Arizona", "US/Pacific"); 
    timeTestJodaOld(nov12.getTime(), "minus1", "US/Arizona", "US/Pacific"); 
    timeTestJodaNew(nov12.getTime(), "minus1", "US/Arizona", "US/Pacific"); 
} 

private void timeTestJodaOld(Date startTime, String text, String from, String to) { 
    System.out.println("joda_old: " + startTime); 
    Date output = convertJodaOld(startTime, TimeZone.getTimeZone(from), TimeZone.getTimeZone(to)); 
    System.out.println(text + ": " + output + "\n"); 
} 

private void timeTestJodaNew(Date startTime, String text, String from, String to) { 
    System.out.println("joda_new: " + startTime); 
    Date output = convertJodaNew(startTime, TimeZone.getTimeZone(from), TimeZone.getTimeZone(to)); 
    System.out.println(text + ": " + output + "\n"); 
} 

private void timeTestJava(Date startTime, String text, String from, String to) { 
    System.out.println("java: " + startTime); 
    Date output = convertJava(startTime, TimeZone.getTimeZone(from), TimeZone.getTimeZone(to)); 
    System.out.println(text + ": " + output + "\n"); 
} 

// Initial Joda implementation, works before and after DST change, but not during the period from 2am-7am UTC on the day of the change 
public Date convertJodaOld(Date dt, TimeZone from, TimeZone to) { 
    DateTimeZone tzFrom = DateTimeZone.forTimeZone(from); 
    DateTimeZone tzTo = DateTimeZone.forTimeZone(to); 

    Date utc = new Date(tzFrom.convertLocalToUTC(dt.getTime(), false)); 
    Date convertedTime = new Date(tzTo.convertUTCToLocal(utc.getTime())); 
    return convertedTime; 
} 

// New attempt at joda implementation, doesn't work after DST (winter) 
public Date convertJodaNew(Date dt, TimeZone from, TimeZone to) { 
    Instant utcInstant = new Instant(dt.getTime()); 
    DateTime datetime = new DateTime(utcInstant); 

    datetime.withZone(DateTimeZone.forID(to.getID())); 
    return datetime.toDate(); 
} 

// Java implementation. Works. 
public Date convertJava(Date dt, TimeZone from, TimeZone to) { 
    long fromOffset = from.getOffset(dt.getTime()); 
    long toOffset = to.getOffset(dt.getTime()); 

    long convertedTime = dt.getTime() - (fromOffset - toOffset); 
    return new Date(convertedTime); 
} 

}

+0

En lugar de proporcionar un solo método, ¿podría proporcionar un programa breve pero completo que demuestre el problema? –

Respuesta

10

Su código se divide fundamentalmente, ya que un objeto no puede Date ser "convertido" entre zonas horarias - representa un instante en el tiempo. getTime() devuelve el tiempo en milis desde el UTC Unix epoch. Un Date no depende de un huso horario, por lo que la idea de convertir una instancia de Date de un huso horario a otro no tiene sentido. Es un poco como convertir un int de "base 10" a "base 16" - las bases solo tienen sentido cuando piensas en una representación en dígitos en lugar del número fundamental.

Debería utilizar para representar LocalDateTime fecha/veces sin una zona de tiempo fijo, o DateTime para representar fecha/horas con una zona horaria específica, o Instant para representar el mismo tipo de concepto que java.util.Date.

Una vez que utilice los tipos adecuados, estoy seguro de que no tendrá ningún problema.

EDIT: Si el código real utiliza una Calendar con la zona horaria correcta, usted no necesita hacer nada para convertir eso a UTC. Simplemente llame al calendar.getTime() y le dará el Date apropiado.

Por ejemplo:

// Display all Date values as UTC for convenience 
    TimeZone.setDefault(TimeZone.getTimeZone("UTC")); 

    // November 6st 2010, 9:00pm (DST is still active until 
    // early AM november 7th) 
    Calendar nov6 = Calendar.getInstance(); 
    nov6.setTimeZone(TimeZone.getTimeZone("US/Arizona")); 
    nov6.set(Calendar.HOUR_OF_DAY, 21); 
    nov6.set(Calendar.MINUTE, 0); 
    nov6.set(Calendar.SECOND, 0); 
    nov6.set(Calendar.YEAR, 2010); 
    nov6.set(Calendar.MONTH, 10); // November 
    nov6.set(Calendar.DATE, 6); 

    // Prints Sun Nov 07 04:00:00 UTC 2010 which is correct 
    System.out.println("Local Nov6 = " + nov6.getTime()); 

Básicamente no es claro por qué usted está tratando de convertir de Estados Unidos/Arizona a US/Pacífico cuando se habla de tratar de salvar a UTC ...

+0

Estamos atrapados con java.util.Date por ahora ya que estamos usando hibernate para persistir los datos. Según entiendo, joda está aplicando un desplazamiento a la fecha, aunque el objeto date no sea realmente consciente de la zona horaria. Si el código está fundamentalmente roto, ¿no estaría roto todo el tiempo, en lugar de solo 14 horas al año? – samspot

+0

@samspot: No es que el objeto de fecha no sea realmente consciente de la zona horaria, es que la fecha siempre representa un instante. "Convertir" una fecha entre zonas horarias no tiene sentido. En cuanto a por qué no está roto todo el tiempo, un ejemplo breve pero completo realmente ayudaría aquí. El hecho de que tenga que usar 'Date' en * algún * punto no significa que deba usarlo * en todas partes *, ¿verdad? –

+0

Hola Jon, agregué una muestra no joda que funciona muy bien. Esto es lo que pensé que Joda estaba haciendo por mí en primer lugar. ¿Estás diciendo que necesito usar algún otro aspecto de joda para lograr esto? – samspot

Cuestiones relacionadas