向java.util.Calendar添加一年时的奇怪结果

时间:2018-04-19 20:33:53

标签: java date kotlin

使用java.util.Calendar初始化May, 31 1900。然后再加上一年二十次。

这是代码:

import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*

fun main(args : Array<String>) {
    val f = SimpleDateFormat("yyyy.dd.MM")
    val cal = Calendar.getInstance()
    cal.set(1900, Calendar.MAY, 31)
    for(i in 1..20) {
        println(f.format(cal.time))
        cal.add(Calendar.YEAR, 1)
    }
}

输出如下:

1900.31.05
1901.31.05
1902.31.05
1903.31.05
1904.31.05
1905.31.05
1906.31.05
1907.31.05
1908.31.05
1909.31.05
1910.31.05
1911.31.05
1912.31.05
1913.31.05
1914.31.05
1915.31.05
1916.31.05
1917.31.05
1918.01.06
1919.01.06

为什么我从1918年开始参加6月1日而不是5月31日?

UPD:有时间信息

1917.31.05 23:38:50.611
1918.01.06 01:38:50.611

如果这是DST的发明,我该如何预防?

2 个答案:

答案 0 :(得分:8)

您似乎在一个时区中运行您的代码,该时区在1917年或1918年将其偏移量改变了两个小时。也就是说,UTC前后的小时数发生了变化。我不知道为什么你的时区会这样做,但我确信这是一个很好的历史原因。

如果您只对日期感兴趣而没有时间组件,请使用java.time.LocalDate类,该类有效地代表日,月和年。它不受任何夏令时异常的影响。

LocalDate today = LocalDate.now(); 

LocalDate moonLanding = LocalDate.of(1969, 7, 20);

答案 1 :(得分:3)

我假设你在欧洲/莫斯科时区。 a comment中的图灵85正确地发现了你所观察到的行为的原因:1918年你所在时区的夏令时(DST)从5月31日开始。时钟从22:00向前移动到24:00,即两个小时您的Calendar对象知道这一点,因此拒绝在此日期给出23:38:50.611。相反,它选择2小时后的时间,1918.01.06 01:38:50.611。现在月份和日期已经变为6月1日。

不幸的是,此更改保留在Calendar中,并持续到下一年。

  

如果这是DST的发明,我该如何预防?

3 {}}中的ThomasKläger给出了正确的解决方案:如果您只需要日期,请使用LocalDate中的java.time

    DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("uuuu.dd.MM");
    LocalDate date = LocalDate.of(1900, Month.MAY, 31);
    for (int i = 1; i <= 20; i++) {
        System.out.println(date.format(dateFormatter));
        date = date.plusYears(1);
    }

输出(缩写):

1900.31.05
1901.31.05
…
1917.31.05
1918.31.05
1919.31.05

LocalDate中的“本地”在java.time行话中表示“没有时区”,因此可以保证您免受时区异常的影响。

如果您需要时间,可以考虑LocalDateTime,但由于这也没有时区,给您不存在的时间1918.31.05 23:38 :50.611,也许不是。

您可以考虑的另一件事是在1900.31.05 23:38:50.611中添加正确的年数。那么至少你会在几年中遇到意外的时候你会遇到不存在的时间。我正在使用ZonedDateTime进行此演示:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu.dd.MM HH:mm:ss.SSS");
    ZonedDateTime originalDateTime = ZonedDateTime.of(1900, Month.MAY.getValue(), 31,
            23, 30, 50, 611000000, ZoneId.of("Europe/Moscow"));
    for (int i = 0; i < 25; i++) {
        System.out.println(originalDateTime.plusYears(i).format(formatter));
    }

输出:

1900.31.05 23:30:50.611
1901.31.05 23:30:50.611
…
1917.31.05 23:30:50.611
1918.01.06 01:30:50.611
1919.01.06 00:30:50.611
1920.31.05 23:30:50.611
…
1924.31.05 23:30:50.611

1919年的夏季时间再次于5月31日开始。这次时间仅提前1小时,从23点提高到24点,所以你比假想时间23:30:50.611只提前1小时。

我建议java.time进行日期和时间工作,尤其是在像你这样的日期做数学时。 Calendar类被认为已经过时了。 java.time的设计承认Calendar和其他旧类的设计很差。现代的工作非常好用。

我怎么能确定它是莫斯科?

在没有其他时区比欧洲/莫斯科的时间1918.31.05 23:38:50.611不存在。我查了一下:

    LocalDateTime dateTime = LocalDateTime.of(1918, Month.MAY, 31, 23, 38, 50, 611000000);
    for (String zid : ZoneId.getAvailableZoneIds()) {
        ZonedDateTime zdt = dateTime.atZone(ZoneId.of(zid));
        LocalDateTime newDateTime = zdt.toLocalDateTime();
        if (! newDateTime.equals(dateTime)) {
            System.out.println(zid + ": -> " + zdt + " -> " + newDateTime);
        }
    }

输出:

Europe/Moscow: -> 1918-06-01T01:38:50.611+04:31:19[Europe/Moscow] -> 1918-06-01T01:38:50.611
W-SU: -> 1918-06-01T01:38:50.611+04:31:19[W-SU] -> 1918-06-01T01:38:50.611

“W-SU”是同一时区的弃用名称,代表西方苏联。

链接