jdk8日期转换中的错误? [英] Bug in jdk8 date-conversion?

查看:82
本文介绍了jdk8日期转换中的错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 java.util.Date java.time.LocalDateTime 之间编写了一些Java-8转换的测试代码,发现<从正常时间过渡到夏季的强小时后,该年份为2038年或更高.

我只是想知道这是否是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-03-28 03 :22:22) 会转换为java.util.Date(2038-03-28 02 :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

解决方案

不同的结果源于切换到夏令时的不匹配,夏令时始于2038年.您可以使用以下代码直观地看出差异:

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

将打印:

 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年一小时后切换.

此行为更改源自此时,代码将根据方法getTransitionIndex返回的索引进行分支.如果索引等于或大于表长度,则 table length=143 index for 2037=141 index for 2038=143

我不知道有任何计划在2038年更改夏令时,因此我认为java.time实施是正确的.同样显而易见的是,任何基于硬编码值的有限表的实现都有一个自然的局限性……

I was writing some testcode for java-8 conversion between java.util.Date and java.time.LocalDateTime, and discovered an anomaly seems to occur in the hour after the transition from normaltime-to-summertime, when the year is 2038 or higher.

I just wanted to know if this is a bug in jdk8, or if I am doing something wrong?

Note: I am on Windows-7, 64-bit jdk, so should not be affected by the 2038-unix bug, which would have a much worse effect.

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

}

Output:

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'

As you can see from the output, LocalDateTime(2038-03-28 03:22:22) gets converted to java.util.Date(2038-03-28 02:22:22), etc. But not when the year is lower than 2038.

Anyone has some input to this?

EDIT:

My ZoneId.systemDefault() gives: "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

解决方案

The different results stem from a mismatch in switching to summer time, which starts at dates in 2038. You can visualize the difference using the following code:

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

which will print:

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

As we can see, both implementations agree on the instant at which to switch to summer time in 2037, whereas the java.util.* implementation switches one hour later in 2038.

This behavioral change stems from a table of hardcoded transition times in sun.util.calendar.ZoneInfo which has a finite size. As we can see at this point, the code branches depending on the index return by the method getTransitionIndex. If the index is equal or higher than the table length, it falls over to using a SimpleTimeZone implementation.

We can verify that this happens:

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

prints on my system:

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

I don’t know of any plans to change the summer time switching in 2038, so I suppose the java.time implementation to be correct. It’s also obvious that any implementation based on a finite table of hardcoded values has a natural limitation…

这篇关于jdk8日期转换中的错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆