将 java.util.Date (01.01.0001) 转换为 java.time.LocalDate 返回 29.12.0000

Converting java.util.Date (01.01.0001) to java.time.LocalDate returns 29.12.0000

提问人:Der Till 提问时间:11/6/2023 最后编辑:Der Till 更新时间:11/8/2023 访问量:184

问:

我想解析到我在搜索问题时找到了这段代码:java.util.Datejava.time.LocalDate

Date date = new SimpleDateFormat("dd.MM.yyyy").parse("01.01.0001");
date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

如果我有开始日期,我遇到了问题:01.01.0001

date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

==> (java.time.LocalDate) 0000-12-29

29.12.0000并且不一样。01.01.0001

  1. 为什么实际结果与预期不同?
  2. 如果我还想收到 值 ,我该怎么办?LocalDate01.01.0001

时区信息:

ZoneId.systemDefault()

==> (java.time.ZoneRegion) 欧洲/柏林

  1. 我认为,问题出在这一步:date.toInstant()
  2. 我不认为,这(只是)一个时区问题。如果我用不同的年份测试它。例如 1901,它以 31.12.1900 结束。
        Date date0001 = new SimpleDateFormat("dd.MM.yyyy").parse("01.01.0001");
        System.out.println(date0001.toInstant());

==> 0000-12-29T23:00:00Z

        Date date1901 = new SimpleDateFormat("dd.MM.yyyy").parse("01.01.1901");
        System.out.println(date1901.toInstant());

==> 1900-12-31T23:00:00Z

日期 java-time

评论

3赞 aled 11/6/2023
你的默认时区是什么?ZoneId.systemDefault()
3赞 Honza Zidek 11/6/2023
你是如何创建 with 的实例的?java.util.Date01.01.0001
2赞 user85421 11/6/2023
最好不要使用这样的旧类,例如(年份以 1900 为基础,月份以 0 为基础,天数为 1);如果没有选择,只能使用它,也许使用 a 来获取要使用的日、月和年(调整值后)||一些测试将显示这一点,并将为相同的毫秒显示不同的(文本)日期(对于过去到目前为止的日期,如本问题中所示)DateCalendarLocalDate.of(year, month, day)DateInstant
2赞 Der Till 11/6/2023
我不想使用日期。但它是遗留代码的一部分。在这一点上,重构遗留代码不是一个选项:(。所以我必须将旧的 Date 转换为 LocalDate。
1赞 user85421 11/7/2023
@deHaar你已经建议了,那为什么要测试它呢???但是在1582年之间和之前会有差异,部分包括1582年(我想它与儒略历,格里高利历和普罗普特公历有关)DateInstant

答:

5赞 Basil Bourque 11/7/2023 #1

java.time

了解 Java 附带了两个不同的日期时间框架。

  • 一个是有严重缺陷的,现在是遗产
  • 另一个是行业领先的,在 JSR 310 中定义,所有类都可以在包中找到。java.time.*

让我们来看看你第一年的第一个时刻。

Instant yearOneStartInUtc = Instant.parse ( "0001-01-01T00:00:00Z" );

yearOneStartInUtc.toString() = 0001-01-01T00:00:00Z

末尾发音为“Zulu”,表示与UTC时间子午线的零小时-分钟-秒偏移量。Z

我们可以通过调用添加到旧类的新方法,在旧框架和现代框架之间自由地来回转换。

java.util.Date judStart = java.util.Date.from ( yearOneStartInUtc );

judStart.toString() = 周日 1 月 2 日 16:00:00 PST 1

避免使用旧版日期时间类

请注意,该方法对我们撒谎。虽然表示 UTC 中显示的时刻(偏移量为零),但其方法在生成文本结果时动态应用 JVM 的当前默认时区。这是避免使用此遗留类以及 、 等的众多原因之一。java.util.Date#toStringjava.util.DatetoStringCalendarSimpleDateFormat

现在我们可以看看你的代码的其余部分。

在区域之间进行调整

您已将时区应用于 要从 UTC 进行调整。你忽略了告诉我们那个时区是什么。因此,让我们任意选择美国/埃德蒙顿的时区。此时区使用的偏移量比 UTC 晚几个小时。Instant

ZoneId zoneEdmonton = ZoneId.of ( "America/Edmonton" );
ZonedDateTime zdtYearOneStart = yearOneStartInUtc.atZone ( zoneEdmonton );

zdtYearOneStart.toString() = 0000-12-31T16:26:08-07:33:52[美国/埃德蒙顿]

请注意,日期与输入不同,0000-12-31 与 0001-01-01。时间也不同,16:26:08-07:33:52 与 00:00:00。这就是我们的解释。

想象一下,就在那一刻,当日期和年份滚动时,爱丽丝站在格林威治皇家天文台的未来地点。她观察到元旦的当前时间是 00:00:00。但对于在加拿大艾伯塔省埃德蒙顿喝酒的鲍勃来说,同样的同时时刻是除夕夜,而不是元当鲍勃抬头看墙上的时钟时,他会看到午夜几个小时的时间。Alice 在“明年”0001,而 Bob 同时在“去年”0000。

👉🏽 对于任何给定时刻,时间和日期在全球范围内因时区而异。

因此,1 月 1 日与 12 月 31 日的日期差异是有道理的。但是,您 12 月 29 日的结果没有意义。您忽略了提供 MCVE,所以我怀疑您犯了一个错误。

评论

0赞 user85421 11/7/2023
时区在此评论中给出,下一条评论解释了问题 - 不幸的是不在问题本身中
3赞 Basil Bourque 11/7/2023
@user85421 在 Stack Overflow 上,读者不需要通过评论来理解问题。作者有责任用所有相关事实编辑他们的问题。
0赞 user85421 11/7/2023
我知道并且确切地为什么我发表评论来通知您;我不是作者,也不会做他们的工作(正如我已经评论的那样:“不幸的是不在问题中”)
0赞 Der Till 11/7/2023
对不起。不编辑我的帖子是我的错。我不习惯写帖子,因为我可以通过网络搜索解决我的大部分问题。我已经编辑了我的原始帖子。谢谢你抽出时间接受采访。
1赞 Lajos Arpad 11/7/2023 #2

29.12.0000 和 01.01.0001 不一样。

事实上。我们需要了解的是:每当您将时间或日期转换为另一个时区时,如果两个时区不同,那么生成的日期最终也可能不同。

您有两个日期转换:

  • atZone()
  • toLocalDate()

atZone 返回 . 将您的日期转换为另一个时区的日期:https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.htmlZonedDateTimetoLocalDate

有超过 24 个时区。

enter image description here

因此,如果您以这种方式进行两次转换,那么两次转换总共很可能至少减去 24 小时,从而导致两个日期差异。

我很困惑地看到你实际上有 3 天的差异。您将需要分解代码并查看数据更改实际发生的位置。它们可能是我描述的时区差异。或者它们可能是代码或旧库中的一些错误。也许你有更多的转化。

一种非常“简单”的方法,以这种方式回到过去,将实际日期转换为时区并忘记小时。然后,如果时区更改的时区偏移量递减,则重复减去天数。

0赞 user85421 11/7/2023 #3

多个问题

  • 1 天差:其他答案中已经解释过的时区,即例如,时区的午夜对应于前一天的 23:00(无夏令时)Europe/BerlinUTC
  • 2 天的差异:旧类 (, , ) 使用的是 1582 年 10 月的儒略历,而类使用的是 Proleptic 公历DateSimpleDateFormatCalendarjava.time

维基百科中,我们可以看到两者之间的区别,以下代码证实了这一点:

TimeZone.setDefault(TimeZone.getTimeZone("UTC")); // one error less
for (var year : List.of(1, 101, 201, 1401, 1501, 1601)) {
    var date = new Date(year-1900, 0, 10);
    var instant = date.toInstant();
    System.out.println(date.getTime() + " = " + date);
    System.out.println(instant.toEpochMilli() + " = " + instant);
    System.out.println();
}

输出:

   -62134992000000 = Mon Jan 10 00:00:00 UTC 1
   -62134992000000 = 0001-01-08T00:00:00Z
   
   -58979232000000 = Sun Jan 10 00:00:00 UTC 101
   -58979232000000 = 0101-01-09T00:00:00Z
   
   -55823472000000 = Sat Jan 10 00:00:00 UTC 201
   -55823472000000 = 0201-01-10T00:00:00Z
   
   -17954352000000 = Mon Jan 10 00:00:00 UTC 1401
   -17954352000000 = 1401-01-19T00:00:00Z
   
   -14798592000000 = Sun Jan 10 00:00:00 UTC 1501
   -14798592000000 = 1501-01-20T00:00:00Z
   
   -11643696000000 = Wed Jan 10 00:00:00 UTC 1601
   -11643696000000 = 1601-01-10T00:00:00Z

Javadoc的

我们可以在文档中找到对差异的解释(强调后加):

  • 公历

    从历史上看,在那些首先采用公历的国家中,1582 年 10 月 4 日(儒略历)紧随其后的是 1582 年 10 月 15 日(公历)。此日历对此进行了正确的建模。在公历转换之前,GregorianCalendar 实现儒略历。公历和儒略历之间的唯一区别是闰年规则。儒略历每四年指定一次闰年,而公历省略了不能被 400 整除的世纪年

  • java.time

    此处定义的类表示主要的日期时间概念,包括时刻、持续时间、日期、时间、时区和期间。它们基于 ISO 日历系统,这是遵循公历规则的事实上的世界日历。


结论

如果处理 1582 年之后的日期,可以使用 、() 和 (),请注意使用正确的时区(偏移量)。无论如何,正如已经警告的那样,强烈建议不要使用这些类!
否则,对于 1582 年之前/之前的日期,您需要首先检查哪个日历(应该)用于表示这些日期。
Date#toInstantInstant#atOffsetInstant#atZoneOffsetDateTime#toLocalDateZonedDateTime#toLocalDate

对于儒略历(1583 年之前),正如我已经评论过的,可以使用以下或类似的代码:

var calendar = Calendar.getInstance(); 
calendar.setTime(date); 
var localDate = LocalDate.of(
    calendar.get(Calendar.YEAR), 
    1 + calendar.get(Calendar.MONTH),   // months are 0-based
    calendar.get(Calendar.DAY_OF_MONTH));