2010-11-05 17 views
5

Abbiamo un'applicazione in cui la tempistica è fondamentale. Stiamo usando joda per fare conversioni temporali e memorizzare tutti i dati in tempo UTC. Siamo stati in produzione per un po 'e tutto è stato perfetto MA ...Il tempo di Joda si converte in "troppo presto"

Ora notiamo che gli eventi che si verificano poche ore prima del cambio di orario sono già stati convertiti troppo presto! In effetti, le ore UTC salvate nel database sono disattivate di un'ora.

Ecco un esempio. Il mio evento si terrà l'11/06/2010 alle 9pm PDT e verrebbe normalmente salvato come 11/7/2010 alle 4 del mattino. Tuttavia, poiché il Daylight Savings Time è terminato il 7 (presumibilmente alle 2:00), questa volta viene spostato e archiviato come 11/7/2010 alle 5:00.

Abbiamo bisogno che la modifica dell'ora legale non venga registrata finché non si verifica effettivamente nell'area PST, alle 2:00 PST. Supponevo che Joda potesse gestirlo, specialmente dal momento che è pubblicizzato per essere molto migliorato rispetto alle funzionalità di default di java.

Qualsiasi riscontro sarà utile, soprattutto se ce la farai prima che il tempo cambi domani! Dopo quello sarà accademico, ma ancora una discussione utile.

Ecco una parte del codice che usiamo per eseguire una modifica del fuso orario e ottenere il risultato come un normale oggetto data java.

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; 
} 

Edit: Esempio di codice per i commenti qui sotto

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

completa di test di tipo Esempio

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

Invece di fornire un singolo metodo, potresti fornire un programma breve ma completo che mostri il problema? –

risposta

10

il codice è fondamentalmente rotto, perché un oggetto non può Date essere "convertito" tra fusi orari - rappresenta un istante nel tempo. getTime() restituisce l'ora in millis dall'epoca UTC Unix. A Date non dipende da un fuso orario, quindi l'idea di convertire un'istanza di Date da un fuso orario a un altro non ha senso. È un po 'come convertire uno int da "base 10" a "base 16" - le basi hanno solo senso quando si pensa a una rappresentazione in cifre anziché al numero fondamentale.

Si deve usare LocalDateTime rappresentare data/volte senza un fuso fissa o DateTime rappresentare data/volte con un fuso orario specifico, o Instant per rappresentare lo stesso tipo di concetto come java.util.Date.

Una volta utilizzati i tipi appropriati, sono sicuro che non si verificheranno problemi.

EDIT: Se il codice vero e proprio sta usando una Calendar con il fuso orario giusto, non è necessario fare nulla convertire tale a UTC. Basta chiamare il numero calendar.getTime() e ti darà l'appropriato Date.

Ad esempio:

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

In sostanza non è chiaro perché si sta cercando di convertire da Stati Uniti/Arizona a US/Pacific quando si parla di cercare di salvare UTC ...

+0

Per ora siamo bloccati con java.util.Date poiché stiamo utilizzando la modalità di sospensione per mantenere i dati. La mia comprensione è che joda applica un offset alla data, anche se l'oggetto data non è realmente a conoscenza del fuso orario. Se il codice è fondamentalmente rotto, non sarebbe rotto tutto il tempo, invece di solo 14 ore all'anno? – samspot

+0

@samspot: non è che l'oggetto data non sia realmente a conoscenza del fuso orario: è che la data rappresenta sempre un istante. "Convertire" una data tra fusi orari non ha senso. Per quanto riguarda il motivo per cui non è rotto tutto il tempo - un esempio breve ma completo sarebbe davvero di aiuto qui. Solo perché devi usare "Date" a * qualche * punto non significa che devi usarlo * ovunque *, vero? –

+0

Hey Jon, ho aggiunto un esempio non joda che funziona alla grande. Questo è quello che pensavo che Joda stesse facendo per me, in primo luogo. Stai dicendo che ho bisogno di usare qualche altro aspetto di joda per raggiungere questo obiettivo? – samspot

Problemi correlati