2016-05-31 16 views
5

Stavo scrivendo alcune testcode per java-8 conversione tra java.util.Date e java.time.LocalDateTime, e ha scoperto un'anomalia sembra verificarsi nel ora dopo la transizione da normaltime-to-estate , quando l'anno è 2038 o superiore.Bug in jdk8 date-conversion?

Volevo solo sapere se questo è un bug in jdk8, o se sto facendo qualcosa di sbagliato?

Nota: Sono su Windows-7, 64 bit JDK, quindi non dovrebbe essere influenzata dal bug 2038-unix, che avrebbe un effetto molto peggio.

Qui il mio demo-code:

package conversiontest; 

import java.text.SimpleDateFormat; 
import java.time.LocalDate; 
import java.time.LocalDateTime; 
import java.time.LocalTime; 
import java.time.ZoneId; 

public class ConversionTest { 

    public static void main(String[] args) { 
     new ConversionTest().testDateConversion(); 
    } 

    // Method under test: 
    public java.util.Date toJavaUtilDate(LocalDateTime localDateTime) { 
     return java.util.Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); 
    } 

    // Test-code: 
    public void testDateConversion() { 
     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
     LocalDate localDate = LocalDate.of(2016, 1, 1); 
     LocalTime localTime = LocalTime.of(3, 22, 22); // 03:22:22 
     while (!localDate.toString().startsWith("2045-")) { 
      LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); 
      java.util.Date date = toJavaUtilDate(localDateTime); 
      String sLocalDateTime = localDateTime.toString().replace("T", " "); 
      String sJavaUtilDate = sdf.format(date); 
      if (!sLocalDateTime.equals(sJavaUtilDate)) { 
       System.out.println(String.format("FAILURE: '%s' != '%s'", sLocalDateTime, sJavaUtilDate)); 
      } 
      localDate = localDate.plusDays(1); 
     } 
    } 

} 

uscita:

FAILURE: '2038-03-28 03:22:22' != '2038-03-28 02:22:22' 
FAILURE: '2039-03-27 03:22:22' != '2039-03-27 02:22:22' 
FAILURE: '2040-03-25 03:22:22' != '2040-03-25 02:22:22' 
FAILURE: '2041-03-31 03:22:22' != '2041-03-31 02:22:22' 
FAILURE: '2042-03-30 03:22:22' != '2042-03-30 02:22:22' 
FAILURE: '2043-03-29 03:22:22' != '2043-03-29 02:22:22' 
FAILURE: '2044-03-27 03:22:22' != '2044-03-27 02:22:22' 

Come si può vedere dall'output, LocalDateTime (2038/03/28 : 22: 22) viene convertito in java.util.Date (2038-03-28 : 22: 22), ecc. Ma non quando l'anno è inferiore a 2038.

Qualcuno ha qualche input per questo?

EDIT:

mio ZoneId.systemDefault() dà: "Europe/Berlin"

C:\>java -version 
java version "1.8.0_91" 
Java(TM) SE Runtime Environment (build 1.8.0_91-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b15, mixed mode) 

C:\>javac -version 
javac 1.8.0_91 
+4

Suggerimento: hai google 'java.util.date 2038'. Ciò si traduce, ad esempio, http://stackoverflow.com/questions/15014589/java-util-date-bug e per il divertimento di leggere anche http://stackoverflow.com/questions/4313707/why-should-a-java-programmer -care-about-year-2038-bug – Dilettant

+0

Java è basato sul tempo Unix, e l'anno 2038 è la versione Unix di Windows y2k bug – yonisha

+0

Grazie per i collegamenti, ma leggendoli attentamente, questo sembra diverso. Java non è ** supposto che abbia il 2038-bug su un sistema Windows a 64 bit, che sto usando. Ho dimenticato di dirlo, mi dispiace. – Rop

risposta

5

I diversi risultati derivano da una mancata corrispondenza nel passaggio all'ora legale, che inizia alle date 2038. È possibile visualizzare la differenza utilizzando il seguente codice:

// for reproducible results 
System.setProperty("user.timezone", "Europe/Berlin"); 

LocalDate[] dates = {LocalDate.of(2037, 3, 29), LocalDate.of(2038, 3, 28)}; 
LocalTime[] time = { LocalTime.of(0, 59, 59), LocalTime.of(1, 00, 01), 
         LocalTime.of(1, 59, 59), LocalTime.of(2, 00, 01) }; 
for(LocalDate localDate : dates) { 
    for(LocalTime localTime1 : time) { 
     ZonedDateTime zoned = LocalDateTime.of(localDate, localTime1) 
          .atZone(ZoneId.of("UTC")) 
          .withZoneSameInstant(ZoneId.systemDefault()); 
     System.out.println(zoned); 
     System.out.println(new java.util.Date(zoned.toEpochSecond()*1000)); 
    } 
    System.out.println(); 
} 

che stamperà:

2037-03-29T01:59:59+01:00[Europe/Berlin] 
Sun Mar 29 01:59:59 CET 2037 
2037-03-29T03:00:01+02:00[Europe/Berlin] 
Sun Mar 29 03:00:01 CEST 2037 
2037-03-29T03:59:59+02:00[Europe/Berlin] 
Sun Mar 29 03:59:59 CEST 2037 
2037-03-29T04:00:01+02:00[Europe/Berlin] 
Sun Mar 29 04:00:01 CEST 2037 

2038-03-28T01:59:59+01:00[Europe/Berlin] 
Sun Mar 28 01:59:59 CET 2038 
2038-03-28T03:00:01+02:00[Europe/Berlin] 
Sun Mar 28 02:00:01 CET 2038 
2038-03-28T03:59:59+02:00[Europe/Berlin] 
Sun Mar 28 02:59:59 CET 2038 
2038-03-28T04:00:01+02:00[Europe/Berlin] 
Sun Mar 28 04:00:01 CEST 2038 

Come possiamo vedere, entrambe le implementazioni concordano l'istante in cui commutare all'ora legale nel 2037, mentre l'attuazione java.util.* passa un'ora dopo nel 2038.

Questo cambiamento comportamentale deriva da un table of hardcoded transition times in sun.util.calendar.ZoneInfo cui ha una dimensione finita Come possiamo vedere at this point, il codice si ramifica in base al ritorno dell'indice con il metodo getTransitionIndex. Se l'indice è uguale o superiore alla lunghezza della tabella, it falls over to using a SimpleTimeZone implementation.

Possiamo verificare che ciò avvenga:

long l1 = LocalDateTime.of(LocalDate.of(2037, 3, 29), LocalTime.of(1, 00, 01)) 
         .atZone(ZoneId.of("UTC")).toInstant().getEpochSecond()*1000; 
long l2 = LocalDateTime.of(LocalDate.of(2038, 3, 28), LocalTime.of(1, 00, 01)) 
         .atZone(ZoneId.of("UTC")).toInstant().getEpochSecond()*1000; 

TimeZone zone=TimeZone.getTimeZone("Europe/Berlin"); 
Field table=zone.getClass().getDeclaredField("transitions"); 
table.setAccessible(true); 
System.out.println("table length="+((long[])table.get(zone)).length); 

Method getTransitionIndex = zone.getClass() 
    .getDeclaredMethod("getTransitionIndex", long.class, int.class); 
getTransitionIndex.setAccessible(true); 
final Integer UTC_TIME = 0; 
int indexFor2037 = (Integer)getTransitionIndex.invoke(zone, l1, UTC_TIME); 
System.out.println("index for 2037="+indexFor2037); 
int indexFor2038 = (Integer)getTransitionIndex.invoke(zone, l2, UTC_TIME); 
System.out.println("index for 2038="+indexFor2038); 

stampe sul mio sistema:

table length=143 
index for 2037=141 
index for 2038=143 

non so di eventuali piani per cambiare il periodo estivo di commutazione nel 2038, quindi suppongo l'implementazione di java.time è corretta.È anche ovvio che qualsiasi implementazione basata su una tabella finita di valori codificati presenta una limitazione naturale ...

+1

Circa l'ultima frase, il JSR-310 conosce anche una "tabella finita di valori hardcoded" E un insieme di regole come la vecchia API rappresentata da [ZoneRules.getTransitions ()] (http://docs.oracle.com/javase/8/docs/api/java/time/zone/ZoneRules.html#getTransitions--) o 'getTransitionRules()' Questo fatto non è solo una limitazione.Ma ho il forte sospetto che l'API basata su 'Calendar' sia diventata sbagliata in tutto il passaggio dai valori di tabella alle regole nel 2038 quando si sostituiva il vecchio formato zi con tzdb.dat. (2038 può essere spiegato dal fatto che il formato ZoneInfo originale (file zi) era limitato all'intervallo.) –

+2

@Meno Hochschild: Non stavo usando il termine "limitazione" come "deve diventare sbagliato in un certo punto". Implica un interruttore comportamentale e ha un maggiore sforzo di manutenzione. Come dice la documentazione di 'getTransitions()', di solito sono storici, il che ha senso, poiché la storia tende ad essere piena di cambiamenti ed eccezioni, mentre per il futuro abbiamo solo una regola corrente (quindi non è chiaro il motivo per cui 'SimpleTimeZone' fa è sbagliato, forse, non è stato testato perché il tavolo raggiunge il 2037). Naturalmente, una volta che guardiamo indietro all'anno 2039, potremmo avere molti dati storici, cambiamenti ed eccezioni ... – Holger