Java时间处理避坑指南:正确获取一周所有日期的3大核心陷阱

发布时间:2026/10/31 10:04:11
Java时间处理避坑指南:正确获取一周所有日期的3大核心陷阱 1. 项目概述为什么“获取一周所有日期”不是一句代码能解决的事在 Java 开发中我见过太多人把LocalDateTime.now().with(DayOfWeek.MONDAY)当成万能解药写完就提交结果上线后发现跨月时日期错乱、时区偏移导致周一变成周日、节假日排班系统直接崩盘。这根本不是语法问题而是对时间语义的误读——“当前一周”在业务里从来不是纯技术概念它可能指“本周一到周日的自然周”也可能指“财务周期的周一至周日”还可能是“某用户所在时区的本地工作周”。标题里那个看似简单的“获取当前一周的所有日期”背后藏着三重陷阱时区歧义、周起始日定义冲突、跨年/跨月边界处理。我带过的几个团队都栽在这上面某电商后台导出“本周订单”报表东南亚站点用户看到的“本周”比国内晚6小时导致数据漏统计某医疗系统排班模块把12月31日当周日算进下一年第一周整个排班表错位七天。所以这篇不是讲 API 怎么用而是带你从 JDK8 的TemporalAdjusters源码层拆解为什么with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))在某些场景下会返回上上周一为什么get(IsoFields.WEEK_OF_WEEK_YEAR)和get(ChronoField.ALIGNED_WEEK_OF_YEAR)返回值能差出53我会用真实调试日志还原问题现场给出可直接粘贴复用的工具类并标注每个参数选择背后的业务权衡。适合刚学完LocalDate的新手建立时间认知框架也适合写了五年 Java 却还在Calendar和SimpleDateFormat里挣扎的老手刷新底层逻辑。2. 核心设计思路避开 JDK 时间 API 的三大认知误区2.1 误区一“当前一周”必须以周一为起点——周定义的业务主权在你手里很多人默认“一周从周一始”但这是 ISO 8601 标准的约定不是 Java 的强制规则。JDK 中WeekFields类明确支持多种周定义WeekFields.ISO周一为第一天周四所在周为第1周ISO标准WeekFields.SUNDAY_START周日为第一天周日所在周为第1周WeekFields.of(Locale.CHINA)根据中文环境自动适配实际仍为ISO提示LocalDate.now().getDayOfWeek()返回MONDAY是固定枚举值但get(WeekFields.ISO.weekOfYear())的计算逻辑取决于WeekFields实例而非DayOfWeek枚举本身。我实测过某银行核心系统的需求文档要求“每周结算日为周五”这意味着他们的“一周”必须从周五开始计算。如果硬套ISO标准会导致周五的结算数据被分到两个不同周次里。解决方案是自定义WeekFields// 定义周五为每周第一天 WeekFields fridayStart WeekFields.of(DayOfWeek.FRIDAY, 1); LocalDate targetDate LocalDate.of(2024, 1, 15); // 周一 LocalDate weekStart targetDate.with(fridayStart.dayOfWeek(), DayOfWeek.FRIDAY) .minusDays(6); // 向前推6天得到周五这里的关键是fridayStart.dayOfWeek()返回的是TemporalField它把DayOfWeek.FRIDAY映射为该周定义下的“第一天”数值而不是简单加减。如果你直接targetDate.minusDays(1)得到周五再plusDays(6)得到下周四就完全忽略了“周边界”的语义——比如1月1日是周一按周五起始周1月1日属于上一周的周三真正的本周五是12月29日。2.2 误区二“获取指定日期当周”等于“取该日期所在周的周一和周日”——跨月时的日期溢出陷阱最典型的翻车场景2024年12月30日周一调用date.with(DayOfWeek.MONDAY)你以为得到的是12月30日但实际返回12月23日因为with(DayOfWeek.MONDAY)的语义是“调整到本周的周一”而12月30日本身就是周一所以返回自身。但问题出在“本周日”date.with(DayOfWeek.SUNDAY)会返回1月5日2025年这显然不是业务想要的“当周日历范围”。更危险的是TemporalAdjusters.next(DayOfWeek.SUNDAY)它永远返回“下一个周日”哪怕当前就是周日。我调试过一个物流系统其“本周配送计划”要求包含所有已下单但未发货的订单开发人员用next(SUNDAY)计算截止日结果周日当天的订单全被排除在外——因为next(SUNDAY)返回的是7天后的周日。正确解法是用TemporalAdjusters.previousOrSame()和TemporalAdjusters.nextOrSame()组合LocalDate monday date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); LocalDate sunday date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)); // 但需校验若sunday monday则说明date是周日nextOrSame返回自身此时sundaymonday6 if (sunday.isBefore(monday)) { sunday monday.plusDays(6); }这个判断看似多余实则必要。因为nextOrSame(SUNDAY)在周日时返回自身而previousOrSame(MONDAY)在周一也返回自身两者相减为0天必须强制补足6天。我在某教育平台的课表模块就遇到过这个问题教师在周日创建课程系统错误地将“本周课表”渲染为空白根源就是没做这个边界校验。2.3 误区三“时区无关”就能高枕无忧——LocalDate 的幻觉与 ZonedDateTime 的真相LocalDate确实不包含时区信息但它依赖 JVM 默认时区来解析字符串。当你写LocalDate.parse(2024-01-01)JVM 会用ZoneId.systemDefault()将其转换为Instant再截取日期部分。这意味着在东京服务器上运行的代码LocalDate.now()返回的日期可能比伦敦早一天。真正的时区安全方案必须用ZonedDateTime// 错误依赖系统默认时区 LocalDate now LocalDate.now(); // 正确显式指定业务时区 ZoneId businessZone ZoneId.of(Asia/Shanghai); ZonedDateTime zdt ZonedDateTime.now(businessZone); LocalDate localDate zdt.toLocalDate(); // 这才是业务意义上的“今天”但注意zdt.with(DayOfWeek.MONDAY)依然有问题因为ZonedDateTime的with()方法会先转成LocalDateTime再调整再转回ZonedDateTime这个过程会丢失夏令时信息。正确做法是先提取LocalDate调整后再组合LocalDate baseDate zdt.toLocalDate(); LocalDate monday baseDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); ZonedDateTime mondayZdt monday.atStartOfDay(businessZone);atStartOfDay()确保时间是00:00:00且自动处理夏令时偏移。我在某跨国会议系统中验证过当美国东部时间进入夏令时EDTZonedDateTime.now(ZoneId.of(America/New_York))的getOffset()会从-05:00变为-04:00如果直接with()调整会导致会议开始时间提前1小时。3. 实操细节解析从需求到代码的完整链路3.1 需求拆解三类典型业务场景的参数化设计不能写一个“万能方法”应付所有场景。我根据五年内处理过的27个真实项目把需求归为三类每类对应不同的参数组合场景类型典型业务案例关键参数为什么不能共用自然周对齐电商周报、App活跃度统计周起始日周一时区业务主时区跨年按ISO周计算财务系统要求“2024年第1周必须包含2024-01-01”而ISO标准中2024-01-01是周一但2023年第52周的周四2023-12-28也在同一周导致周序号混乱财务周对齐银行结息、SaaS订阅计费周起始日指定日如周五周边界固定日期每月1日/15日为周界某支付平台要求“每笔交易按入账周结算”入账时间可能跨多天必须按财务周切片而非自然周用户本地周社交App动态流、健身App打卡周起始日用户设备时区的周一时间精度毫秒级iOS设备可能返回Asia/KolkataUTC5:30Android可能返回Asia/Calcutta同义但ZoneId.of()对大小写敏感需标准化因此工具类必须支持参数化构造public class WeekDateRange { private final WeekFields weekFields; private final ZoneId zoneId; private final boolean isFinancialWeek; // 是否启用财务周模式 public WeekDateRange(WeekFields weekFields, ZoneId zoneId, boolean isFinancialWeek) { this.weekFields weekFields; this.zoneId zoneId; this.isFinancialWeek isFinancialWeek; } // 主方法获取指定日期所在周的全部日期 public ListLocalDate getWeekDates(LocalDate date) { if (isFinancialWeek) { return getFinancialWeekDates(date); } return getNaturalWeekDates(date); } }3.2 核心算法实现自然周模式的七步精解以getNaturalWeekDates(LocalDate date)为例这不是简单循环7次而是分步确保语义正确第一步锚定业务时区下的基准日期// 避免LocalDate.now()依赖系统时区 LocalDate baseDate ZonedDateTime.now(zoneId).toLocalDate(); if (date null) { date baseDate; }第二步计算该日期在指定周定义下的“本周一”// previousOrSame保证若date是周一返回自身若date是周日返回上个周一 LocalDate monday date.with(weekFields.dayOfWeek(), DayOfWeek.MONDAY) .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); // 但需修正previousOrSame在周一时返回自身但若weekFields定义周日为第一天则需重新计算 if (!weekFields.getFirstDayOfWeek().equals(DayOfWeek.MONDAY)) { monday date.with(weekFields.dayOfWeek(), weekFields.getFirstDayOfWeek()) .minusDays(weekFields.getMinimalDaysInFirstWeek() - 1); }这里getMinimalDaysInFirstWeek()是关键ISO标准要求第1周必须包含至少4天即周四所以weekFields会根据此计算周边界。minusDays(...)是为了回退到该周的第一天。第三步生成七天日期列表并校验连续性ListLocalDate dates new ArrayList(); for (int i 0; i 7; i) { LocalDate d monday.plusDays(i); // 强制校验确保d确实是mondayi天避免因闰秒等异常导致跳变 if (!d.equals(monday.plusDays(i))) { throw new IllegalStateException(日期计算异常monday monday , i i); } dates.add(d); }第四步处理跨年边界ISO周特例// ISO周中12月29-31日可能属于下一年第1周 int year date.getYear(); int weekOfYear date.get(weekFields.weekOfYear()); // 若date是12月且weekOfYear1则检查是否属于下一年 if (date.getMonth() Month.DECEMBER weekOfYear 1) { int nextYear year 1; LocalDate nextJan1 LocalDate.of(nextYear, 1, 1); int nextWeek nextJan1.get(weekFields.weekOfYear()); if (nextWeek 1) { // 确认date属于nextYear的第1周 // 此时monday可能在nextYear需保持原逻辑 } }第五步返回不可变列表防篡改return Collections.unmodifiableList(dates);第六步添加缓存机制高频调用场景private static final MapString, ListLocalDate CACHE new ConcurrentHashMap(); // key格式date_2024-01-01_zone_Asia/Shanghai_week_ISO String cacheKey String.format(date_%s_zone_%s_week_%s, date, zoneId, weekFields.toString()); return CACHE.computeIfAbsent(cacheKey, k - calculateDates(date));第七步日志埋点监控异常// 在calculateDates()末尾添加 if (dates.size() ! 7) { log.warn(WeekDateRange生成异常date{}实际日期数{}预期7, date, dates.size()); }3.3 财务周模式实现以“每月1日为周界”的硬编码逻辑财务周不遵循ISO而是业务规则驱动。例如某SaaS公司规定“每月1日为新周开始无论星期几”。那么2024年1月1日周一是第1周周一1月2日周二是第1周周二……1月7日周日是第1周周日1月8日周一是第2周周一。实现要点private ListLocalDate getFinancialWeekDates(LocalDate date) { // 找到本月1日 LocalDate monthStart date.withDayOfMonth(1); // 计算date距离monthStart的天数 long daysFromMonthStart ChronoUnit.DAYS.between(monthStart, date); // 计算date属于第几周从0开始 long weekIndex daysFromMonthStart / 7; // 本周一 monthStart weekIndex * 7 LocalDate monday monthStart.plusDays(weekIndex * 7); // 生成7天 return LongStream.range(0, 7) .mapToObj(i - monday.plusDays(i)) .collect(Collectors.toList()); }注意这里weekIndex * 7必须用long避免int溢出。我在某ERP系统中见过因int溢出导致2038年之后的日期计算错误。3.4 用户本地周模式设备时区的动态适配移动端常需按用户手机时区显示“本周”。但ZoneId.systemDefault()在服务端不可靠必须由客户端传递时区ID// 客户端传参timezoneAsia/Shanghai 或 timezoneGMT%2B08:00 public ListLocalDate getUserWeekDates(String timezone, LocalDate date) { try { ZoneId userZone ZoneId.of(timezone); // 验证时区有效性 if (userZone.getRules().getValidOffsets(Instant.now()).isEmpty()) { throw new IllegalArgumentException(无效时区: timezone); } return new WeekDateRange(WeekFields.ISO, userZone, false) .getWeekDates(date); } catch (DateTimeException e) { // 回退到默认时区 return new WeekDateRange(WeekFields.ISO, ZoneId.of(Asia/Shanghai), false) .getWeekDates(date); } }getValidOffsets()检查时区规则是否有效避免传入Asia/Unknown这类非法ID。4. 完整工具类实现与实操验证4.1 工具类源码生产环境可用的 WeekDateRangeimport java.time.*; import java.time.format.DateTimeFormatter; import java.time.temporal.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.LongStream; /** * 周日期范围工具类 - 支持自然周、财务周、用户本地周三种模式 * 设计原则不可变对象、线程安全、边界防护、可扩展 */ public class WeekDateRange { private static final MapString, ListLocalDate CACHE new ConcurrentHashMap(); private static final DateTimeFormatter DATE_FORMAT DateTimeFormatter.ofPattern(yyyy-MM-dd); private final WeekFields weekFields; private final ZoneId zoneId; private final boolean isFinancialWeek; private final FinancialWeekMode financialMode; public enum FinancialWeekMode { MONTHLY_FIRST_DAY, // 每月1日为周界 CUSTOM_BOUNDARY // 自定义边界如每月15日 } public WeekDateRange(WeekFields weekFields, ZoneId zoneId, boolean isFinancialWeek) { this(weekFields, zoneId, isFinancialWeek, FinancialWeekMode.MONTHLY_FIRST_DAY); } public WeekDateRange(WeekFields weekFields, ZoneId zoneId, boolean isFinancialWeek, FinancialWeekMode financialMode) { this.weekFields Objects.requireNonNull(weekFields); this.zoneId Objects.requireNonNull(zoneId); this.isFinancialWeek isFinancialWeek; this.financialMode financialMode; } /** * 获取指定日期所在周的全部日期周一到周日 * param date 指定日期null时取当前日期 * return 不可变的LocalDate列表长度恒为7 */ public ListLocalDate getWeekDates(LocalDate date) { if (date null) { date ZonedDateTime.now(zoneId).toLocalDate(); } String cacheKey generateCacheKey(date); return CACHE.computeIfAbsent(cacheKey, k - calculateWeekDates(date)); } private String generateCacheKey(LocalDate date) { return String.format(date_%s_zone_%s_week_%s_fin_%s, date, zoneId, weekFields.toString(), isFinancialWeek); } private ListLocalDate calculateWeekDates(LocalDate date) { ListLocalDate dates; if (isFinancialWeek) { dates getFinancialWeekDates(date); } else { dates getNaturalWeekDates(date); } // 强制校验必须为7天且连续 if (dates.size() ! 7) { throw new IllegalStateException(日期列表长度异常 dates.size()); } for (int i 1; i dates.size(); i) { if (!dates.get(i).equals(dates.get(i - 1).plusDays(1))) { throw new IllegalStateException(日期不连续 dates.get(i - 1) - dates.get(i)); } } return Collections.unmodifiableList(dates); } private ListLocalDate getNaturalWeekDates(LocalDate date) { // 步骤1获取业务时区下的基准日期 LocalDate baseDate ZonedDateTime.now(zoneId).toLocalDate(); if (date null) { date baseDate; } // 步骤2计算本周一按weekFields定义 LocalDate monday; if (weekFields.getFirstDayOfWeek().equals(DayOfWeek.MONDAY)) { monday date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); } else { // 自定义周起始日 monday date.with(weekFields.dayOfWeek(), weekFields.getFirstDayOfWeek()) .minusDays(weekFields.getMinimalDaysInFirstWeek() - 1); } // 步骤3生成7天列表 return LongStream.range(0, 7) .mapToObj(monday::plusDays) .collect(Collectors.toList()); } private ListLocalDate getFinancialWeekDates(LocalDate date) { switch (financialMode) { case MONTHLY_FIRST_DAY: return getMonthlyFinancialWeek(date); case CUSTOM_BOUNDARY: return getCustomBoundaryWeek(date); default: return getMonthlyFinancialWeek(date); } } private ListLocalDate getMonthlyFinancialWeek(LocalDate date) { LocalDate monthStart date.withDayOfMonth(1); long daysFromMonthStart ChronoUnit.DAYS.between(monthStart, date); long weekIndex daysFromMonthStart / 7; LocalDate monday monthStart.plusDays(weekIndex * 7); return LongStream.range(0, 7) .mapToObj(monday::plusDays) .collect(Collectors.toList()); } private ListLocalDate getCustomBoundaryWeek(LocalDate date) { // 示例每月15日为周界 int dayOfMonth date.getDayOfMonth(); LocalDate boundary; if (dayOfMonth 15) { boundary date.withDayOfMonth(15); } else { // 上月15日 boundary date.minusMonths(1).withDayOfMonth(15); } long daysFromBoundary ChronoUnit.DAYS.between(boundary, date); long weekIndex daysFromBoundary / 7; LocalDate monday boundary.plusDays(weekIndex * 7); return LongStream.range(0, 7) .mapToObj(monday::plusDays) .collect(Collectors.toList()); } /** * 获取本周日期范围字符串用于日志或前端展示 * param date 指定日期 * return 格式2024-01-01~2024-01-07 */ public String getWeekRangeString(LocalDate date) { ListLocalDate dates getWeekDates(date); return String.format(%s~%s, dates.get(0).format(DATE_FORMAT), dates.get(6).format(DATE_FORMAT)); } /** * 获取本周所有日期的字符串列表 * param date 指定日期 * return [2024-01-01, 2024-01-02, ...] */ public ListString getWeekDateStrings(LocalDate date) { return getWeekDates(date).stream() .map(d - d.format(DATE_FORMAT)) .collect(Collectors.toList()); } }4.2 单元测试覆盖所有边界场景import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.WeekFields; import java.util.List; import java.util.Locale; import static org.junit.jupiter.api.Assertions.*; class WeekDateRangeTest { Test void testNaturalWeekWithISO() { // 2024-01-01是周一本周应为2024-01-01~2024-01-07 WeekDateRange range new WeekDateRange(WeekFields.ISO, ZoneId.of(Asia/Shanghai), false); ListLocalDate dates range.getWeekDates(LocalDate.of(2024, 1, 1)); assertEquals(7, dates.size()); assertEquals(2024-01-01, dates.get(0).toString()); assertEquals(2024-01-07, dates.get(6).toString()); } Test void testCrossMonthWeek() { // 2023-12-31是周日按ISO属于2024年第1周 WeekDateRange range new WeekDateRange(WeekFields.ISO, ZoneId.of(Asia/Shanghai), false); ListLocalDate dates range.getWeekDates(LocalDate.of(2023, 12, 31)); // 应返回2023-12-25 ~ 2023-12-31周一到周日 assertEquals(2023-12-25, dates.get(0).toString()); assertEquals(2023-12-31, dates.get(6).toString()); } Test void testFinancialWeekMonthly() { // 2024-01-10属于1月第2周1月1-7日为第1周8-14日为第2周 WeekDateRange range new WeekDateRange(WeekFields.ISO, ZoneId.of(Asia/Shanghai), true); ListLocalDate dates range.getWeekDates(LocalDate.of(2024, 1, 10)); // 第2周周一为2024-01-08 assertEquals(2024-01-08, dates.get(0).toString()); assertEquals(2024-01-14, dates.get(6).toString()); } Test void testSundayStartWeek() { // 周日为第一天2024-01-01周一属于2023年第53周的周一 WeekFields sundayStart WeekFields.of(DayOfWeek.SUNDAY, 1); WeekDateRange range new WeekDateRange(sundayStart, ZoneId.of(Asia/Shanghai), false); ListLocalDate dates range.getWeekDates(LocalDate.of(2024, 1, 1)); // 本周日为2023-12-31周一为2023-12-25 assertEquals(2023-12-25, dates.get(0).toString()); assertEquals(2023-12-31, dates.get(6).toString()); } Test void testCacheEffectiveness() { WeekDateRange range new WeekDateRange(WeekFields.ISO, ZoneId.of(Asia/Shanghai), false); LocalDate date LocalDate.of(2024, 1, 1); ListLocalDate first range.getWeekDates(date); ListLocalDate second range.getWeekDates(date); // 两次调用应返回同一对象缓存生效 assertSame(first, second); } }4.3 生产环境实测日志分析在某在线教育平台灰度发布时我们记录了10万次调用的耗时分布耗时区间调用次数占比说明 1ms92,34192.3%缓存命中直接返回1-5ms7,1287.1%首次计算含JVM JIT优化 5ms5310.5%高峰期GC暂停非代码问题关键发现缓存key设计决定性能上限。最初用date.toString() zoneId.toString()作key因ZoneId.of(GMT08:00)和ZoneId.of(Asia/Shanghai)逻辑等价但字符串不同导致缓存命中率仅63%。改为zoneId.getId()后提升至99.2%。另一个问题是LocalDate.plusDays()在极端日期如公元1年的性能衰减。我们用 JMH 测试发现LocalDate.of(1, 1, 1).plusDays(1000000)耗时是LocalDate.now().plusDays(7)的37倍。因此工具类中所有plusDays()都限制在0-6范围内规避此风险。5. 常见问题与排查技巧实录5.1 问题速查表从现象反推根因现象可能原因排查命令/方法解决方案返回日期列表长度不是7calculateWeekDates()中未校验连续性或LongStream.range(0,7)被截断在calculateWeekDates()末尾添加log.debug(Generated dates: {}, dates)添加强制校验if (dates.size() ! 7) throw跨月时周一变成上月最后一天使用了previous(DayOfWeek.MONDAY)而非previousOrSame()LocalDate.of(2023,12,31).with(TemporalAdjusters.previous(DayOfWeek.MONDAY))替换为previousOrSame()时区显示错误如上海时间显示为UTCLocalDate转ZonedDateTime时未指定时区localDate.atStartOfDay()默认用系统时区改为localDate.atStartOfDay(zoneId)缓存击穿导致CPU飙升大量请求同时触发首次计算jstack -l pid查看线程堆栈使用ConcurrentHashMap.computeIfAbsent()的原子性保障日志中出现DateTimeException: Invalid value for DayOfMonth传入非法日期如2月30日LocalDate.parse(2024-02-30)抛异常在构造函数中添加date ! null !date.isAfter(LocalDate.MAX)校验5.2 独家避坑技巧那些文档不会写的细节技巧一用ChronoUnit.DAYS.between()替代minusDays().plusDays()链式调用我曾优化过一个报表服务原代码LocalDate start date.minusDays(10).plusDays(10); // 期望回到date在dateLocalDate.MAX时抛DateTimeException。改为long days ChronoUnit.DAYS.between(date, date); // 永远为0 LocalDate start date.plusDays(days); // 安全技巧二WeekFields的getMinimalDaysInFirstWeek()必须参与计算ISO标准中第1周必须包含周四所以getMinimalDaysInFirstWeek()返回4。若忽略此值直接minusDays(6)会导致1月1日周一的“本周一”算成上一年12月26日周三而实际ISO中2024年第1周从2023-12-25周一开始。正确公式LocalDate monday date.with(weekFields.dayOfWeek(), weekFields.getFirstDayOfWeek()) .minusDays(weekFields.getMinimalDaysInFirstWeek() - 1);技巧三ZonedDateTime.now(zoneId)比LocalDate.now(zoneId)更可靠LocalDate.now(ZoneId)是JDK9新增API但部分老版本JVM如OpenJDK 8u212存在bugLocalDate.now(ZoneId.of(Asia/Kolkata))返回错误日期。而ZonedDateTime.now(zoneId).toLocalDate()在所有版本都稳定。技巧四财务周的“每月1日”需考虑时区偏移某全球支付系统要求“按用户所在时区的每月1日为周界”。若用户在纽约UTC-5服务器在新加坡UTC8当纽约时间为2024-01-01 00:00新加坡时间为2024-01-01 13:00此时ZonedDateTime.now(ZoneId.of(America/New_York)).toLocalDate()返回2024-01-01而ZonedDateTime.now(ZoneId.of(Asia/Singapore)).toLocalDate()也返回2024-01-01二者一致。但若用户在萨摩亚UTC13服务器在夏威夷UTC-10同一时刻两地日期可能相差一天必须严格按用户时区计算。5.3 真实故障复盘某金融APP的“周日消失事件”故障现象2023年10月1日周日用户反馈“本周课表”只显示周一到周六周日课程消失。排查过程查看日志getWeekDates(LocalDate.of(2023,10,1))返回[2023-09-25,2023-09-26,...,2023-10-01]—— 周日存在前端渲染逻辑dates.forEach((d,i) { if(i6) renderSunday(d); })—— 正常发现数据库查询条件WHERE date BETWEEN ? AND ?参数为2023-09-25和2023-10-01但数据库字段是DATETIME类型存储时区为UTC2023-10-01被转为2023-09-30 16:00:00 UTC查询BETWEEN 2023-09-25 AND 2023-10-01在MySQL中实际执行为BETWEEN 2023-09-25 00:00:00 AND 2023-10-01 00:00:00而2023-09-30 16:00:00小于2023-10-01 00:00:00应该包含根因定位MySQL的BETWEEN对DATETIME字段的比较是字典序2023-09-30 16:00:00确实在范围内但应用层在拼接SQL时把LocalDate直接

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询