查找下一次出现的时间,例如 TemporalAdjuster
Find next occurrence of a time, like TemporalAdjuster
JSR-310 中是否有用于查找给定时间的下一次出现的内容?我真的在寻找与 this question 相同的东西,但时间而不是几天。
例如,从 06:30 UTC 的日期 2020-09-17 开始,我想找到下一次出现的 05:30:
LocalTime time = LocalTime.of(5, 30);
ZonedDateTime startDateTime = ZonedDateTime.of(2020, 9, 17, 6, 30, 0, 0, ZoneId.of("UTC"));
ZonedDateTime nextTime = startDateTime.with(TemporalAdjusters.next(time)); // This doesn't exist
在上面,我希望 nextTime
在 05:30 UTC 代表 2020-09-18,即第二天早上 05:30。
为了澄清我的期望,所有的时间都是05:30:
----------------------------------------
| startDateTime | expected nextTime |
| 2020-09-07 06:30 | 2020-09-08 05:30 |
| 2020-09-07 05:00 | 2020-09-07 05:30 |
| 2020-09-07 05:30 | 2020-09-08 05:30 |
----------------------------------------
如果您只是想让它与 LocalDateTime
s 和 LocalTime
s 或任何其他类型的 Temporal
一起使用,它有 24 小时制,逻辑非常简单:
public static TemporalAdjuster nextTime(LocalTime time) {
return temporal -> {
LocalTime lt = LocalTime.from(temporal);
if (lt.isBefore(time)) {
return temporal.with(time);
} else {
return temporal.plus(Duration.ofHours(24)).with(time);
}
};
}
但是对所有具有时间分量的 Temporal
执行此操作实际上非常困难。想想你必须为 ZonedDateTime
做些什么。我们可能需要增加 23 或 25 小时,而不是增加 24 小时,因为 DST 转换会使“一天”变短或变长。您可以通过添加“日”来稍微解决这个问题:
public static TemporalAdjuster nextTime(LocalTime time) {
return temporal -> {
LocalTime lt = LocalTime.from(temporal);
if (lt.isBefore(time) || !temporal.isSupported(ChronoUnit.DAYS)) {
return temporal.with(time);
} else {
return temporal.plus(1, ChronoUnit.DAYS).with(time);
}
};
}
然而,它仍然总是正确处理间隙和重叠。例如,当我们要求从 01:31 开始的下一个 01:30 时,在 02:00 处有一个 1 小时的重叠过渡,即时钟在 02:00 处倒退一个小时].正确答案是加上 59 分钟,但是上面的代码会给我们一个第二天的日期时间。要处理这种情况,您需要像 Andreas 的回答那样做一些复杂的事情。
如果你看看其他内置的时间调节器,它们都非常简单,所以我猜他们只是不想加入这种复杂性。
像 next(LocalTime time)
这样的调整器只对同时具有日期和时间的类型有意义。
Java的时间API有4种类型:LocalDateTime
、OffsetDateTime
、ZonedDateTime
和Instant
。
为了完全支持所有 4 个,代码需要对 Instant
进行特殊处理,因为 Instant
和 LocalTime
不直接相关,而对于 ZonedDateTime
,要处理夏令时重叠。
此实现可以处理所有这些:
public static TemporalAdjuster next(LocalTime time) {
return temporal -> {
if (temporal instanceof Instant) {
OffsetDateTime utcDateTime = ((Instant) temporal).atOffset(ZoneOffset.UTC);
return next(utcDateTime, time).toInstant();
}
return next(temporal, time);
};
}
@SuppressWarnings("unchecked")
private static <T extends Temporal> T next(T refDateTime, LocalTime targetTime) {
T adjusted = (T) refDateTime.with(targetTime);
if (refDateTime.until(adjusted, ChronoUnit.NANOS) > 0)
return adjusted;
if (adjusted instanceof ChronoZonedDateTime<?>) {
ChronoZonedDateTime<?> laterOffset = ((ChronoZonedDateTime<?>) adjusted).withLaterOffsetAtOverlap();
if (laterOffset != adjusted && refDateTime.until(laterOffset, ChronoUnit.NANOS) > 0)
return (T) laterOffset;
}
return (T) refDateTime.plus(1, ChronoUnit.DAYS).with(targetTime);
}
测试(now()
为 2020-09-18 上午 10 点后的某个时间)
System.out.println(LocalDateTime.now().with(next(LocalTime.of(10, 0))));
System.out.println(OffsetDateTime.now().with(next(LocalTime.of(10, 0))));
System.out.println(ZonedDateTime.now().with(next(LocalTime.of(10, 0))));
System.out.println(Instant.now().with(next(LocalTime.of(10, 0))));
输出
2020-09-19T10:00
2020-09-19T10:00-04:00
2020-09-19T10:00-04:00[America/New_York]
2020-09-19T10:00:00Z
测试重叠
对于美国东部时区,夏令时结束于 2020 年 11 月 1 日星期日上午 2:00。
// We start at 1:45 AM EDT on November 1, 2020
ZoneId usEastern = ZoneId.of("America/New_York");
ZonedDateTime earlierOffset = ZonedDateTime.of(2020, 11, 1, 1, 45, 0, 0, usEastern);
System.out.println(earlierOffset);
// Now we look for next 1:20 AM after the 1:45 AM, and will find 1:20 AM EST
System.out.println(earlierOffset.with(next(LocalTime.of(1, 20))));
输出
2020-11-01T01:45-04:00[America/New_York]
2020-11-01T01:20-05:00[America/New_York]
即使一天中的时间出现得更早 (1:20 < 1:45),但实际上是更晚的时间。
JSR-310 中是否有用于查找给定时间的下一次出现的内容?我真的在寻找与 this question 相同的东西,但时间而不是几天。
例如,从 06:30 UTC 的日期 2020-09-17 开始,我想找到下一次出现的 05:30:
LocalTime time = LocalTime.of(5, 30);
ZonedDateTime startDateTime = ZonedDateTime.of(2020, 9, 17, 6, 30, 0, 0, ZoneId.of("UTC"));
ZonedDateTime nextTime = startDateTime.with(TemporalAdjusters.next(time)); // This doesn't exist
在上面,我希望 nextTime
在 05:30 UTC 代表 2020-09-18,即第二天早上 05:30。
为了澄清我的期望,所有的时间都是05:30:
----------------------------------------
| startDateTime | expected nextTime |
| 2020-09-07 06:30 | 2020-09-08 05:30 |
| 2020-09-07 05:00 | 2020-09-07 05:30 |
| 2020-09-07 05:30 | 2020-09-08 05:30 |
----------------------------------------
如果您只是想让它与 LocalDateTime
s 和 LocalTime
s 或任何其他类型的 Temporal
一起使用,它有 24 小时制,逻辑非常简单:
public static TemporalAdjuster nextTime(LocalTime time) {
return temporal -> {
LocalTime lt = LocalTime.from(temporal);
if (lt.isBefore(time)) {
return temporal.with(time);
} else {
return temporal.plus(Duration.ofHours(24)).with(time);
}
};
}
但是对所有具有时间分量的 Temporal
执行此操作实际上非常困难。想想你必须为 ZonedDateTime
做些什么。我们可能需要增加 23 或 25 小时,而不是增加 24 小时,因为 DST 转换会使“一天”变短或变长。您可以通过添加“日”来稍微解决这个问题:
public static TemporalAdjuster nextTime(LocalTime time) {
return temporal -> {
LocalTime lt = LocalTime.from(temporal);
if (lt.isBefore(time) || !temporal.isSupported(ChronoUnit.DAYS)) {
return temporal.with(time);
} else {
return temporal.plus(1, ChronoUnit.DAYS).with(time);
}
};
}
然而,它仍然总是正确处理间隙和重叠。例如,当我们要求从 01:31 开始的下一个 01:30 时,在 02:00 处有一个 1 小时的重叠过渡,即时钟在 02:00 处倒退一个小时].正确答案是加上 59 分钟,但是上面的代码会给我们一个第二天的日期时间。要处理这种情况,您需要像 Andreas 的回答那样做一些复杂的事情。
如果你看看其他内置的时间调节器,它们都非常简单,所以我猜他们只是不想加入这种复杂性。
像 next(LocalTime time)
这样的调整器只对同时具有日期和时间的类型有意义。
Java的时间API有4种类型:LocalDateTime
、OffsetDateTime
、ZonedDateTime
和Instant
。
为了完全支持所有 4 个,代码需要对 Instant
进行特殊处理,因为 Instant
和 LocalTime
不直接相关,而对于 ZonedDateTime
,要处理夏令时重叠。
此实现可以处理所有这些:
public static TemporalAdjuster next(LocalTime time) {
return temporal -> {
if (temporal instanceof Instant) {
OffsetDateTime utcDateTime = ((Instant) temporal).atOffset(ZoneOffset.UTC);
return next(utcDateTime, time).toInstant();
}
return next(temporal, time);
};
}
@SuppressWarnings("unchecked")
private static <T extends Temporal> T next(T refDateTime, LocalTime targetTime) {
T adjusted = (T) refDateTime.with(targetTime);
if (refDateTime.until(adjusted, ChronoUnit.NANOS) > 0)
return adjusted;
if (adjusted instanceof ChronoZonedDateTime<?>) {
ChronoZonedDateTime<?> laterOffset = ((ChronoZonedDateTime<?>) adjusted).withLaterOffsetAtOverlap();
if (laterOffset != adjusted && refDateTime.until(laterOffset, ChronoUnit.NANOS) > 0)
return (T) laterOffset;
}
return (T) refDateTime.plus(1, ChronoUnit.DAYS).with(targetTime);
}
测试(now()
为 2020-09-18 上午 10 点后的某个时间)
System.out.println(LocalDateTime.now().with(next(LocalTime.of(10, 0))));
System.out.println(OffsetDateTime.now().with(next(LocalTime.of(10, 0))));
System.out.println(ZonedDateTime.now().with(next(LocalTime.of(10, 0))));
System.out.println(Instant.now().with(next(LocalTime.of(10, 0))));
输出
2020-09-19T10:00
2020-09-19T10:00-04:00
2020-09-19T10:00-04:00[America/New_York]
2020-09-19T10:00:00Z
测试重叠
对于美国东部时区,夏令时结束于 2020 年 11 月 1 日星期日上午 2:00。
// We start at 1:45 AM EDT on November 1, 2020
ZoneId usEastern = ZoneId.of("America/New_York");
ZonedDateTime earlierOffset = ZonedDateTime.of(2020, 11, 1, 1, 45, 0, 0, usEastern);
System.out.println(earlierOffset);
// Now we look for next 1:20 AM after the 1:45 AM, and will find 1:20 AM EST
System.out.println(earlierOffset.with(next(LocalTime.of(1, 20))));
输出
2020-11-01T01:45-04:00[America/New_York]
2020-11-01T01:20-05:00[America/New_York]
即使一天中的时间出现得更早 (1:20 < 1:45),但实际上是更晚的时间。