2. 方法列表与功能说明

方法 功能
withDayOfMonth(int dayOfMonth) 修改为指定的月内日期
withDayOfYear(int dayOfYear) 修改为指定的年内日期(1-365/366)
withMonth(int month) 修改为指定的月份(1-12)
withYear(int year) 修改为指定的年份

返回类型LocalDate(新实例)


3. 详细操作步骤

3.1 withDayOfMonth(int dayOfMonth)

步骤 1:获取原始 LocalDate

import java.time.LocalDate;

LocalDate original = LocalDate.of(2025, 8, 15);
System.out.println("原始日期: " + original); // 2025-08-15

步骤 2:修改日(月内)

// 修改为当月1日
LocalDate firstDay = original.withDayOfMonth(1);
System.out.println("当月1日: " + firstDay); // 2025-08-01

// 修改为当月最后一天(先获取当月天数)
int lastDay = original.lengthOfMonth(); // 8月有31天
LocalDate lastDayOfMonth = original.withDayOfMonth(lastDay);
System.out.println("当月最后一天: " + lastDayOfMonth); // 2025-08-31

步骤 3:验证原对象不变

System.out.println("原始日期仍为: " + original); // 2025-08-15

3.2 withDayOfYear(int dayOfYear)

步骤 1:获取原始 LocalDate

LocalDate original = LocalDate.of(2025, 8, 15);
System.out.println("原始日期: " + original); // 2025-08-15

步骤 2:修改为年内第N天

// 修改为年内第1天(1月1日)
LocalDate firstDayOfYear = original.withDayOfYear(1);
System.out.println("年内第1天: " + firstDayOfYear); // 2025-01-01

// 修改为年内第100天
LocalDate day100 = original.withDayOfYear(100);
System.out.println("年内第100天: " + day100); // 2025-04-10

// 修改为年内最后一天
int lastDayOfYear = original.lengthOfYear(); // 2025年有365天
LocalDate lastDay = original.withDayOfYear(lastDayOfYear);
System.out.println("年内最后一天: " + lastDay); // 2025-12-31

步骤 3:验证原对象不变

System.out.println("原始日期仍为: " + original); // 2025-08-15

3.3 withMonth(int month)

步骤 1:获取原始 LocalDate

LocalDate original = LocalDate.of(2025, 8, 15);
System.out.println("原始日期: " + original); // 2025-08-15

步骤 2:修改月份

// 修改为1月
LocalDate january = original.withMonth(1);
System.out.println("1月15日: " + january); // 2025-01-15

// 修改为12月
LocalDate december = original.withMonth(12);
System.out.println("12月15日: " + december); // 2025-12-15

// 使用 Month 枚举(更清晰)
import java.time.Month;
LocalDate march = original.withMonth(Month.MARCH.getValue());
System.out.println("3月15日: " + march); // 2025-03-15

步骤 3:处理跨月日溢出

// 原日期是1月31日,改为2月
LocalDate jan31 = LocalDate.of(2025, 1, 31);
// LocalDate feb31 = jan31.withMonth(2); 
// 抛出 DateTimeException: Invalid date 'FEBRUARY 31'

// 正确做法:先调整日,再调整月
LocalDate febLast = jan31.withDayOfMonth(28).withMonth(2);
System.out.println("2月最后一天: " + febLast); // 2025-02-28

3.4 withYear(int year)

步骤 1:获取原始 LocalDate

LocalDate original = LocalDate.of(2025, 8, 15);
System.out.println("原始日期: " + original); // 2025-08-15

步骤 2:修改年份

// 修改为2023年
LocalDate past = original.withYear(2023);
System.out.println("2023年8月15日: " + past); // 2023-08-15

// 修改为未来年份
LocalDate future = original.withYear(2030);
System.out.println("2030年8月15日: " + future); // 2030-08-15

步骤 3:处理闰年2月29日

// 原日期是闰年2月29日
LocalDate leapDay = LocalDate.of(2024, 2, 29);
System.out.println("闰年2月29日: " + leapDay); // 2024-02-29

// 修改为非闰年
// LocalDate nonLeap = leapDay.withYear(2025); 
// 抛出 DateTimeException: Invalid date 'FEBRUARY 29'

// 正确做法:调整为2月28日或3月1日
LocalDate feb28 = leapDay.withYear(2025).withDayOfMonth(28);
System.out.println("非闰年2月28日: " + feb28); // 2025-02-28

4. 常见错误

4.1 无效日期值

LocalDate date = LocalDate.of(2025, 8, 15);

// ❌ 错误:8月没有40日
// date.withDayOfMonth(40); // DateTimeException

// ❌ 错误:年内没有400天
// date.withDayOfYear(400); // DateTimeException

// ❌ 错误:月份超出1-12
// date.withMonth(13); // DateTimeException

4.2 日期溢出(跨月)

LocalDate jan31 = LocalDate.of(2025, 1, 31);

// ❌ 错误:2月没有31日
// jan31.withMonth(2); // DateTimeException

4.3 闰年2月29日问题

LocalDate leapDay = LocalDate.of(2024, 2, 29);

// ❌ 错误:2025年不是闰年
// leapDay.withYear(2025); // DateTimeException

5. 注意事项

  1. 不可变性:所有 with 方法都返回新对象,原对象不变。
  2. 有效性验证:方法会自动验证日期有效性,无效日期抛出异常。
  3. 月份范围withMonth() 参数为 1-12,不是 0-11。
  4. 链式调用安全:可以安全地链式调用,但要注意中间状态的有效性。
  5. 性能:创建新对象有轻微开销,但通常可忽略。

6. 使用技巧

6.1 链式调用

LocalDate date = LocalDate.of(2025, 8, 15);

// 链式修改多个字段
LocalDate modified = date.withYear(2023)
                         .withMonth(12)
                         .withDayOfMonth(25);
System.out.println(modified); // 2023-12-25

6.2 获取月份第一天/最后一天

LocalDate anyDay = LocalDate.of(2025, 8, 15);

// 当月第一天
LocalDate firstDay = anyDay.withDayOfMonth(1);

// 当月最后一天
LocalDate lastDay = anyDay.withDayOfMonth(anyDay.lengthOfMonth());

6.3 获取年份第一天/最后一天

LocalDate anyDay = LocalDate.of(2025, 8, 15);

// 年内第一天
LocalDate firstDay = anyDay.withDayOfYear(1);

// 年内最后一天
LocalDate lastDay = anyDay.withDayOfYear(anyDay.lengthOfYear());

6.4 安全的月份修改(处理31日问题)

public static LocalDate withMonthSafe(LocalDate date, int month) {
    try {
        return date.withMonth(month);
    } catch (DateTimeException e) {
        // 如果目标月天数不足,返回最后一天
        LocalDate temp = date.withMonth(month);
        return temp.withDayOfMonth(temp.lengthOfMonth());
    }
}

// 使用
LocalDate jan31 = LocalDate.of(2025, 1, 31);
LocalDate feb28 = withMonthSafe(jan31, 2); // 返回 2025-02-28

7. 最佳实践与性能优化

7.1 最佳实践

  1. 优先使用 with 方法:比手动解析字符串更高效、更安全。
  2. 处理异常情况:在可能产生无效日期的场景中,提前验证或捕获异常。
  3. 使用常量:对于固定的修改(如每月1日),可直接使用 withDayOfMonth(1)
  4. 结合其他方法:与 plus()minus() 等方法配合使用。

7.2 性能优化

  1. 避免不必要的创建:如果新旧日期相同,with 方法仍会创建新对象。
  2. 批量操作优化:在循环中使用时,确保逻辑正确。
  3. 缓存常用日期:对于频繁使用的特殊日期(如每月1日),可考虑缓存。

8. 总结

方法 用途 参数范围 典型用例
withDayOfMonth() 修改月内日期 1 至 当月天数 获取每月1日/最后一天
withDayOfYear() 修改年内日期 1-365/366 获取年内第N天、年初/年末
withMonth() 修改月份 1-12 日期跨月调整
withYear() 修改年份 int 范围 日期跨年调整

核心要点

  • 所有方法都返回新实例,原对象不变。
  • 会进行严格的有效性检查,无效日期抛出 DateTimeException
  • 支持链式调用,代码简洁。
  • 处理闰年和月份天数差异时需特别注意。

推荐使用场景

// 获取当月第一天
LocalDate first = date.withDayOfMonth(1);

// 获取当年最后一天
LocalDate last = date.withDayOfYear(date.lengthOfYear());

// 将日期调整到指定年月(安全版本)
LocalDate target = date.withYear(2023).withMonth(2);
try {
    return target.withDayOfMonth(Math.min(date.getDayOfMonth(), target.lengthOfMonth()));
} catch (DateTimeException e) {
    return target.withDayOfMonth(target.lengthOfMonth());
}