一、核心概念

LocalDate 是 Java 8 引入的现代化日期 API,位于 java.time 包中,表示一个 不包含时区和时间的纯日期(如 2025-08-16)。

它提供了多个静态工厂方法来创建 LocalDate 实例,其中最常用的是:

  • LocalDate.of(year, month, day):通过年、月、日创建
  • LocalDate.ofYearDay(year, dayOfYear):通过年份和年中的第几天创建

二、LocalDate.of() 方法

1. 方法定义

public static LocalDate of(int year, int month, int dayOfMonth)
public static LocalDate of(int year, Month month, int dayOfMonth)
  • 参数
    • year:年份(支持负数表示公元前)
    • month:月份(1-12),或使用 Month 枚举(如 Month.AUGUST
    • dayOfMonth:该月中的日期(1-31,根据月份和闰年自动校验)
  • 返回值LocalDate 实例
  • 异常DateTimeException(当日期无效时抛出)

2. 示例代码

import java.time.LocalDate;
import java.time.Month;

public class OfExample {
    public static void main(String[] args) {
        // 方式1:使用数字表示月份
        LocalDate date1 = LocalDate.of(2025, 8, 16);
        System.out.println("日期1: " + date1); // 2025-08-16

        // 方式2:使用 Month 枚举(推荐,更清晰)
        LocalDate date2 = LocalDate.of(2025, Month.AUGUST, 16);
        System.out.println("日期2: " + date2); // 2025-08-16

        // 其他示例
        LocalDate newYear = LocalDate.of(2025, 1, 1);        // 新年
        LocalDate christmas = LocalDate.of(2025, 12, 25);     // 圣诞节
        LocalDate leapDay = LocalDate.of(2024, 2, 29);        // 闰年2月29日

        System.out.println("新年: " + newYear);
        System.out.println("圣诞节: " + christmas);
        System.out.println("闰日: " + leapDay);
    }
}

3. 常见错误

// ❌ 错误1:无效日期(2月30日)
// LocalDate invalid = LocalDate.of(2025, 2, 30); 
// 抛出: DateTimeException: Invalid date 'FEBRUARY 30'

// ❌ 错误2:月份超出范围
// LocalDate invalid = LocalDate.of(2025, 13, 1); 
// 抛出: DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 13

// ❌ 错误3:空的 Month 枚举
// LocalDate invalid = LocalDate.of(2025, null, 1); 
// 抛出: NullPointerException

三、LocalDate.ofYearDay() 方法

1. 方法定义

public static LocalDate ofYearDay(int year, int dayOfYear)
  • 参数
    • year:年份
    • dayOfYear:该年中的第几天(1-365 或 1-366)
  • 返回值LocalDate 实例
  • 异常DateTimeException(当 dayOfYear 超出范围时)

💡 提示:此方法非常适合处理“年积日”(Day of Year)数据,常见于气象、科学计算等领域。

2. 示例代码

import java.time.LocalDate;

public class OfYearDayExample {
    public static void main(String[] args) {
        // 获取 2025 年的第 1 天
        LocalDate firstDay = LocalDate.ofYearDay(2025, 1);
        System.out.println("2025年第1天: " + firstDay); // 2025-01-01

        // 获取 2025 年的第 100 天
        LocalDate day100 = LocalDate.ofYearDay(2025, 100);
        System.out.println("2025年第100天: " + day100); // 2025-04-10

        // 获取 2025 年的最后一天(第365天)
        LocalDate lastDay = LocalDate.ofYearDay(2025, 365);
        System.out.println("2025年第365天: " + lastDay); // 2025-12-31

        // 闰年示例
        LocalDate leapDay = LocalDate.ofYearDay(2024, 60);
        System.out.println("2024年第60天: " + leapDay); // 2024-02-29 (闰年)

        // 验证:今天是今年的第几天
        LocalDate today = LocalDate.now();
        int dayOfYear = today.getDayOfYear();
        System.out.println("今天是今年的第 " + dayOfYear + " 天");
        
        // 反向验证:ofYearDay 是否正确
        LocalDate fromDay = LocalDate.ofYearDay(today.getYear(), dayOfYear);
        System.out.println("验证结果: " + today.equals(fromDay)); // true
    }
}

3. 常见错误

// ❌ 错误1:超出非闰年范围
// LocalDate invalid = LocalDate.ofYearDay(2025, 366);
// 抛出: DateTimeException: Invalid dayOfYear: 366 for year: 2025

// ❌ 错误2:超出闰年范围
// LocalDate invalid = LocalDate.ofYearDay(2024, 367);
// 抛出: DateTimeException: Invalid dayOfYear: 367 for year: 2024

// ❌ 错误3:dayOfYear <= 0
// LocalDate invalid = LocalDate.ofYearDay(2025, 0);
// 抛出: DateTimeException: Invalid dayOfYear: 0

四、使用技巧

✅ 技巧 1:结合 getDayOfYear() 使用

LocalDate date = LocalDate.of(2025, 8, 16);
int dayOfYear = date.getDayOfYear(); // 228
LocalDate reconstructed = LocalDate.ofYearDay(2025, dayOfYear);
System.out.println(date.equals(reconstructed)); // true

✅ 技巧 2:计算年中进度

LocalDate today = LocalDate.now();
double progress = (double) today.getDayOfYear() / (today.isLeapYear() ? 366 : 365);
System.out.printf("今年已过去 %.1f%%\n", progress * 100);

✅ 技巧 3:批量生成日期

// 生成某年所有周五
int year = 2025;
for (int day = 1; day <= 365; day++) {
    LocalDate d = LocalDate.ofYearDay(year, day);
    if (d.getDayOfWeek().getValue() == 5) { // Friday
        System.out.println(d);
    }
}

五、注意事项

  1. 不可变性LocalDate 是不可变对象,所有操作返回新实例。
  2. 范围检查:两个方法都会进行严格的日期有效性检查。
  3. 性能:都是轻量级操作,可频繁调用。
  4. 线程安全LocalDate 是线程安全的,适合在多线程环境使用。

六、最佳实践

✅ 推荐使用 Month 枚举

// 推荐(清晰、避免错误)
LocalDate.of(2025, Month.AUGUST, 16)

// 可接受但易错
LocalDate.of(2025, 8, 16)

✅ 优先使用 ofYearDay() 处理年积日数据

// 当输入是 "2025-228" 这种格式时
LocalDate date = LocalDate.ofYearDay(2025, 228);

✅ 结合 isLeapYear() 判断

LocalDate date = LocalDate.of(2024, 2, 29);
if (date.isLeapYear()) {
    System.out.println("是闰年");
}

七、总结对比

方法 适用场景 参数 优点 缺点
of(year, month, day) 通用创建 年、月、日 直观、常用 月份易错(0-11 vs 1-12)
ofYearDay(year, dayOfYear) 年积日处理 年、年第几天 简洁处理 DOY 数据 需知道具体天数

💡 终极建议
在 Java 8+ 项目中,优先使用 LocalDate.of()ofYearDay() 创建日期,它们比传统的 CalendarDate 构造函数更安全、更直观、更现代化。