【问题标题】:Java 8 date-time: get start of day from ZonedDateTimeJava 8 日期时间:从 ZonedDateTime 获取一天的开始
【发布时间】:2015-05-22 12:39:09
【问题描述】:

这些有什么区别吗:

zonedDateTime.truncatedTo(ChronoUnit.DAYS);

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

有什么理由偏爱一个而不是另一个?

谢谢

【问题讨论】:

  • 他们不能把atStartOfDay() 方法放在ZonedDataTime 类中吗?

标签: java datetime java-8 java-time


【解决方案1】:

为了更正而更新:

在大多数情况下是一样的,请参阅以下巴西从冬季时间切换到夏季时间的示例:

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,那么您选择午夜。根据javadoctruncate()-方法最终转换回新的ZonedDateTime,并将时间向前移动间隔的大小(1 小时)。

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

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

对于 Overlaps,一般的策略是如果本地日期时间 落在重叠的中间,那么前一个偏移量将是 保留。如果没有前一个偏移量,或者前一个偏移量是 无效,则使用较早的偏移量,通常是“夏季”时间。

如果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();

【讨论】:

  • 这并不总是正确的!请参阅我的答案,了解为什么不这样做。
  • @Austin 您的回答似乎是合理的。我会调查并相应地更正我的答案。
  • 第二种方法,你的意思是? zdt.toLocalDate().atStartOfDay().atZone(zdt.getZone()).withEarlierOffsetAtOverlap();
  • @Dr.jacky 是的,你已经明白第二种方法的意思了。
  • @MenoHochschild 好吧,我已经关注了这个:pastebin.com/vcrLr2EJ。您认为这可能有问题吗?
【解决方案2】:

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

例如,古巴在凌晨 1 点恢复夏令时,然后回落到凌晨 12 点。如果您从该转换后的某个时间开始,atStartOfDay() 将返回第一次出现的 12am,而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());
ZonedDateTime zdt3 = zdt.with(LocalTime.MIN);

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]
System.out.println(zdt3); // 2016-11-06T00:00-05:00[America/Havana]

【讨论】:

  • 非常感谢您找到反例。已为您投票并更新了我的答案。
【解决方案3】:

注意还有另一种方法:

zonedDateTime.with(LocalTime.MIN);

产生与 truncatedTo 相同的结果

【讨论】:

  • 在夏令时 (DST) 从午夜开始的情况下,即一天从 01:00 开始的情况下表现如何?在任何情况下,代码都没有表达这种情况下所需的行为。
  • 它将按应有的方式运行 - 即将在该时区(这是适当的 DST 时区)产生午夜 00:00 例如 2018-01-27T00:00+11:00[澳大利亚/悉尼] vs 2018-06-27T00:00+10:00[Australia/Sydney] 你会从所有 3 种方法中得到相同的结果 - 原始问题中的 2 和这个较短的版本
  • 实际上,澄清一下,这个产生的结果与角落案例的 ChronoUnit.DAYS 版本相同
【解决方案4】:

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 小时时)。

【讨论】:

    猜你喜欢
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多