提问人:Der Till 提问时间:11/6/2023 最后编辑:Der Till 更新时间:11/8/2023 访问量:184
将 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
问:
我想解析到我在搜索问题时找到了这段代码:java.util.Date
java.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
- 为什么实际结果与预期不同?
- 如果我还想收到 值 ,我该怎么办?
LocalDate
01.01.0001
时区信息:
ZoneId.systemDefault()
==> (java.time.ZoneRegion) 欧洲/柏林
- 我认为,问题出在这一步:
date.toInstant()
- 我不认为,这(只是)一个时区问题。如果我用不同的年份测试它。例如 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
了解 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#toString
java.util.Date
toString
Calendar
SimpleDateFormat
现在我们可以看看你的代码的其余部分。
在区域之间进行调整
您已将时区应用于 要从 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,所以我怀疑您犯了一个错误。
评论
29.12.0000 和 01.01.0001 不一样。
事实上。我们需要了解的是:每当您将时间或日期转换为另一个时区时,如果两个时区不同,那么生成的日期最终也可能不同。
您有两个日期转换:
atZone()
toLocalDate()
atZone 返回 . 将您的日期转换为另一个时区的日期:https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html。ZonedDateTime
toLocalDate
因此,如果您以这种方式进行两次转换,那么两次转换总共很可能至少减去 24 小时,从而导致两个日期差异。
我很困惑地看到你实际上有 3 天的差异。您将需要分解代码并查看数据更改实际发生的位置。它们可能是我描述的时区差异。或者它们可能是代码或旧库中的一些错误。也许你有更多的转化。
一种非常“简单”的方法,以这种方式回到过去,将实际日期转换为时区并忘记小时。然后,如果时区更改的时区偏移量递减,则重复减去天数。
多个问题
- 1 天差:其他答案中已经解释过的时区,即例如,时区的午夜对应于前一天的 23:00(无夏令时)
Europe/Berlin
UTC
- 2 天的差异:旧类 (, , ) 使用的是 1582 年 10 月的儒略历,而新类使用的是 Proleptic 公历
Date
SimpleDateFormat
Calendar
java.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#toInstant
Instant#atOffset
Instant#atZone
OffsetDateTime#toLocalDate
ZonedDateTime#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));
评论
ZoneId.systemDefault()
java.util.Date
01.01.0001
Date
Calendar
LocalDate.of(year, month, day)
Date
Instant
Date
Instant