2016-05-31 86 views
5

我在寫一些testcode爲java.time.LocalDateTime的java-8的轉換java.util.Date和之間,發現異常似乎從normaltime到夏季的過渡後,發生在小時,當年是2038年或更高。jdk8日期轉換中的錯誤?

我只是想知道這是否是jdk8中的錯誤,或者我做錯了什麼?

注:我對Windows的7,64位JDK,所以不應該由2038 UNIX的錯誤,這將有更糟糕的效果受到影響。

這裏我的演示代碼:

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

} 

輸出:

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' 

正如你可以從輸出,LocalDateTime見(2038年3月28日:22:22) 被轉換爲java.util.Date(2038-03-28 :22:22)等。但是不是當年低於2038時。

任何人都有一些輸入呢?

編輯:

我ZoneId.systemDefault()給出: 「歐洲/柏林」

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

提示:您是否在谷歌'java.util.date 2038'。這產生例如http://stackoverflow.com/questions/15014589/java-util-date-bug和閱讀的樂趣也http://stackoverflow.com/questions/4313707/why-should-a-java-programmer -care-about-year-2038-bug – Dilettant

+0

Java基於Unix時間,而2038年是Unix的Windows版本y2k bug – yonisha

+0

感謝您的鏈接,但仔細閱讀它們,這看起來不同。 Java是**不**應該有一個64位的Windows系統,我正在使用2038錯誤。忘了提到那個,對不起。 – Rop

回答

5

不同的結果,從一個不匹配的幹中切換到夏天的時候,它開始於2012年新

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

,它將打印:2038年,您可以使用下面的代碼可視化差異

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 

我們可以看到,無論是實現同意在該切換到夏天的時候在2037的瞬間,而java.util.*實現一小時後在切換2038年

這種行爲的改變源於一個table of hardcoded transition times in sun.util.calendar.ZoneInfo其中具有有限的尺寸。正如我們可以看到at this point,代碼根據索引返回的方法getTransitionIndex分支。如果指數等於或高於表長度,則爲it falls over to using a SimpleTimeZone implementation

我們可以驗證這種情況的發生:

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

我的系統上打印:

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

我不知道有任何計劃改變夏天的時候在2038開關,所以我想java.time實施是正確的。同樣顯而易見的是,基於硬編碼值的有限表的任何實現都有自然限制...

+1

關於你的最後一句,JSR-310也知道一個「硬編碼值的有限表」和一組規則,比如由[ZoneRules.getTransitions( )](http://docs.oracle.com/javase/8/docs/api/java/time/zone/ZoneRules.html#getTransitions--)或'getTransitionRules()'這個事實只是不是限制。但是我強烈懷疑在2038年,當用tzdb.dat替換舊的zi格式時,基於Calendar'的API在2038年從表值切換到規則時出現了錯誤。 (2038可以通過原始ZoneInfo格式(zi-files)被限制在int範圍的事實來解釋。) –

+2

@Meno Hochschild:我沒有使用術語「限制」作爲「必須在某個點」。它意味着行爲轉換並具有較高的維護成本。正如'getTransitions()'的文檔所說,它們通常是歷史的,這是有道理的,因爲歷史往往充滿了變化和例外,而對於未來,我們只有一個當前的規則(所以目前還不清楚爲什麼'SimpleTimeZone'會這可能是錯誤的,也許它在表格達到2037年時未經測試)。當然,一旦我們回顧2039年,我們可能會有很多歷史數據,變化和例外...... – Holger