Java中如何获取满足自定义时间条件的下一个Date对象
适配Minecraft Spigot的下一个匹配时间计算实现
核心设计前提
- 支持对时、分、秒三个时间字段设置匹配规则,每个字段可设置为固定值、或匹配任意值
- 最终返回的时间必须严格晚于参考时间,不允许返回和参考时间相等的结果
- 全程使用JDK原生API实现,无第三方依赖,不会和Spigot的类加载机制产生冲突,计算结果可直接转换为
Date类型适配Spigot的调度接口
实现思路
不需要逐秒遍历全时间段做匹配,按照「大时间粒度→小时间粒度」的优先级逐层推导判断:从最大的天级偏移开始,逐层排查小时、分钟、秒的可能取值,找到第一个严格晚于当前参考时间的合法时间点就直接返回,整体计算耗时在微秒级,完全满足服务端高性能要求。
代码实现
首先定义匹配规则对象,每个字段用固定标记代表“匹配任意值”:
import java.time.LocalDateTime; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.TimeZone; public class TimeMatchRule { // 标记当前字段匹配任意值 public static final int ANY = -1; private int hour = ANY; private int minute = ANY; private int second = ANY; // 按需添加getter、setter方法即可 public int getHour() { return hour; } public void setHour(int hour) { this.hour = hour; } public int getMinute() { return minute; } public void setMinute(int minute) { this.minute = minute; } public int getSecond() { return second; } public void setSecond(int second) { this.second = second; } }
核心计算方法:
public static Date getNextStrictMatchDate(Date referenceTime, TimeMatchRule rule, TimeZone serverTimeZone) { // 用服务端实际时区转换时间,避免硬编码时区导致的时间偏差 LocalDateTime refTime = LocalDateTime.ofInstant(referenceTime.toInstant(), serverTimeZone.toZoneId()); LocalDateTime candidate = refTime.truncatedTo(ChronoUnit.SECONDS); // 最多向后排查3天,一定能找到合法匹配时间,不会出现死循环 for (int dayOffset = 0; dayOffset < 3; dayOffset++) { // 确定当前排查的小时范围 int startHour = rule.getHour() == TimeMatchRule.ANY ? candidate.getHour() : rule.getHour(); // 如果规则指定的小时早于当前候选的小时,当天不可能匹配,直接切到下一天 if (rule.getHour() != TimeMatchRule.ANY && rule.getHour() < candidate.getHour()) { candidate = candidate.plusDays(1).truncatedTo(ChronoUnit.DAYS); continue; } // 逐小时排查 for (int h = startHour; h < 24; h++) { LocalDateTime hourBase = candidate.withHour(h); // 确定当前排查的分钟范围 int startMinute = rule.getMinute() == TimeMatchRule.ANY ? hourBase.getMinute() : rule.getMinute(); // 如果规则指定的分钟早于当前小时下的现有分钟,当前小时不可能匹配,跳到下一个小时 if (rule.getMinute() != TimeMatchRule.ANY && rule.getMinute() < hourBase.getMinute()) { continue; } // 逐分钟排查 for (int m = startMinute; m < 60; m++) { LocalDateTime minuteBase = hourBase.withMinute(m); // 确定当前排查的秒范围 int startSecond = rule.getSecond() == TimeMatchRule.ANY ? minuteBase.getSecond() : rule.getSecond(); // 如果规则指定的秒早于当前分钟下的现有秒,当前分钟不可能匹配,跳到下一个分钟 if (rule.getSecond() != TimeMatchRule.ANY && rule.getSecond() < minuteBase.getSecond()) { continue; } // 逐秒排查 for (int s = startSecond; s < 60; s++) { LocalDateTime matchCandidate = minuteBase.withSecond(s); // 严格校验必须晚于参考时间,等于当前时间的结果直接跳过 if (matchCandidate.isAfter(refTime)) { return Date.from(matchCandidate.atZone(serverTimeZone.toZoneId()).toInstant()); } } } } // 当天没有找到匹配项,切到下一天从头开始排查 candidate = candidate.plusDays(1).truncatedTo(ChronoUnit.DAYS); } throw new IllegalStateException("未找到符合规则的匹配时间"); }
场景验证
拿提到的测试场景举例:
参考时间为
15:58:00,匹配规则为minute=1、second=30、hour=ANY
计算过程:
- 排查到15点这个小时时,规则指定的分钟1早于当前的58分,直接跳过整个15点的匹配段
- 跳到16点,按照规则从分钟1、秒30开始取时间,得到
16:01:30,校验发现该时间严格晚于参考时间,直接返回,和预期完全一致
Spigot开发注意事项
- 调用方法时直接传入Spigot服务端配置的时区即可,不要硬编码时区,避免服务器跨区部署导致的时间计算偏差
- 方法返回的
Date可以直接传给Bukkit.getScheduler()的定时任务接口,不需要做额外类型转换 - 整体计算没有大对象创建、没有全量时间遍历,性能完全适配服务端高频调用的场景
内容的提问来源于stack exchange,提问作者jnts
相关产品推荐
相关产品推荐

