Java日期加减实战:从Calendar到LocalDateTime的演进与最佳实践

发布时间:2026/8/6 4:36:46
Java日期加减实战:从Calendar到LocalDateTime的演进与最佳实践 1. 项目概述为什么Java日期处理是每个开发者必须迈过的坎如果你刚开始接触Java或者已经写了几个月代码我敢打赌你一定在某个时刻被日期时间处理搞得焦头烂额。这几乎是每个Java开发者成长路上的“必修课”。我刚开始工作时就曾因为一个简单的“计算下个月今天”的需求对着java.util.Date和java.util.Calendar的API文档研究了整整一个下午最后写出的代码又长又绕还差点因为时区问题在测试环境翻车。日期加减听起来简单得不能再简单不就是给一个日期加上几天、几个月或者几年吗但在Java的世界里这背后却隐藏着从JDK 1.0到JDK 8乃至今天整个日期时间API的演进史和设计哲学。用错了API你的代码可能今天运行正常明天就出BUG可能在这个时区正确换到另一个时区就错得离谱。更别提那些令人困惑的月份从0开始、年份从1900年开始的“历史遗留问题”了。所以今天我们不谈空泛的理论就从一个最实际、最高频的需求——“日期加减”入手彻底搞懂Java中处理这个问题的三种主流方式古老的Calendar、一度流行的第三方库Joda-Time以及现代Java的“官方答案”java.time包LocalDateTime等。我会带你亲手写代码对比它们的优劣并分享我在实际项目中踩过的坑和总结的最佳实践。无论你是正在准备Java面试、解决Java基础问题还是想优化手头的代码这篇文章都能给你一份清晰的“操作手册”。2. 方式一使用java.util.Calendar—— 旧时代的“瑞士军刀”在Java 8之前Calendar类通常配合Date使用是进行日期时间计算包括加减的绝对主力。它就像一个功能齐全但操作复杂的瑞士军刀能完成所有工作但用起来需要格外小心。2.1 Calendar的核心工作机制与初始化Calendar本身是一个抽象类我们通常通过Calendar.getInstance()来获取一个实例。这个方法会根据你的默认时区Locale返回一个具体的子类通常是GregorianCalendar即公历日历。这里就埋下了第一个坑它的操作是直接修改自身Mutable而不是返回一个新的对象。import java.util.Calendar; import java.util.Date; public class CalendarDemo { public static void main(String[] args) { // 获取一个表示当前时间的Calendar实例 Calendar calendar Calendar.getInstance(); System.out.println(当前时间: calendar.getTime()); // 注意calendar.getTime() 返回的是一个Date对象 Date originalDate calendar.getTime(); } }2.2 日期加减的详细操作与参数解析Calendar进行加减的核心方法是add(int field, int amount)。field参数指定要对哪个时间字段进行操作amount是要增加正数或减少负数的量。常用字段常量Calendar类的静态常量Calendar.YEAR年Calendar.MONTH月注意月份从0开始0代表一月11代表十二月Calendar.DATE或Calendar.DAY_OF_MONTH月中的日1-31Calendar.HOUR_OF_DAY24小时制的小时0-23Calendar.MINUTE分钟Calendar.SECOND秒Calendar.MILLISECOND毫秒让我们看一个综合的例子Calendar calendar Calendar.getInstance(); calendar.set(2023, Calendar.MAY, 15); // 设置为2023年5月15日。注意月份是4Calendar.MAY的值是4 System.out.println(原始日期: calendar.getTime()); // 加10天 calendar.add(Calendar.DATE, 10); System.out.println(加10天后: calendar.getTime()); // 2023-05-25 // 减3个月 calendar.add(Calendar.MONTH, -3); System.out.println(减3个月后: calendar.getTime()); // 2023-02-25 // 加2年 calendar.add(Calendar.YEAR, 2); System.out.println(加2年后: calendar.getTime()); // 2025-02-25 // 复杂的加减加1年减20天 calendar.add(Calendar.YEAR, 1); calendar.add(Calendar.DATE, -20); System.out.println(加1年减20天后: calendar.getTime()); // 2026-02-05注意add方法是智能的。例如从1月31日加1个月Calendar会得到2月28日或29日闰年而不是无效的2月31日。同样减操作也会正确处理跨月、跨年的边界。2.3 Calendar的“坑”与实战避雷指南尽管功能强大但Calendar的槽点实在太多这也是它被逐渐淘汰的主要原因。月份从0开始这是最著名的反人类设计。Calendar.JANUARY是0Calendar.DECEMBER是11。在设置或获取月份时你必须在“人类理解的月份”和“Calendar理解的月份”之间进行1或-1的转换极易出错。// 错误示范想设置6月 calendar.set(Calendar.MONTH, 6); // 这实际设置了7月 // 正确做法 calendar.set(Calendar.MONTH, Calendar.JUNE); // 使用常量或者 calendar.set(Calendar.MONTH, 5);可变性Mutable带来的副作用由于Calendar实例会修改自身状态如果你有多个地方引用同一个实例一处修改会影响到所有地方。这在多线程环境下是灾难也是Java面试中关于线程安全的经典问题。Calendar cal1 Calendar.getInstance(); Calendar cal2 cal1; // cal2 和 cal1 指向同一个对象 cal1.add(Calendar.DATE, 1); System.out.println(cal2.getTime()); // cal2的日期也被改变了解决方案如果需要保存一个时间点的快照务必使用clone()方法或重新创建一个实例。Calendar snapshot (Calendar) calendar.clone();API设计冗长且不直观为了获取一个字段的值你需要调用get方法并传入一个常量。代码可读性很差。时区处理隐晦getInstance()默认使用系统时区但时区转换和夏令时处理逻辑隐藏在复杂的get、set、add方法背后不仔细研究文档很容易出错。个人心得在维护遗留系统时你不得不与Calendar打交道。我的建议是在修改相关代码时先写单元测试用明确的用例特别是月底、闰年、时区切换日来验证你的加减逻辑是否正确。对于新项目请毫不犹豫地放弃它。3. 方式二借助Joda-Time—— 辉煌的过渡者在java.time出现之前Joda-Time库几乎是Java社区处理日期时间事实上的标准。它由Stephen Colebourne创建其优秀的设计后来直接影响了Java 8的java.timeAPI。如果你的项目还在用Java 7或更早版本引入Joda-Time是提升日期处理体验的最佳选择。3.1 为什么Joda-Time曾风靡一时Joda-Time解决了Calendar的所有主要痛点不可变性Immutable所有核心类如DateTime都是不可变的。任何修改操作都会返回一个新的实例彻底杜绝了副作用天生线程安全。直观的API方法名自解释性强如plusDays(),minusMonths()阅读代码就像读英语句子。合理的默认值月份从1开始星期也从1星期一开始符合人类直觉。强大的功能提供了极其丰富的时间段、时间间隔、格式化、解析和时区支持。3.2 使用Joda-Time进行日期加减首先你需要将Joda-Time库添加到项目依赖中例如Mavendependency groupIdjoda-time/groupId artifactIdjoda-time/artifactId version2.12.5/version !-- 请使用最新版本 -- /dependency 然后看代码示例 java import org.joda.time.DateTime; import org.joda.time.LocalDate; // 一个只包含日期部分无时间、无时区的类 public class JodaTimeDemo { public static void main(String[] args) { // 创建一个表示当前日期时间的DateTime对象默认时区 DateTime now new DateTime(); System.out.println(当前时间: now); // 加减操作非常直观 DateTime tomorrow now.plusDays(1); DateTime nextMonth now.plusMonths(1); DateTime lastYear now.minusYears(1); System.out.println(明天: tomorrow); System.out.println(下个月: nextMonth); System.out.println(去年: lastYear); // 链式调用进行复杂计算 DateTime complexDate now.plusYears(2).minusMonths(3).plusWeeks(1).minusDays(5); System.out.println(复杂计算后: complexDate); // 使用LocalDate进行纯日期计算无视时间和时区 LocalDate today LocalDate.now(); LocalDate payDay today.plusMonths(1).withDayOfMonth(25); // 下个月25号 System.out.println(发薪日: payDay); // 处理月底问题Joda-Time是智能的 LocalDate jan31 new LocalDate(2023, 1, 31); LocalDate feb28 jan31.plusMonths(1); System.out.println(1月31日加1个月: feb28); // 2023-02-28 } }3.3 Joda-Time的精华Period与DurationJoda-Time的强大之处还在于它对“时间段”概念的清晰建模。Period以人类可理解的单位年、月、周、日表示一个模糊的时间段。例如“1个月”在不同的月份天数不同。Duration以精确的毫秒数表示一个精确的时间长度。这在做日期加减时特别有用import org.joda.time.DateTime; import org.joda.time.Period; public class JodaPeriodDemo { public static void main(String[] args) { DateTime start new DateTime(2023, 1, 1, 0, 0); // 使用Period Period oneMonth Period.months(1); DateTime end start.plus(oneMonth); // 2023-02-01 System.out.println(end); // 更复杂的Period Period complexPeriod Period.years(1).withMonths(2).withDays(15); DateTime future start.plus(complexPeriod); System.out.println(future); // 2024-03-16 } }实战建议即使你的项目已经升级到Java 8如果历史代码中大量使用了Joda-Time也不必急于全部迁移到java.time。两者可以共存并且java.time包中提供了与Joda-Time互转的工具类。对于新编写的模块则优先使用java.time。4. 方式三拥抱java.time(LocalDateTime等) —— 现代的终极方案Java 8引入的java.time包JSR-310是对日期时间API的一次彻底重做其设计大量借鉴了Joda-Time的优点。这是目前以及未来Java日期时间处理的官方标准和最佳实践。它清晰地将日期时间概念分为多个类例如LocalDate只包含日期年-月-日无时间无时区。LocalTime只包含时间时-分-秒-纳秒无日期无时区。LocalDateTime包含日期和时间但无时区信息。ZonedDateTime包含日期、时间和时区。Instant时间线上的一个瞬时点从1970-01-01T00:00:00Z开始的纳秒数用于机器时间。4.1 LocalDateTime等类的加减操作java.time中所有核心类都是不可变的并且提供了前缀为plus和minus的一系列方法以及一个通用的plus(TemporalAmount)和minus(TemporalAmount)方法。import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Period; import java.time.Duration; import java.time.temporal.ChronoUnit; public class JavaTimeDemo { public static void main(String[] args) { // 使用LocalDate LocalDate date LocalDate.of(2023, 5, 15); // 月份从1开始太棒了 System.out.println(原始日期: date); LocalDate datePlus10Days date.plusDays(10); LocalDate dateMinus3Months date.minusMonths(3); LocalDate datePlus2Years date.plusYears(2); System.out.println(加10天: datePlus10Days); // 2023-05-25 System.out.println(减3个月: dateMinus3Months); // 2023-02-15 System.out.println(加2年: datePlus2Years); // 2025-05-15 // 使用LocalDateTime LocalDateTime dateTime LocalDateTime.of(2023, 5, 15, 14, 30, 0); LocalDateTime dateTimePlus dateTime.plusHours(2).minusMinutes(15); System.out.println(加2小时减15分钟: dateTimePlus); // 2023-05-15T16:15 // 链式调用 LocalDateTime complex dateTime.plusYears(1).minusMonths(2).plusDays(3).plusHours(4); System.out.println(复杂计算: complex); } }4.2 使用Period和Duration进行复杂时间跨度计算与Joda-Time类似java.time也使用Period和Duration来分别表示基于日期单位和基于时间的量。Period用于LocalDate的加减表示一段“年月日”的时间。Period oneYearTwoMonthsThreeDays Period.of(1, 2, 3); LocalDate newDate date.plus(oneYearTwoMonthsThreeDays); System.out.println(加1年2个月3天: newDate); // 计算两个日期之间的Period LocalDate start LocalDate.of(2023, 1, 1); LocalDate end LocalDate.of(2024, 3, 15); Period between Period.between(start, end); System.out.printf(间隔: %d 年, %d 月, %d 日%n, between.getYears(), between.getMonths(), between.getDays());Duration用于LocalTime、LocalDateTime、Instant的加减表示一段精确的“纳秒”时间。Duration twoHoursThirtyMinutes Duration.ofHours(2).plusMinutes(30); LocalDateTime newDateTime dateTime.plus(twoHoursThirtyMinutes); System.out.println(加2小时30分钟: newDateTime); // 使用ChronoUnit LocalDateTime nextWeek dateTime.plus(1, ChronoUnit.WEEKS); System.out.println(加1周: nextWeek);4.3 处理月底、闰年等边界情况java.time的加减操作是“智能”的能自动处理无效日期。LocalDate endOfJan LocalDate.of(2023, 1, 31); LocalDate endOfFeb endOfJan.plusMonths(1); System.out.println(1月31日加1个月: endOfFeb); // 2023-02-28 LocalDate leapYearDate LocalDate.of(2024, 2, 28); LocalDate nextDay leapYearDate.plusDays(1); System.out.println(闰年2月28日加1天: nextDay); // 2024-02-29 LocalDate march1st leapYearDate.plusDays(2); System.out.println(闰年2月28日加2天: march1st); // 2024-03-01这种“月末最后一天”的语义在业务中非常常见比如订阅服务、账单周期java.time帮你完美处理了。4.4 时区敏感日期加减ZonedDateTime当你的应用需要面向全球用户时时区就成了必须考虑的问题。ZonedDateTime是解决这个问题的利器。import java.time.ZoneId; import java.time.ZonedDateTime; public class ZonedDateTimeDemo { public static void main(String[] args) { // 上海时间 ZonedDateTime shanghaiTime ZonedDateTime.now(ZoneId.of(Asia/Shanghai)); System.out.println(上海当前时间: shanghaiTime); // 加一天会考虑夏令时吗在Asia/Shanghai时区不会因为中国不实行夏令时。 ZonedDateTime tomorrowShanghai shanghaiTime.plusDays(1); System.out.println(上海明天此时: tomorrowShanghai); // 切换到纽约时间 ZonedDateTime newYorkTime shanghaiTime.withZoneSameInstant(ZoneId.of(America/New_York)); System.out.println(对应的纽约时间: newYorkTime); // 在纽约时间上加一天会处理夏令时转换 ZonedDateTime tomorrowNewYork newYorkTime.plusDays(1); System.out.println(纽约明天此时: tomorrowNewYork); } }关键点ZonedDateTime的plus/minus操作会考虑时区规则包括夏令时DST调整。例如在夏令时开始或结束的那一天加减天数得到的小时数可能会发生变化。这是Calendar和简单的LocalDateTime无法自动处理的。5. 三种方式对比与选型指南了解了三种方式后我们该如何选择下面这个表格从多个维度进行了对比特性维度java.util.CalendarJoda-Timejava.time(LocalDateTime等)引入版本JDK 1.1第三方库 (Joda-Time)JDK 8(官方标准)设计理念可变、抽象、复杂不可变、流畅、直观不可变、清晰、领域驱动月份表示0-11(反人类)1-12(符合直觉)1-12(符合直觉)线程安全否(可变对象)是(不可变对象)是(不可变对象)API易用性冗长、易错非常流畅、易读流畅、易读功能丰富度基础时区处理隐晦非常丰富丰富标准且现代处理月底/闰年智能处理智能处理智能处理时区与夏令时支持但API复杂优秀支持优秀且清晰的支持性能一般优秀优秀未来维护已过时不推荐新项目使用已停止新功能开发维护模式官方主力持续发展选型结论与实战建议新项目JDK 8无条件选择java.time包。它是现代Java的组成部分设计优秀功能全面是未来的标准。从项目第一天起就使用它能避免无数潜在的坑。旧项目升级/维护如果项目已使用Joda-Time不必急于大规模迁移。Joda-Time本身非常稳定可靠且与java.time可以互操作通过toInstant()、from()等方法。对于新增加的模块或修改的类可以逐步引入java.time。如果项目大量使用Calendar和Date这是一个技术债。在修改相关代码时强烈建议将其重构为java.time。虽然工作量可能不小但长期来看能极大提升代码的可读性、可维护性和稳定性。可以制定计划分模块、分批次进行迁移。必须支持JDK 7或更早版本在这种情况下你无法使用java.time。那么引入Joda-Time库是你最好的选择它能极大改善开发体验。一个常见的迁移示例将一段使用Calendar计算“下个月第一天”的代码重构成使用java.time。// 旧方式 (Calendar) Calendar cal Calendar.getInstance(); cal.add(Calendar.MONTH, 1); cal.set(Calendar.DAY_OF_MONTH, 1); Date firstDayOfNextMonth cal.getTime(); // 新方式 (java.time) LocalDate today LocalDate.now(); LocalDate firstDayOfNextMonthModern today.plusMonths(1).withDayOfMonth(1); // 或者更优雅的 LocalDate firstDayOfNextMonthModern2 today.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth());可以看到新代码不仅更短而且意图一目了然彻底避免了月份从0开始的陷阱。6. 高频场景实战与避坑精讲掌握了核心方法我们来看看在实际开发中日期加减有哪些高频场景和对应的“坑”。6.1 场景一计算会员到期日、订阅续费日这是电商、SaaS系统中最常见的需求。假设用户在今天2023-10-27购买了1个月的会员。import java.time.LocalDate; import java.time.Period; public class MembershipDemo { public static void main(String[] args) { LocalDate signUpDate LocalDate.of(2023, 10, 27); Period oneMonth Period.ofMonths(1); // 计算到期日签约日 1个月 LocalDate expireDate signUpDate.plus(oneMonth); System.out.println(会员到期日: expireDate); // 2023-11-27 // **坑点如果签约日是1月31日加1个月是多少** LocalDate trickySignUp LocalDate.of(2023, 1, 31); LocalDate trickyExpire trickySignUp.plusMonths(1); System.out.println(特殊日期到期日: trickyExpire); // 2023-02-28 // 业务上通常可以接受因为2月没有31号。如果需要固定到月末可以用withDayOfMonth调整逻辑。 } }业务思考到期日是否需要包含当天即10月27日购买11月27日当天还能使用吗这需要和产品经理明确业务规则。plusMonths(1)的计算结果是包含起始日的。6.2 场景二生成过去N天/未来N天的日期序列常用于数据统计、报表生成比如需要获取最近7天的日期列表。import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; public class DateSequenceDemo { public static void main(String[] args) { LocalDate today LocalDate.now(); int pastDays 7; // 方法1传统循环 ListLocalDate dateList1 new ArrayList(); for (int i 0; i pastDays; i) { dateList1.add(today.minusDays(i)); } System.out.println(过去7天含今天: dateList1); // 方法2使用Stream (Java 8) ListLocalDate dateList2 IntStream.range(0, pastDays) .mapToObj(today::minusDays) .toList(); System.out.println(过去7天含今天: dateList2); // 格式化输出 DateTimeFormatter formatter DateTimeFormatter.ofPattern(yyyy-MM-dd); dateList2.forEach(date - System.out.println(date.format(formatter))); } }6.3 场景三结合时间与时长计算如缓存过期、任务超时这种场景通常涉及精确到秒甚至毫秒的计算LocalDateTime和Duration是绝配。import java.time.Duration; import java.time.LocalDateTime; public class CacheTimeoutDemo { public static void main(String[] args) { LocalDateTime cacheTime LocalDateTime.now(); Duration timeout Duration.ofMinutes(30); // 缓存30分钟过期 LocalDateTime expireTime cacheTime.plus(timeout); System.out.println(缓存写入时间: cacheTime); System.out.println(缓存过期时间: expireTime); // 判断是否过期 LocalDateTime checkTime LocalDateTime.now().plusMinutes(35); boolean isExpired checkTime.isAfter(expireTime); System.out.println(当前时间 checkTime 是否过期? isExpired); } }6.4 场景四处理跨时区的航班时间计算这是ZonedDateTime的典型应用场景。假设从上海8飞往纽约-5考虑夏令时飞行时间14小时。import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; public class FlightTimeDemo { public static void main(String[] args) { DateTimeFormatter formatter DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm z); // 上海起飞时间 LocalDateTime departureLocal LocalDateTime.of(2023, 10, 28, 14, 30); ZonedDateTime departureShanghai ZonedDateTime.of(departureLocal, ZoneId.of(Asia/Shanghai)); System.out.println(上海起飞时间: departureShanghai.format(formatter)); // 飞行14小时 Duration flightDuration Duration.ofHours(14); // 计算到达时间上海时间 ZonedDateTime arrivalShanghaiTime departureShanghai.plus(flightDuration); System.out.println(到达时间上海时区: arrivalShanghaiTime.format(formatter)); // 转换为纽约时间美国东部时间考虑夏令时结束。2023年夏令时于11月5日结束 // 我们需要知道到达时纽约的时区规则。ZonedDateTime会自动处理。 ZonedDateTime arrivalNewYork arrivalShanghaiTime.withZoneSameInstant(ZoneId.of(America/New_York)); System.out.println(到达时间纽约当地时间: arrivalNewYork.format(formatter)); // **关键点**由于飞行跨越了夏令时变更点实际显示的纽约当地时间与简单时区偏移计算可能不同。 // ZonedDateTime 确保了计算的绝对正确性。 } }这个例子清晰地展示了为什么不能简单地用LocalDateTime加小时数再手动调时区。ZonedDateTime结合了绝对的时刻Instant和时区规则是处理跨时区时间计算的唯一正确工具。7. 性能考量、序列化与数据库交互在大型应用中日期时间的性能、存储和传输也是需要考虑的方面。7.1 性能微基准测试通常java.time和Joda-Time的性能都远优于Calendar因为后者有大量的同步开销和内部状态计算。对于亿万次级别的简单加减操作java.time的LocalDate.plusDays()可能比Calendar.add()快一个数量级。但在绝大多数业务场景下日期操作的性能开销可以忽略不计代码的清晰度和正确性才是首要考虑因素。7.2 序列化JSON与数据库JSON序列化如JacksonDate/Calendar需要配置日期格式否则可能输出为毫秒时间戳。java.time类型Jackson有专门的模块jackson-datatype-jsr310来处理。添加依赖后可以自动序列化为ISO-8601格式如2023-05-15T14:30:00也可以自定义格式。dependency groupIdcom.fasterxml.jackson.datatype/groupId artifactIdjackson-datatype-jsr310/artifactId /dependency数据库交互如JPA/HibernateDate/Calendar映射到java.sql.Date/Timestamp较为直接但类型模糊。java.time类型从Hibernate 5.2/JPA 2.2开始原生支持。LocalDate-DATELocalTime-TIMELocalDateTime-TIMESTAMP(不带时区)Instant-TIMESTAMP(通常映射为带时区的TIMESTAMP取决于数据库)最佳实践在实体类中直接使用LocalDate等类型让JPA去处理转换。这比在业务层手动转换Date要安全、清晰得多。7.3 避免日期时间处理的常见反模式用long类型的时间戳进行业务计算时间戳自1970年以来的毫秒数适合存储和机器交换但不适合人类阅读和进行复杂的日历计算如加一个月。业务层应始终使用高级的日期时间对象LocalDate,ZonedDateTime仅在接口边界如API参数、数据库存储进行转换。忽略时区如果你的用户遍布全球那么任何涉及“天”的业务如“次日达”、“每日报表”都必须考虑时区。存储和计算时应使用ZonedDateTime或至少存储UTC时间的Instant在展示时再转换为用户本地时间。混淆LocalDateTime和ZonedDateTimeLocalDateTime不包含时区信息它只是一个挂钟时间。2023-10-28T02:30这个时间在纽约和上海代表的是完全不同的两个时刻。如果时间需要明确对应到时间线上的一个点就必须使用ZonedDateTime或Instant。8. 总结与个人工具箱分享回顾一下Java中进行日期加减你有三种武器Calendar一把生锈的瑞士军刀只在维护古董代码时不得已而用之。Joda-Time一把锋利的好刀如果你被困在Java 7的世界它是你的救星。java.time官方打造的现代多功能军刀设计精良是你新项目和老项目重构的首选。从我个人的经验来看彻底掌握java.time包是Java开发者从“能用”到“用好”的关键一步。它强迫你以更清晰、更准确的方式去思考时间这个复杂的概念。刚开始可能会觉得类有点多LocalDate,LocalTime,LocalDateTime,ZonedDateTime,Instant,Period,Duration但用习惯了就会发现这种精确的领域划分让代码的意图无比清晰。最后分享几个我工作中常用的“工具箱”方法你可以直接拿去用import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.TemporalAdjusters; public class DateUtils { /** * 获取某个日期所在月的第一天 */ public static LocalDate firstDayOfMonth(LocalDate date) { return date.with(TemporalAdjusters.firstDayOfMonth()); } /** * 获取某个日期所在月的最后一天 */ public static LocalDate lastDayOfMonth(LocalDate date) { return date.with(TemporalAdjusters.lastDayOfMonth()); } /** * 获取下一个星期X的日期 (如下个周一) * param date 起始日期 * param dayOfWeek 星期几使用DayOfWeek枚举如DayOfWeek.MONDAY */ public static LocalDate nextDayOfWeek(LocalDate date, DayOfWeek dayOfWeek) { return date.with(TemporalAdjusters.next(dayOfWeek)); } /** * 判断一个日期是否是周末周六或周日 */ public static boolean isWeekend(LocalDate date) { DayOfWeek day date.getDayOfWeek(); return day DayOfWeek.SATURDAY || day DayOfWeek.SUNDAY; } /** * 计算两个日期之间相差的自然日天数常用 */ public static long daysBetween(LocalDate start, LocalDate end) { return Math.abs(start.until(end).getDays()); // 使用Period的getDays() // 或者更精确的ChronoUnit.DAYS.between(start, end); } }记住处理日期时间谨慎永远不为过。在写任何相关代码前多问自己几句需不需要考虑时区月底怎么办闰年呢夏令时呢想清楚了再动手能帮你省下大量调试和救火的时间。

相关新闻