Java 8 LocalDate实战:从基础创建到复杂业务日期处理

📅 2026/7/15 5:57:53
Java 8 LocalDate实战:从基础创建到复杂业务日期处理
1. Java 8 LocalDate入门从零开始掌握日期处理如果你还在用老旧的Date和Calendar处理日期那就像用算盘计算圆周率一样低效。Java 8引入的LocalDate彻底改变了游戏规则它就像给你的代码装上了日期处理的瑞士军刀。我经历过项目从Date迁移到LocalDate的全过程最大的感受就是再也不用担心闰秒、时区转换这些头疼问题了。先看个最简单的例子LocalDate today LocalDate.now(); System.out.println(今天的日期是 today); // 输出类似2023-08-15这行代码背后其实藏着三个重要特性不可变性每次操作都返回新对象线程安全无忧ISO-8601标准全球统一的日期表示方式纯日期类型没有混乱的时间时区干扰创建LocalDate的七种武器// 1. 当前日期系统默认时区 LocalDate date1 LocalDate.now(); // 2. 指定年月日月份用1-12表示 LocalDate date2 LocalDate.of(2023, 8, 15); // 3. 使用Month枚举更优雅 LocalDate date3 LocalDate.of(2023, Month.AUGUST, 15); // 4. 年一年中的第几天1-365/366 LocalDate date4 LocalDate.ofYearDay(2023, 227); // 2023-08-15 // 5. 从1970-01-01开始计算 LocalDate date5 LocalDate.ofEpochDay(19531); // 2023-08-15 // 6. 解析标准格式字符串 LocalDate date6 LocalDate.parse(2023-08-15); // 7. 自定义格式解析 LocalDate date7 LocalDate.parse(15/08/2023, DateTimeFormatter.ofPattern(dd/MM/yyyy));实际项目中我特别推荐方法7当需要处理不同国家日期格式时特别管用。曾经有个国际项目要处理MM/dd/yyyy和dd-MM-yyyy混用的情况DateTimeFormatter轻松搞定。2. 日期比较与计算业务场景实战处理过电商促销活动的同学都知道日期比较是刚需。LocalDate的比较操作简单得让人感动LocalDate startPromo LocalDate.of(2023, 8, 1); LocalDate endPromo LocalDate.of(2023, 8, 31); LocalDate userDate LocalDate.of(2023, 8, 15); // 基础三连比较 boolean isAfter userDate.isAfter(startPromo); // true boolean isBefore userDate.isBefore(endPromo); // true boolean isInPromo !userDate.isBefore(startPromo) !userDate.isAfter(endPromo); // true日期计算才是重头戏。做过会员系统的应该深有体会各种到期日计算太常见了LocalDate today LocalDate.now(); // 基础加减法 LocalDate nextWeek today.plusWeeks(1); LocalDate lastMonth today.minusMonths(1); // 更灵活的带单位计算 LocalDate future today.plus(3, ChronoUnit.HALF_DAYS); // 报错LocalDate不支持半天 // 计算两个日期差值三种方式 Period between Period.between(today, nextWeek); // P7D long days ChronoUnit.DAYS.between(today, nextWeek); // 7 long months ChronoUnit.MONTHS.between(today, today.plusYears(1)); // 12踩坑提醒计算间隔时Period和ChronoUnit各有所长。需要年月日完整差值用Period只要单一单位用ChronoUnit。曾经在计算年龄时用错方法导致2月29日出生的用户每年年龄跳动被测试同学追着打。3. 格式化与解析搞定多国日期显示日期格式化是老生常谈LocalDate的玩法可不一样。先看个国际化案例LocalDate date LocalDate.of(2023, 8, 15); // 中文格式 String chineseFormat date.format( DateTimeFormatter.ofPattern(yyyy年MM月dd日 E, Locale.CHINA)); // 2023年08月15日 星期二 // 美国格式 String usFormat date.format( DateTimeFormatter.ofPattern(MMM dd, yyyy, Locale.US)); // Aug 15, 2023 // ISO格式数据库存储推荐 String isoFormat date.format(DateTimeFormatter.ISO_DATE); // 2023-08-15预定义的格式化器才是真香警告// 使用预定义格式器 DateTimeFormatter shortFormatter DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT); System.out.println(date.format(shortFormatter.withLocale(Locale.CHINA))); // 23/8/15 System.out.println(date.format(shortFormatter.withLocale(Locale.US))); // 8/15/23项目经验分享在微服务架构中建议API层统一使用ISO格式传输日期前端按需格式化。曾经有个项目前后端为日期格式扯皮两周最后定这个规范才消停。4. 进阶业务处理TemporalAdjuster实战TemporalAdjuster是LocalDate的王牌功能能优雅解决各种变态业务需求。来看几个真实案例4.1 财务周期计算做过ERP系统的都知道月底关账是个技术活LocalDate today LocalDate.now(); // 当月最后一天自动处理不同月份 LocalDate lastDay today.with(TemporalAdjusters.lastDayOfMonth()); // 下个季度第一天 LocalDate nextQuarter today.with( temporal - { int currentQuarter (temporal.get(Month.MONTH) / 3) 1; int year temporal.get(Year.YEAR); return temporal.with(Year.of(year)) .with(Month.of(currentQuarter * 3 1)) .with(TemporalAdjusters.firstDayOfMonth()); });4.2 工作日调度处理每周报表生成// 下个周五 LocalDate nextFriday today.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); // 本月最后一个工作日 LocalDate lastWorkday today.with(TemporalAdjusters.lastDayOfMonth()) .with(temporal - { LocalDate date LocalDate.from(temporal); while (date.getDayOfWeek() DayOfWeek.SATURDAY || date.getDayOfWeek() DayOfWeek.SUNDAY) { date date.minusDays(1); } return date; });4.3 中国节假日处理虽然Java没有内置中国节假日但我们可以扩展public class ChineseHolidayAdjuster implements TemporalAdjuster { private static final SetMonthDay HOLIDAYS Set.of( MonthDay.of(Month.JANUARY, 1), // 元旦 MonthDay.of(Month.MAY, 1), // 劳动节 MonthDay.of(Month.OCTOBER, 1) // 国庆 // 其他节假日... ); Override public Temporal adjustInto(Temporal temporal) { LocalDate date LocalDate.from(temporal); if (HOLIDAYS.contains(MonthDay.from(date))) { // 如果是节假日找到下一个工作日 return date.with(t - { LocalDate d LocalDate.from(t); while (HOLIDAYS.contains(MonthDay.from(d)) || d.getDayOfWeek().getValue() 5) { d d.plusDays(1); } return d; }); } return temporal; } } // 使用示例 LocalDate springFestival LocalDate.of(2023, 1, 22); LocalDate workday springFestival.with(new ChineseHolidayAdjuster());5. 新旧API互操作平滑迁移方案老项目迁移时Date和LocalDate的转换是必经之路。这是我总结的安全转换方案// Date转LocalDate兼容null public static LocalDate convertToLocalDate(Date date) { return date ! null ? date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() : null; } // LocalDate转Date处理时区 public static Date convertToDate(LocalDate localDate) { return localDate ! null ? Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()) : null; } // 处理SQL日期 public static LocalDate convertFromSqlDate(java.sql.Date sqlDate) { return sqlDate ! null ? sqlDate.toLocalDate() : null; } public static java.sql.Date convertToSqlDate(LocalDate localDate) { return localDate ! null ? java.sql.Date.valueOf(localDate) : null; }特别提醒数据库交互时JPA 2.2直接支持LocalDateMyBatis需要类型处理器。用JDBC的话PreparedStatement已经支持setObject和getObject方法。6. 性能优化与最佳实践经过多个项目实战总结出这些经验对象复用DateTimeFormatter是线程安全的应该复用// 推荐做法 private static final DateTimeFormatter CUSTOM_FORMATTER DateTimeFormatter.ofPattern(yyyy-MM-dd); // 不推荐每次创建新实例 public String badFormat(LocalDate date) { return date.format(DateTimeFormatter.ofPattern(yyyy-MM-dd)); }批量处理优化使用Stream处理日期集合ListLocalDate dates IntStream.range(0, 100) .mapToObj(i - LocalDate.now().plusDays(i)) .collect(Collectors.toList());时区陷阱即使LocalDate无时区系统默认时区会影响now()// 明确指定时区更安全 LocalDate utcDate LocalDate.now(ZoneId.of(UTC));验证技巧处理用户输入日期时要验证public OptionalLocalDate safeParse(String dateStr, String pattern) { try { return Optional.of(LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern))); } catch (DateTimeParseException e) { return Optional.empty(); } }排序优化LocalDate天然实现ComparableListLocalDate sortedDates dates.stream() .sorted() .collect(Collectors.toList());