Java 8日期时间:从ZonedDateTime获取一天的开始

时间:2015-03-19 11:55:12

标签: java datetime java-8 java-time

这些之间有什么区别:

zonedDateTime.truncatedTo(ChronoUnit.DAYS);

zonedDateTime.toLocalDate().atStartOfDay(zonedDateTime.getZone());

有理由偏爱另一个吗?

由于

4 个答案:

答案 0 :(得分:43)

为了纠正而更新:

在大多数情况下是相同的,请参阅以下巴西从冬季到夏季转换时的示例:

ZonedDateTime zdt = 
  ZonedDateTime.of(2015, 10, 18, 0, 30, 0, 0, 
    ZoneId.of("America/Sao_Paulo")); // switch to summer time
ZonedDateTime zdt1 = zdt.truncatedTo(ChronoUnit.DAYS);
ZonedDateTime zdt2 = zdt.toLocalDate().atStartOfDay(zdt.getZone());

System.out.println(zdt); // 2015-10-18T01:30-02:00[America/Sao_Paulo]
System.out.println(zdt1); // 2015-10-18T01:00-02:00[America/Sao_Paulo]
System.out.println(zdt2); // 2015-10-18T01:00-02:00[America/Sao_Paulo]

截断发生在本地时间轴上。如果您选择DAYS,那么您选择午夜。根据{{​​3}} truncate() - 方法最终转换回新的ZonedDateTime并将时间向前移动间隔的大小(1小时)。

首先将zdt转换为LocalDate(切断时间部分),然后在给定时区中查找其ZonedDateTime - 部分对于这种情况实际上是相同的。

然而,对于从夏季时间切换回冬季时间的相反情况,有一个例外(非常感谢@Austin提供了反例)。问题是在重叠期间何时决定使用哪个偏移量。通常设计/指定类ZonedDateTime以使用前一个偏移量,另请参阅javadoc的摘录:

  

对于Overlaps,一般策略是如果是本地日期时间   落在重叠的中间,然后是前一个偏移   保留。如果没有先前的偏移量,或者前一个偏移量是   无效,则使用较早的偏移量,通常为" summer"时间。

如果类ZonedDateTime因此遵循其自己的规范,那么这两个程序仍然是等同的意思:

zdt.truncatedTo(ChronoUnit.DAYS);

应该相当于

zdt.toLocalDate().atStartOfDay().atZone(zdt.getZone()).withEarlierOffsetAtOverlap();

但根据@Austin的例子,我在自己的测试中证实的真实行为是:

zdt.toLocalDate().atStartOfDay().atZone(zdt.getZone()).withLaterOffsetAtOverlap();

看起来像ZonedDateTime课程中隐藏的不一致,温和地说。如果你问我哪种方法更受欢迎,那么我宁愿提倡第二种方法,尽管它更长,需要更多的击键。但它的最大优点是更加透明。偏好第二种方法的另一个原因是:

它确实获得当地时间等于一天开始的第一瞬间。否则,在使用第一种方法时,您必须写:

zdt.truncatedTo(ChronoUnit.DAYS).withEarlierOffsetAtOverlap();

答案 1 :(得分:29)

他们略有不同。根据javadocs,truncatedTo()将尝试在重叠的情况下保留时区,但atStartOfDay()将找到第一次出现的午夜。

例如,古巴在凌晨1点恢复夏令时,回到上午12点。如果您在转换后的某个时间开始,atStartOfDay()将返回第一次出现的上午12点,而truncatedTo()将返回第二次出现。

ZonedDateTime zdt = ZonedDateTime.of(2016, 11, 6, 2, 0, 0, 0, ZoneId.of("America/Havana"));
ZonedDateTime zdt1 = zdt.truncatedTo(ChronoUnit.DAYS);
ZonedDateTime zdt2 = zdt.toLocalDate().atStartOfDay(zdt.getZone());

System.out.println(zdt);  // 2016-11-06T02:00-05:00[America/Havana]
System.out.println(zdt1); // 2016-11-06T00:00-05:00[America/Havana]
System.out.println(zdt2); // 2016-11-06T00:00-04:00[America/Havana]

答案 2 :(得分:6)

请注意,还有另一种方法:

zonedDateTime.with(LocalTime.MIN);

产生与truncatedTo

相同的结果

答案 3 :(得分:0)

zonedDateTime.truncatedTo(ChronoUnit.DAYS) 是更好的选择。

正如 Austin 指出的那样,toLocalDate() 会丢失区域信息。来自 LocalDate 文档:

<块引用>

ISO-8601 日历系统中没有时区的日期,例如 2007-12-03。 LocalDate 是一个不可变的日期时间对象,它表示 一个日期,通常被视为年-月-日。其他日期字段,例如 也可以访问年份、星期和星期。为了 例如,值“2nd October 2007”可以存储在 LocalDate 中。

除了 Austin 给出的示例之外,在开发和部署在不同机器上的常见情况下,这可能会导致问题。

举一个具体的例子,考虑处理纽约一家餐厅从美国东部时间上午 11 点到晚上 11 点收到的订单。如果代码是在纽约时间在机器上开发的,toLocalDate() 将不会显示任何错误。但是,当代码部署在 UTC 时区的服务器上时,它会在晚上 8 点之后关闭(当 EDT 比 UTC 晚 4 小时时)。