You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 21实现英国电力市场结算时段计算(适配夏令时转换)

英国电力市场结算时段的Java 21实现

需求说明

需实现一个方法,计算从英国当地当日午夜到指定时刻已完成的半小时间隔数(即结算时段编号),规则如下:

  • 正常日:48个时段,编号1~48
  • 短日(夏令时开始,时钟调快1小时):46个时段,编号1~46(跳过1小时对应的两个半小时间隔)
  • 长日(夏令时结束,时钟调慢1小时):50个时段,编号1~50(重复1小时对应的两个半小时间隔)

方法签名:

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;
}

第二次尝试通过判断时区转换优化,但存在代码冗余、转换时间点判断错误的问题。

正确实现方案

基于java.time API处理夏令时的间隙(gap)和重叠(overlap)场景,调整本地时间的总分钟数后计算时段编号:

import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.Objects;

public class SettlementPeriodCalculator {

    private static final ZoneId LONDON_ZONE = ZoneId.of("Europe/London");

    public static int settlementPeriod(final OffsetDateTime readingTime) {
        Objects.requireNonNull(readingTime, "readingTime must not be null");

        // 转换为伦敦时区的时间
        ZonedDateTime zdt = readingTime.atZoneSameInstant(LONDON_ZONE);
        LocalDate localDate = zdt.toLocalDate();
        LocalTime localTime = zdt.toLocalTime();
        ZoneOffset offset = zdt.getOffset();
        ZoneRules zoneRules = LONDON_ZONE.getRules();

        // 计算本地时间从午夜开始的总分钟数
        int totalMinutes = (int) localTime.toSecondOfDay() / 60;

        // 处理短日:夏令时开始,本地时间跳过01:00-02:00(间隙)
        ZoneOffsetTransition forwardTransition = zoneRules.getTransition(localDate.atStartOfDay(LONDON_ZONE).toInstant());
        if (forwardTransition != null && forwardTransition.isGap()) {
            LocalTime gapStart = forwardTransition.getDateTimeBefore().toLocalTime();
            LocalTime gapEnd = forwardTransition.getDateTimeAfter().toLocalTime();
            // 若当前时间在间隙之后,减去间隙时长(60分钟)
            if (localTime.compareTo(gapEnd) >= 0) {
                totalMinutes -= (int) ChronoUnit.MINUTES.between(gapStart, gapEnd);
            }
        }

        // 处理长日:夏令时结束,本地时间重复01:00-02:00(重叠)
        ZoneOffsetTransition backwardTransition = zoneRules.getTransition(localDate.plusDays(1).atStartOfDay(LONDON_ZONE).toInstant());
        if (backwardTransition != null && backwardTransition.isOverlap()) {
            LocalTime overlapStart = backwardTransition.getDateTimeBefore().toLocalTime();
            LocalTime overlapEnd = backwardTransition.getDateTimeAfter().toLocalTime();
            // 若当前时间在重叠区间内,且是第二个实例(偏移更小的GMT),加上重叠时长(60分钟)
            if (localTime.compareTo(overlapStart) >= 0 && localTime.compareTo(overlapEnd) < 0) {
                if (offset.equals(backwardTransition.getOffsetAfter())) {
                    totalMinutes += (int) ChronoUnit.MINUTES.between(overlapStart, overlapEnd);
                }
            }
        }

        // 计算已完成的半小时间隔数,加1得到时段编号
        return (totalMinutes / 30) + 1;
    }
}

逻辑说明

  1. 时区转换:将输入的OffsetDateTime转换为伦敦时区的ZonedDateTime,获取本地日期、时间及时区偏移。
  2. 基础分钟数计算:统计本地时间从午夜开始的总分钟数。
  3. 短日处理:判断当日是否存在夏令时开始的间隙(本地时间跳过1小时),若当前时间在间隙之后,减去间隙时长修正总分钟数。
  4. 长日处理:判断次日起始时刻是否存在夏令时结束的重叠(本地时间重复1小时),若当前时间是重叠区间的第二个实例(GMT偏移),加上重叠时长修正总分钟数。
  5. 时段计算:用修正后的总分钟数除以30,加1得到最终的结算时段编号。

测试场景验证

  • 正常日:2024-04-01T00:30+01:00 → 总分钟数30 → 时段编号2,符合预期。
  • 短日:2024-03-31T03:00+01:00 → 修正后总分钟数120 → 时段编号5,符合短日跳过两个时段的规则。
  • 长日(重复时间第二次):2024-10-27T01:30Z(GMT)→ 修正后总分钟数150 → 时段编号6,符合长日新增两个时段的规则。

内容的提问来源于stack exchange,提问作者Boris the Spider

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 13:19:50