Java 8 LocalDate 日期操作全攻略
Java 中的 java.time.LocalDate
类是 Java 8 引入的新日期时间 API (JSR 310) 的核心类型之一。它代表一个不带时区信息的日期,通常格式为 年-月-日(例如:2023-10-27)。它只包含日期部分(年、月、日),不包含时间(时、分、秒、纳秒)和时区信息。
核心特性
- 不可变性 (Immutable):
LocalDate
的所有实例都是不可变的。任何修改操作(如加天数、减月份)都会返回一个新的LocalDate
对象,原对象保持不变。这是线程安全的关键。 - 无时区 (Time-Zone Agnostic): 它只表示日历日期(如生日、节假日、事件日期),与一天中的具体时间和地球上的哪个位置无关。如果需要表示特定的时刻或考虑时区,应使用
Instant
,ZonedDateTime
或OffsetDateTime
。 - ISO-8601 标准: 默认遵循 ISO-8601 日历系统(即公历/格里高利历)。这是国际通用的标准日期格式
YYYY-MM-DD
。 - 丰富的 API: 提供了大量便捷、直观的方法用于日期的计算、比较、解析和格式化。
主要用途
- 表示生日、纪念日、事件日期、截止日期等仅需日期信息的场景。
- 进行日期计算(如加/减天数、周数、月数、年数)。
- 比较两个日期的先后。
- 获取日期的组成部分(年、月、日、星期几)。
- 格式化日期输出或解析字符串为日期。
常用创建方式
-
获取当前日期:
LocalDate today = LocalDate.now(); // 使用系统默认时区的当前日期
-
指定年、月、日:
LocalDate specificDate1 = LocalDate.of(2023, 10, 27); // 2023年10月27日 LocalDate specificDate2 = LocalDate.of(2023, Month.OCTOBER, 27); // 使用 Month 枚举更清晰
-
解析 ISO 格式字符串 (YYYY-MM-DD):
LocalDate parsedDate = LocalDate.parse("2023-10-27"); // 直接解析标准格式
-
解析自定义格式字符串: 使用
DateTimeFormatter
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); LocalDate customParsedDate = LocalDate.parse("27/10/2023", formatter);
-
从纪元日开始计算:
LocalDate epochDay = LocalDate.ofEpochDay(365); // 1970-01-01 + 365天 = 1971-01-01
-
获取指定年份的第几天:
LocalDate dayOfYear = LocalDate.ofYearDay(2023, 300); // 2023年的第300天
常用操作与方法
-
获取日期组成部分:
int year = today.getYear(); // 获取年份 (e.g., 2023) Month month = today.getMonth(); // 获取月份枚举 (e.g., Month.OCTOBER) int monthValue = today.getMonthValue(); // 获取月份数字 (1-12) (e.g., 10) int dayOfMonth = today.getDayOfMonth(); // 获取月份中的第几天 (1-31) (e.g., 27) DayOfWeek dayOfWeek = today.getDayOfWeek(); // 获取星期几枚举 (e.g., DayOfWeek.FRIDAY) int dayOfYear = today.getDayOfYear(); // 获取年份中的第几天 (1-366) (e.g., 300)
-
日期比较:
boolean isBefore = date1.isBefore(date2); // date1 是否在 date2 之前 boolean isAfter = date1.isAfter(date2); // date1 是否在 date2 之后 boolean isEqual = date1.isEqual(date2); // date1 是否等于 date2 int comparison = date1.compareTo(date2); // 比较: 负(前), 零(等), 正(后)
-
日期计算 (加减):
LocalDate tomorrow = today.plusDays(1); // 加1天 LocalDate nextWeek = today.plusWeeks(1); // 加1周 LocalDate nextMonth = today.plusMonths(1); // 加1月 (智能处理月末) LocalDate nextYear = today.plusYears(1); // 加1年LocalDate yesterday = today.minusDays(1); // 减1天 LocalDate lastMonth = today.minusMonths(1); // 减1月 // 其他 minusWeeks, minusYears 类似
-
调整日期 (TemporalAdjusters): 更复杂的调整,如获取本月第一天、最后一天、下一个星期一等。
LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth()); LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth()); LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
-
计算日期差:
- 使用
Period
(适合年、月、日差):Period period = Period.between(startDate, endDate); int years = period.getYears(); int months = period.getMonths(); int days = period.getDays(); // 注意:这个差不是总天数,是“年/月”之后剩余的天数差
- 使用
ChronoUnit
(适合特定单位的总差):long daysBetween = ChronoUnit.DAYS.between(startDate, endDate); // 总天数差 long monthsBetween = ChronoUnit.MONTHS.between(startDate, endDate); // 总月数差 long yearsBetween = ChronoUnit.YEARS.between(startDate, endDate); // 总年数差
- 使用
-
日期判断:
boolean isLeapYear = today.isLeapYear(); // 是否是闰年 int lengthOfMonth = today.lengthOfMonth(); // 该月有多少天 (28, 29, 30, 31) int lengthOfYear = today.lengthOfYear(); // 该年有多少天 (365 or 366)
-
格式化输出:
String isoDate = today.toString(); // "2023-10-27" (ISO-8601) DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 EEEE"); String formattedDate = today.format(formatter); // e.g., "2023年10月27日 星期五"
重要注意事项
-
时区无关性:
LocalDate.now()
获取的是系统默认时区的当前日期。如果需要在特定时区获取当前日期,应使用:LocalDate todayInTokyo = LocalDate.now(ZoneId.of("Asia/Tokyo"));
虽然
LocalDate
本身没有时区,但获取“当前日期”这个行为依赖于时区(因为不同时区进入新一天的时间不同)。 -
月末处理:
plusMonths()
,minusMonths()
,withMonth()
等方法能智能处理月末日期。例如:LocalDate jan31 = LocalDate.of(2023, 1, 31); LocalDate feb28 = jan31.plusMonths(1); // 2023-02-28 (非闰年) LocalDate mar31 = feb28.plusMonths(1); // 2023-03-31
-
与旧 Date/Calendar 转换:
- 虽然不推荐混用新旧API,但有时需要转换:
// LocalDate -> java.util.Date (通过 Instant + ZoneId) Date oldDate = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); // java.util.Date -> LocalDate (通过 Instant + ZoneId) LocalDate newDate = oldDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
- 虽然不推荐混用新旧API,但有时需要转换:
总结
LocalDate
是 Java 现代日期时间 API 中用于表示纯日期(年-月-日)的核心类。它的不可变性、线程安全性、丰富的操作方法和清晰的语义使其成为处理日期相关逻辑的理想选择。在开发中,应优先使用 LocalDate
、LocalTime
、LocalDateTime
、ZonedDateTime
等 java.time
包下的类,避免使用过时且易出错的 java.util.Date
和 java.util.Calendar
。