Java LocalDate 详解:Java 8 及以后日期处理完整指南

目次

什么是 LocalDate?

自 Java 8 起,日期处理有了显著的演进,而 LocalDate 正是这场演进的核心。LocalDate 是一个不可变对象,仅表示日期(年、月、日,例如 2025-06-26),不涉及时间或时区概念。它让你能够以简洁安全的方式处理今天的日期或特定的日历日期。

与传统 Date 类的区别

在 Java 8 之前,通常使用 java.util.Datejava.util.Calendar 等类。然而,这些类存在诸多问题,包括 易出错的设计(如月份从 0 开始)、缺乏线程安全、以及 不直观的 API。因此,它们常常导致 bug 或意外行为。

LocalDate 解决了这些问题,并提供了以下特性:

  • 仅显式管理年、月、日(例如 2025 年 6 月 26 日)
  • 不可变对象(值不可更改,确保安全)
  • 直观的方法名和 API 设计(例如 plusDays(1) 表示下一天,getMonthValue() 返回月份数字)
  • 独立于时区(无论系统或服务器设置如何,行为一致)

何时应该使用 LocalDate?

当你需要明确处理日期、无需时间信息,并希望以安全、简便的方式实现日期操作时,LocalDate 是理想选择。典型的使用场景包括:

  • 记录仅包含日期的事件,如生日或纪念日
  • 管理日程、截止日期和到期日
  • 计算截止时间或剩余天数

基于以上原因,LocalDate 可以视为 Java 日期处理的新标准。接下来我们将详细说明 LocalDate 的基本用法和初始化方式。

LocalDate 的基本操作

LocalDate 提供了直观且简洁的 API 用于日期操作。本节通过具体示例解释常用功能。

获取当前日期

要获取今天的日期,使用 LocalDate.now() 方法。它返回当前系统日期(独立于时区)的 LocalDate 实例。

import java.time.LocalDate;

LocalDate today = LocalDate.now();
System.out.println(today); // Example: 2025-06-26

创建特定日期

要创建过去或未来的任意日期,使用 LocalDate.of(int year, int month, int dayOfMonth)。这样可以自由构造诸如 2024 年 12 月 31 日等日期。

LocalDate specialDay = LocalDate.of(2024, 12, 31);
System.out.println(specialDay); // 2024-12-31

从字符串解析日期

要从类似 “2023-03-15” 的字符串生成 LocalDate,使用 LocalDate.parse(String text) 方法。如果字符串遵循标准 ISO 格式(“YYYY-MM-DD”),则无需额外配置。

LocalDate parsedDate = LocalDate.parse("2023-03-15");
System.out.println(parsedDate); // 2023-03-15

使用自定义格式解析(补充)

如果需要处理自定义格式的日期,例如 “2023/03/15”,可以将 DateTimeFormatterparse() 结合使用。

import java.time.format.DateTimeFormatter;

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate formattedDate = LocalDate.parse("2023/03/15", formatter);
System.out.println(formattedDate); // 2023-03-15

如上所示,LocalDate 的基本操作直观且易于上手。在现代 Java 代码中,初始化和转换日期几乎不再令人困惑。下一章节我们将说明如何从 LocalDate 中提取年、月、日以及星期几。

获取年份、月份、日期和星期几

LocalDate 不仅可以轻松获取完整日期,还能单独提取年份、月份、日期和星期几等组件。本节介绍如何获取这些常用元素。

获取年份、月份和日期

要从 LocalDate 实例中获取各个组件,使用专门的 getter 方法。

LocalDate date = LocalDate.of(2025, 6, 26);

int year = date.getYear();           // Year (e.g. 2025)
int month = date.getMonthValue();    // Month as a number (1–12, e.g. 6)
int day = date.getDayOfMonth();      // Day of month (1–31, e.g. 26)

System.out.println("Year: " + year);
System.out.println("Month: " + month);
System.out.println("Day: " + day);

获取月份和星期名称

LocalDate 还支持获取月份和星期的名称,这在需要文本表示时非常有用。

  • 月份名称(英文表示) 使用 getMonth() 返回一个 Month 枚举值(例如 JUNE)。
    import java.time.Month;
    
    Month monthName = date.getMonth(); // JUNE (uppercase English)
    System.out.println(monthName);
    
  • 星期名称 getDayOfWeek() 返回一个 DayOfWeek 枚举(例如 THURSDAY)。
    import java.time.DayOfWeek;
    
    DayOfWeek dayOfWeek = date.getDayOfWeek(); // THURSDAY (uppercase English)
    System.out.println(dayOfWeek);
    

用日语显示月份和星期名称

如果想要以日语而不是英语显示月份或星期名称,可以使用 DateTimeFormatter 自定义输出。

import java.time.format.DateTimeFormatter;
import java.util.Locale;

DateTimeFormatter formatter =
    DateTimeFormatter.ofPattern("yyyy年MM月dd日(E)", Locale.JAPANESE);
String formatted = date.format(formatter); // 2025年06月26日(木)
System.out.println(formatted);

已检索组件的摘要

LocalDate 的一个关键优势是它允许你使用直观的方法检索 年份、月份、日期和星期。这种灵活性使得在业务和 Web 应用中处理日期更加轻松。

日期计算(加法和减法)

日期计算(如加天或减天)在日程管理和截止日期计算中经常需要。使用 LocalDate,你可以安全且直观地执行 “三天后”、 “一周前” 或 “两个日期之间的差距” 等操作。

添加日期

  • 添加天数
    LocalDate today = LocalDate.of(2025, 6, 26);
    LocalDate threeDaysLater = today.plusDays(3); // Three days later
    System.out.println(threeDaysLater); // 2025-06-29
    
  • 添加月份或年份
    LocalDate nextMonth = today.plusMonths(1); // One month later
    LocalDate nextYear = today.plusYears(1);   // One year later
    System.out.println(nextMonth); // 2025-07-26
    System.out.println(nextYear);  // 2026-06-26
    

减少日期

  • 减去天数、月份或年份
    LocalDate lastWeek = today.minusWeeks(1);   // One week earlier
    LocalDate previousDay = today.minusDays(1); // Previous day
    System.out.println(lastWeek);    // 2025-06-19
    System.out.println(previousDay); // 2025-06-25
    

计算日期之间的差异

  • 计算天数差
    import java.time.temporal.ChronoUnit;
    
    LocalDate start = LocalDate.of(2025, 6, 1);
    LocalDate end = LocalDate.of(2025, 6, 26);
    
    long daysBetween = ChronoUnit.DAYS.between(start, end); // 25
    System.out.println(daysBetween); // 25
    
  • 使用其他单位(月份、年份)计算差异
    long monthsBetween = ChronoUnit.MONTHS.between(start, end); // 0
    long yearsBetween = ChronoUnit.YEARS.between(start, end);   // 0
    

小结

通过使用 LocalDate 的加法和减法方法,你可以轻松实现常见的日期计算,如 “下个月的截止日期” 或 “自上次事件以来的天数”。

由于 LocalDate 是不可变的,原始实例永远不会被修改。每一次操作都会返回一个新的 LocalDate 实例,确保日期处理的安全性。

高级操作:调整特定日期

在实际的日期处理场景中,单纯的加法或减法往往不够。常见需求包括确定 “本月最后一天” 或 “下个月的第一天”。LocalDate 提供了便捷的 API 来完成这些类型的调整。

使用 TemporalAdjuster

使用 LocalDate 时,你可以将 with() 方法与 TemporalAdjuster 结合,执行直观的操作,例如“月末”“月初”或“下一个特定的星期几”。内置的调整器在 TemporalAdjusters 类中提供。

获取月份的第一天和最后一天

  • 获取当月的最后一天
    import java.time.LocalDate;
    import java.time.temporal.TemporalAdjusters;
    
    LocalDate date = LocalDate.of(2025, 6, 26);
    LocalDate endOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
    System.out.println(endOfMonth); // 2025-06-30
    
  • 获取当月的第一天
    LocalDate startOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());
    System.out.println(startOfMonth); // 2025-06-01
    

基于工作日的调整

基于工作日的调整——例如“本月的第二个星期一”或“下一个星期五”——也很容易实现。

  • 获取下一个星期五
    import java.time.DayOfWeek;
    
    LocalDate nextFriday =
        date.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
    System.out.println(nextFriday); // 2025-06-27
    
  • 获取本月的第二个星期一
    LocalDate secondMonday =
        date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.MONDAY));
    System.out.println(secondMonday); // 2025-06-09
    

调整到一年中的开始或结束

你可以使用相同的方法来获取一年中的第一天或最后一天。

LocalDate startOfYear =
    date.with(TemporalAdjusters.firstDayOfYear());
LocalDate endOfYear =
    date.with(TemporalAdjusters.lastDayOfYear());

System.out.println(startOfYear); // 2025-01-01
System.out.println(endOfYear);   // 2025-12-31

创建自定义调整器

如果需要基于特定业务规则的自定义日期调整逻辑,可以自行实现 TemporalAdjuster 接口。

通过将 LocalDateTemporalAdjusters 结合使用,即使是复杂的日期计算也变得直观且灵活。这在处理截止日期或业务特定的时间表时尤为有用。

使用 LocalDate 和 LocalDateTime

在 Java 日期时间 API(java.time 包)中,LocalDate 仅表示日期,而 LocalDateTime 同时表示日期和时间。实际开发中,开发者常常需要在这两种类型之间进行转换。本节将说明如何完成这些转换。

将 LocalDate 转换为 LocalDateTime

要为 LocalDate 添加时间信息并转换为 LocalDateTime,可以使用 atTime()atStartOfDay()

  • 添加特定时间
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    
    LocalDate date = LocalDate.of(2025, 6, 26);
    LocalDateTime dateTime =
        date.atTime(14, 30, 0); // 2025-06-26 14:30:00
    System.out.println(dateTime);
    
  • 创建当天开始的 LocalDateTime
    LocalDateTime startOfDay =
        date.atStartOfDay(); // 2025-06-26T00:00
    System.out.println(startOfDay);
    

将 LocalDateTime 转换为 LocalDate

要从 LocalDateTime 中仅提取日期部分,使用 toLocalDate() 方法。

import java.time.LocalDateTime;

LocalDateTime dateTime =
    LocalDateTime.of(2025, 6, 26, 14, 30);
LocalDate dateOnly = dateTime.toLocalDate();
System.out.println(dateOnly); // 2025-06-26

将 LocalDate 与 LocalTime 组合

你也可以将 LocalDateLocalTime 组合,生成 LocalDateTime

import java.time.LocalTime;

LocalTime time = LocalTime.of(9, 0);
LocalDateTime combined =
    date.atTime(time); // 2025-06-26T09:00
System.out.println(combined);

小结

  • 使用 atTime()atStartOfDay()LocalDate 转换为 LocalDateTime
  • 使用 toLocalDate()LocalDateTime 转换为 LocalDate
  • 在实际系统中,分离和组合日期与时间是常见需求

异常处理与最佳实践

日期处理如果使用了无效的值或格式,往往会导致意外的异常。即使在使用 LocalDate 时,也可能因为 不存在的日期解析错误 而抛出异常。本节将说明常见异常以及安全处理的最佳实践。

指定不存在的日期

如果尝试创建一个不存在的日期——例如 2023 年 2 月 30 日——将会抛出 DateTimeException

import java.time.LocalDate;

// Example that throws an exception
LocalDate invalidDate = LocalDate.of(2023, 2, 30);

在这种情况下,重要的是捕获异常并进行适当的处理。

try {
    LocalDate invalidDate = LocalDate.of(2023, 2, 30);
} catch (DateTimeException e) {
    System.out.println("An invalid date was specified: " + e.getMessage());
}

字符串解析期间的异常

使用 LocalDate.parse() 时,如果字符串格式无效或日期本身不存在,会抛出 DateTimeParseException

import java.time.format.DateTimeParseException;

try {
    LocalDate date = LocalDate.parse("2023/02/30");
} catch (DateTimeParseException e) {
    System.out.println("Failed to parse date: " + e.getMessage());
}

最佳实践

  • 提前验证输入值
    在接受用户输入时,先验证格式和数值,防止在解析时出现异常。
  • 捕获异常并提供友好的提示
    与其让应用直接崩溃,不如返回清晰、易懂的错误信息给用户。
  • 利用不可变性
    由于 LocalDate 是不可变的,始终将计算结果视为 新实例,而不是覆盖已有实例。

常见陷阱

  • 处理闰年中的 2 月 29 日
  • 指定超出有效范围的值(例如 month = 13,day = 0)
  • 字符串解析时格式不匹配

这些问题在初学者中尤为常见,需要格外注意。

LocalDate 的实际使用场景

LocalDate 并不仅限于简单的日期存储——它在真实业务系统和应用中被广泛使用。下面列出几个实用示例。

生日与年龄计算

根据出生日期计算年龄是经典用例。配合 Period 使用 LocalDate 可以轻松实现。

import java.time.LocalDate;
import java.time.Period;

LocalDate birthDay = LocalDate.of(1990, 8, 15);
LocalDate today = LocalDate.now();

Period period = Period.between(birthDay, today);
int age = period.getYears();
System.out.println("Age: " + age);

管理截止日期和到期日

LocalDate 也适用于任务管理系统,例如计算距离截止日期还有多少天。

LocalDate deadline = LocalDate.of(2025, 7, 10);
long daysLeft =
    java.time.temporal.ChronoUnit.DAYS.between(today, deadline);

System.out.println("Days remaining until deadline: " + daysLeft);

排程与日历生成

诸如 “每月第二个星期一的会议” 之类的需求,可以使用 TemporalAdjusters 轻松实现。

import java.time.DayOfWeek;
import java.time.temporal.TemporalAdjusters;

LocalDate secondMonday =
    LocalDate.of(2025, 7, 1)
        .with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.MONDAY));

System.out.println("Second Monday of July: " + secondMonday);

Web 系统和 API 中的日期校验

后端系统经常使用 LocalDate 来校验日期输入。例如,你可能需要拒绝未来的日期或早于某个范围的日期。

LocalDate inputDate = LocalDate.parse("2024-12-31");
LocalDate tenYearsAgo = today.minusYears(10);

if (inputDate.isAfter(today)) {
    System.out.println("Future dates are not allowed");
} else if (inputDate.isBefore(tenYearsAgo)) {
    System.out.println("Please specify a date within the last 10 years");
} else {
    System.out.println("The date is valid");
}

Adoption in Training and Production Systems

As seen in many competing articles, LocalDate is now a standard topic in Java training programs and onboarding curricula. It is also widely used in production systems such as banking business-day calculations and inventory management.

FAQ (Frequently Asked Questions)

Q1. What is the difference between LocalDate and Date?

A.
LocalDate is part of the modern Java Date and Time API introduced in Java 8 and represents only a date (year, month, day). java.util.Date, on the other hand, is a legacy class that includes time and internally manages values in milliseconds.

LocalDate is immutable, intuitive, and thread-safe, and is recommended for modern Java development.

Q2. Can LocalDate handle time zones?

A.
LocalDate itself does not contain time zone information. If time zone support is required, use ZonedDateTime or OffsetDateTime. A common approach is to manage dates with LocalDate first, then convert when time zones become necessary.

Q3. What is the difference between LocalDate and LocalDateTime?

A.
LocalDate represents only a date. LocalDateTime represents both date and time (e.g. 2025-06-26 14:00). Use LocalDate for deadlines or anniversaries, and LocalDateTime for events with precise timestamps.

Q4. Can I parse custom date formats?

A.
Yes. By using DateTimeFormatter, you can parse dates in custom formats.

import java.time.format.DateTimeFormatter;

DateTimeFormatter formatter =
    DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date =
    LocalDate.parse("2025/06/26", formatter);

Q5. How should I handle invalid dates or formats?

A.
Invalid dates or formats cause exceptions such as DateTimeException or DateTimeParseException. Use try-catch blocks, validate input in advance, and provide clear error messages to users.

Q6. Can I compare two LocalDate instances?

A.
Yes. Use isAfter(), isBefore(), or isEqual().

LocalDate date1 = LocalDate.of(2025, 6, 26);
LocalDate date2 = LocalDate.of(2025, 7, 1);

if (date1.isBefore(date2)) {
    System.out.println("date1 is earlier than date2");
}

Conclusion

This article provided a comprehensive explanation of Java LocalDate, from basic concepts to advanced use cases. Key points include:

  • What LocalDate is An immutable date-only object introduced in Java 8 that fixes the flaws of legacy Date and Calendar classes.
  • Basic usage Retrieving the current date, creating specific dates, and parsing strings using simple APIs.
  • Extracting components Easily retrieving year, month, day, and weekday values.
  • Date calculations Intuitive addition, subtraction, and difference calculations.
  • Date adjustments Using TemporalAdjusters to handle end-of-month, weekdays, and more.
  • Integration with time APIs Flexible conversion between LocalDate, LocalDateTime, and LocalTime.
  • Safe handling and best practices Proper exception handling and validation for robust systems.
  • Real-world applications and FAQs Practical examples such as age calculation, deadlines, scheduling, and validation.

Next Steps

Once you master LocalDate, date handling becomes straightforward and reliable. For more advanced scenarios—such as time zones, period calculations, and formatting—consider learning ZonedDateTime, Period, and DateTimeFormatter.

Use LocalDate as a powerful foundation to build clean, robust, and maintainable Java applications.