package com.baiyue.lastleg.appointmentplanning; import java.time.DateTimeException; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.util.List; /** Resolves a location's appointment window without using the operator's system time zone. */ public final class AppointmentTimePolicy { private AppointmentTimePolicy() { } public record Window(Instant start, Instant end, String timezone) { } public static Window resolve(LocalDateTime start, LocalDateTime end, String timezone, ZoneOffset startOffset, ZoneOffset endOffset) { if (start == null) throw new IllegalArgumentException("请填写预约开始时间"); if (timezone == null || timezone.isBlank()) throw new IllegalArgumentException("请先补齐预约地点时区"); final ZoneId zone; try { zone = ZoneId.of(timezone); } catch (DateTimeException error) { throw new IllegalArgumentException("预约地点时区无效", error); } if (zone instanceof ZoneOffset) throw new IllegalArgumentException("预约地点需要地区时区,不能仅填写固定时差"); Instant first = instant(start, zone, startOffset); Instant last = end == null ? null : instant(end, zone, endOffset); if (end == null && endOffset != null) throw new IllegalArgumentException("未填写结束时间,不能指定结束时差"); if (last != null && !last.isAfter(first)) throw new IllegalArgumentException("预约结束时间必须晚于开始时间"); return new Window(first, last, zone.getId()); } private static Instant instant(LocalDateTime value, ZoneId zone, ZoneOffset selected) { List offsets = zone.getRules().getValidOffsets(value); if (offsets.isEmpty()) throw new IllegalArgumentException("该当地时间因夏令时切换不存在,请重新选择"); if (selected == null && offsets.size() > 1) { throw new IllegalArgumentException("该当地时间在夏令时切换期间出现两次,请明确时差"); } if (selected != null && !offsets.contains(selected)) { throw new IllegalArgumentException("所选时差与预约地点及日期不符"); } return value.toInstant(selected == null ? offsets.get(0) : selected); } }