我有一个看似简单的任务,我希望用纯 Java 21 来解决它 - 尽管如果必须的话我会使用库。
我需要计算从当天午夜开始完成的半小时间隔的数量 - 这是专门针对英国电力市场的,并且对此计数有特定规则。一定有
1..48
1..46
的 46 个周期(时钟向前推进)1..50
的 50 个周期(时钟倒转)您可以将其视为从一天开始到
now()
(或特定时刻)的半小时“墙上时间”的计数。时区的相关性仅在于时区的变化会导致英国一天的时间多或少一小时。
所以方法签名是这样的
int settlementPeriod(final OffsetDateTime date)
即给定时间线上的任意点,确定英国的正确时期。
我的第一次尝试非常简单,本质上是在英国开始一天的工作,并在英国进行同样的工作;将它们之间的持续时间除以半小时
public static int settlementPeriod(final OffsetDateTime readingTime) {
final var zone = ZoneId.of("Europe/London");
final var startOfDay = readingTime.truncatedTo(ChronoUnit.DAYS).atZoneSameInstant(zone);
final var duration = Duration.between(startOfDay, readingTime.atZoneSameInstant(zone));
return (int) duration.dividedBy(Duration.ofMinutes(30)) + 1;
}
这对于正常的日子是有效的,但对于“短”和“长”的日子来说都严重失败。
在“短”的一天中,一旦发生转变,指数就会跳升一小时。
在“长”的一天,一旦发生转变,指数就会重复一小时。
对,所以当我将
readingTime
转换为本地 zone
时得到的时区是转换后 之后的时区 - 所以我正在比较
其中,
startOfDay
应该位于转换前的时区。
我想出了以下几乎通过我的测试的内容
public static int settlementPeriod(final OffsetDateTime readingTime) {
final var zone = ZoneId.of("Europe/London");
final var transition = zone.getRules().previousTransition(readingTime.toInstant());
final ZonedDateTime startOfDay;
if (Objects.equals(transition.getDateTimeBefore().toLocalDate(), readingTime.toLocalDate())) {
startOfDay = readingTime.toLocalDate().atStartOfDay(transition.getOffsetBefore());
} else {
startOfDay = readingTime.truncatedTo(ChronoUnit.DAYS).atZoneSameInstant(zone);
}
final var duration = Duration.between(startOfDay, readingTime.atZoneSameInstant(zone));
return (int) duration.dividedBy(Duration.ofMinutes(30)) + 1;
}
但是有几个问题
readingTime
在转换时间上是 exaclty,则 previousTransition
返回错误的转换(有意义)使用
java.time
执行此操作的“正确”方法是什么?
您应该先做
atZoneSameInstant(zone)
,然后做truncatedTo(ChronoUnit.DAYS)
。
final var startOfDay = readingTime.atZoneSameInstant(zone).truncatedTo(ChronoUnit.DAYS);
在
truncatedTo
上执行 OffsetDateTime
将使偏移保持不变,但这不是时区转换的一天会发生的情况。一天开始的偏移量可能与 readingTime
的偏移量不同。首先将其更改为 ZonedDateTime
,确保 truncatedTo
考虑到时区转换。
旁注:我觉得这个方法应该采用
Instant
或 ZonedDateTime
作为参数。