Java 处理日期和时间的不一致

Art*_*sky 5 java time datetime calendar date

我注意到 java 中日期和时间的奇怪行为。我有以下代码:

public class TestDateTime {
    public static void main(String[] args) {
        TimeZone.setDefault(TimeZone.getTimeZone("Europe/Helsinki"));

        Calendar calendar = GregorianCalendar.getInstance();

        assert(calendar.getTimeZone().equals(TimeZone.getDefault()));
        //Set 1899-12-30T23:00:00
        calendar.set(1899,11,30,23,0,0);
        calendar.set(Calendar.MILLISECOND,0);

        long timeInMillis = calendar.getTimeInMillis();
        java.util.Date calendarDateTime = new java.util.Date(timeInMillis);

        LocalDateTime localDateTime = LocalDateTime.ofInstant(ofEpochMilli(timeInMillis), ZoneId.systemDefault());
        System.out.println("Time in millis: " + timeInMillis);
        System.out.println("Date: " + calendarDateTime.toString());
        System.out.println("Local DateTime: " + localDateTime.toString());
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

Time in millis: -2209086000000
Date: Sat Dec 30 23:00:00 EET 1899
Local DateTime: 1899-12-30T22:39:49
Run Code Online (Sandbox Code Playgroud)

timeInMillis 必须包含从 传递的毫秒数1970-01-01T00:00:00Z。Date 类的实例存储从 传递的毫秒数1970-01-01T00:00:00Z。 Date.toString()方法返回默认时区的本地日期和时间。

因此Date.toString()和LocalDateTime.toString()必须返回相同的日期和时间,但我们看到了差异(超过 20 分钟)。

这是java的错误,还是我在Java中错误地使用了日期和时间?

Jim*_*son 5

LocalDateTime是正确的。根据 TZ 数据库,该日期的 GMT 偏移量为1:39:49:

# Zone  NAME        GMTOFF  RULES   FORMAT  [UNTIL]
Zone    Europe/Helsinki 1:39:49 -   LMT 1878 May 31
            1:39:49 -   HMT 1921 May    # Helsinki Mean Time
            2:00    Finland EE%sT   1983
            2:00    EU  EE%sT
Run Code Online (Sandbox Code Playgroud)

历史时区非常复杂,在标准化之前,偏移量是从基于平均太阳正午等设置继承的。回溯到那么远,几乎任何偏移都是可能的,而IANA TZ 数据库是历史数据的主要参考。

从我在数据库中看到的情况来看,2:00:00直到 1921 年 HMT 被 EE(S)T 取代时,这种奇怪的偏移量才得到标准化。

  • 不,“Date”仅用于保存 UTC 时间,并且长期以来一直被认为是一个功能不足、过时的界面,不能以合理的方式处理时间。这就是像 JodaTime 这样的库存在的原因,以及添加“java.time”包的原因。 (2认同)

And*_*eas 5

这是芬兰时间变化造成的奇怪现象,参见1921 年芬兰赫尔辛基 (Helsingfors) 的时钟变化:

\n
\n

1921 年 5 月 1 日 - 时区更改 (HMT \xe2\x86\x92 EET)

\n

当当地标准时间即将到达时\n1921 年 5 月 1 日星期日 12:00:00 午夜时钟向前拨动0:20:11 小时到\n1921 年 5 月 1 日星期日中午 12:20:11 当地标准时间反而

\n
\n

这 20 分 11 秒似乎就是你所观察到的。

\n

正如吉姆·加里森(Jim Garrison)在他的回答中所说,LocalDateTime正确地处理了这一点,而事实Calendar并非如此。

\n

实际上,似乎旧的TimeZone偏移量是错误的,而新的ZoneId偏移量是正确的,如以下测试代码所示:

\n
public static void main(String[] args) {\n    compare(1800, 1, 1,  0, 0, 0);\n    compare(1899,12,31, 23,59,59);\n    compare(1900, 1, 1,  0, 0, 0);\n    compare(1900,12,30, 23, 0, 0);\n    compare(1921, 4,30,  0, 0, 0);\n    compare(1921, 5, 1,  0, 0, 0);\n    compare(1921, 5, 2,  0, 0, 0);\n}\nprivate static void compare(int year, int month, int day, int hour, int minute, int second) {\n    Calendar calendar = new GregorianCalendar();\n    calendar.clear();\n    calendar.setTimeZone(TimeZone.getTimeZone("Europe/Helsinki"));\n    calendar.set(year, month-1, day, hour, minute, second);\n    Date date = calendar.getTime();\n    \n    ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.of("Europe/Helsinki"));\n    \n    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z XXX");\n    sdf.setTimeZone(TimeZone.getTimeZone("Europe/Helsinki"));\n    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss z XXX");\n    \n    System.out.printf("%04d-%02d-%02d %02d:%02d:%02d   %s = %d   %s = %d   %d%n",\n                      year, month, day, hour, minute, second,\n                      sdf.format(date), date.getTime(),\n                      dtf.format(zdt), zdt.toInstant().toEpochMilli(),\n                      date.getTime() - zdt.toInstant().toEpochMilli());\n}\n
Run Code Online (Sandbox Code Playgroud)\n

输出

\n
public static void main(String[] args) {\n    compare(1800, 1, 1,  0, 0, 0);\n    compare(1899,12,31, 23,59,59);\n    compare(1900, 1, 1,  0, 0, 0);\n    compare(1900,12,30, 23, 0, 0);\n    compare(1921, 4,30,  0, 0, 0);\n    compare(1921, 5, 1,  0, 0, 0);\n    compare(1921, 5, 2,  0, 0, 0);\n}\nprivate static void compare(int year, int month, int day, int hour, int minute, int second) {\n    Calendar calendar = new GregorianCalendar();\n    calendar.clear();\n    calendar.setTimeZone(TimeZone.getTimeZone("Europe/Helsinki"));\n    calendar.set(year, month-1, day, hour, minute, second);\n    Date date = calendar.getTime();\n    \n    ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.of("Europe/Helsinki"));\n    \n    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z XXX");\n    sdf.setTimeZone(TimeZone.getTimeZone("Europe/Helsinki"));\n    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss z XXX");\n    \n    System.out.printf("%04d-%02d-%02d %02d:%02d:%02d   %s = %d   %s = %d   %d%n",\n                      year, month, day, hour, minute, second,\n                      sdf.format(date), date.getTime(),\n                      dtf.format(zdt), zdt.toInstant().toEpochMilli(),\n                      date.getTime() - zdt.toInstant().toEpochMilli());\n}\n
Run Code Online (Sandbox Code Playgroud)\n