Java 日期时间 API 完整学习指南:从 LocalDate 到 ZonedDateTime

本文基于 JDK 8+ / JDK 21 的现代日期时间 API,对 Java 中最常用的 LocalDate、LocalTime、LocalDateTime、DateTimeFormatter、Period、Duration、ChronoUnit、Instant、ZoneId、ZonedDateTime、OffsetDateTime、TemporalAdjusters、YearMonth 等 API 进行系统整理。 全文按照“API 总表 → 单独示例 → 类型转换 → Web 开发映射 → 综合案例”展开,并对核心方法进行连续编号。掌握本文内容后,应能够独立完成生日计算、订单过期、时间格式化、时间差计算、时区转换、时间戳处理等常见业务。

一、Java 日期时间体系总览

Java 现代日期时间 API 位于:

java.time

真正需要建立的是下面这套认知:

日期
 ↓
LocalDate

时间
 ↓
LocalTime

日期 + 时间
 ↓
LocalDateTime

格式转换
 ↓
DateTimeFormatter

日期差
 ↓
Period

精确时间差
 ↓
Duration / ChronoUnit

绝对时间点
 ↓
Instant

时区
 ↓
ZoneId

日期时间 + 时区
 ↓
ZonedDateTime / OffsetDateTime

二、LocalDate:日期

LocalDate 表示:

2026-09-17

它只有:

年 + 月 + 日

没有时分秒,也没有时区。

2.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | ------------ | --------------- | ------------------ | ------------------------------------- | --------------------------- | | 1 | 当前日期 | LocalDate | now() | 无 | 获取系统当前日期 | | 2 | 指定日期 | LocalDate | of() | int year, int month, int dayOfMonth | 创建指定日期 | | 3 | 解析后的日期 | LocalDate | parse() | CharSequence text | 字符串转日期 | | 4 | 年份 | int | getYear() | 无 | 获取年份 | | 5 | 月份数字 | int | getMonthValue() | 无 | 获取月份,范围 1~12 | | 6 | 月份枚举 | Month | getMonth() | 无 | 获取月份枚举 | | 7 | 日 | int | getDayOfMonth() | 无 | 获取当月第几天 | | 8 | 星期 | DayOfWeek | getDayOfWeek() | 无 | 获取星期 | | 9 | 新日期 | LocalDate | plusDays() | long daysToAdd | 增加天数 | | 10 | 新日期 | LocalDate | plusMonths() | long monthsToAdd | 增加月份 | | 11 | 新日期 | LocalDate | plusYears() | long yearsToAdd | 增加年份 | | 12 | 新日期 | LocalDate | minusDays() | long daysToSubtract | 减少天数 | | 13 | 新日期 | LocalDate | minusMonths() | long monthsToSubtract | 减少月份 | | 14 | 新日期 | LocalDate | minusYears() | long yearsToSubtract | 减少年份 | | 15 | 新日期 | LocalDate | withYear() | int year | 修改年份 | | 16 | 新日期 | LocalDate | withMonth() | int month | 修改月份 | | 17 | 新日期 | LocalDate | withDayOfMonth() | int dayOfMonth | 修改日期 | | 18 | 是否更早 | boolean | isBefore() | ChronoLocalDate other | 判断是否在另一个日期之前 | | 19 | 是否更晚 | boolean | isAfter() | ChronoLocalDate other | 判断是否在另一个日期之后 | | 20 | 是否相等 | boolean | isEqual() | ChronoLocalDate other | 判断日期是否相同 | | 21 | 本月天数 | int | lengthOfMonth() | 无 | 获取当前月份天数 | | 22 | 是否闰年 | boolean | isLeapYear() | 无 | 判断当前年份是否为闰年 | | 23 | 当天零点 | LocalDateTime | atStartOfDay() | 无 | 转换为当天 00:00 的日期时间 |

2.2 示例

import java.time.LocalDate;

public class LocalDateDemo {
    public static void main(String[] args) {

        LocalDate today = LocalDate.now();

        LocalDate birthday =
                LocalDate.of(2004, 5, 20);

        LocalDate date =
                LocalDate.parse("2026-09-17");

        System.out.println(date.getYear());
        System.out.println(date.getMonthValue());
        System.out.println(date.getMonth());
        System.out.println(date.getDayOfMonth());
        System.out.println(date.getDayOfWeek());

        System.out.println(date.plusDays(10));
        System.out.println(date.plusMonths(1));
        System.out.println(date.plusYears(1));

        System.out.println(date.minusDays(7));
        System.out.println(date.minusMonths(1));
        System.out.println(date.minusYears(1));

        System.out.println(date.withYear(2030));
        System.out.println(date.withMonth(12));
        System.out.println(date.withDayOfMonth(1));

        LocalDate future = LocalDate.of(2030, 1, 1);

        System.out.println(date.isBefore(future));
        System.out.println(date.isAfter(future));
        System.out.println(date.isEqual(future));

        System.out.println(date.lengthOfMonth());
        System.out.println(date.isLeapYear());

        System.out.println(date.atStartOfDay());
    }
}

三、LocalTime:时间

LocalTime 表示:

10:30:45

包含:

时 + 分 + 秒 + 纳秒

不包含日期和时区。

3.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | -------- | ----------- | ---------------- | ---------------------------------- | -------------- | | 24 | 当前时间 | LocalTime | now() | 无 | 获取当前时间 | | 25 | 指定时间 | LocalTime | of() | int hour, int minute | 创建指定时分 | | 26 | 指定时间 | LocalTime | of() | int hour, int minute, int second | 创建指定时分秒 | | 27 | 时间对象 | LocalTime | parse() | CharSequence text | 字符串转时间 | | 28 | 小时 | int | getHour() | 无 | 获取小时 | | 29 | 分钟 | int | getMinute() | 无 | 获取分钟 | | 30 | 秒 | int | getSecond() | 无 | 获取秒 | | 31 | 新时间 | LocalTime | plusHours() | long hoursToAdd | 增加小时 | | 32 | 新时间 | LocalTime | plusMinutes() | long minutesToAdd | 增加分钟 | | 33 | 新时间 | LocalTime | minusHours() | long hoursToSubtract | 减少小时 | | 34 | 新时间 | LocalTime | minusMinutes() | long minutesToSubtract | 减少分钟 | | 35 | 是否更早 | boolean | isBefore() | LocalTime other | 比较时间先后 | | 36 | 是否更晚 | boolean | isAfter() | LocalTime other | 比较时间先后 |

3.2 示例

import java.time.LocalTime;

public class LocalTimeDemo {
    public static void main(String[] args) {

        LocalTime now = LocalTime.now();

        LocalTime start =
                LocalTime.of(8, 30);

        LocalTime end =
                LocalTime.of(18, 0, 0);

        LocalTime time =
                LocalTime.parse("12:30:45");

        System.out.println(time.getHour());
        System.out.println(time.getMinute());
        System.out.println(time.getSecond());

        System.out.println(time.plusHours(2));
        System.out.println(time.plusMinutes(30));

        System.out.println(time.minusHours(1));
        System.out.println(time.minusMinutes(10));

        System.out.println(start.isBefore(end));
        System.out.println(start.isAfter(end));
    }
}

四、LocalDateTime:日期 + 时间

这是 Java Web 开发中非常常用的日期类型。

2026-09-17T10:30:20

常见业务:

用户创建时间
文章发布时间
订单创建时间
更新时间
登录时间
支付时间

4.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | ------------ | --------------- | --------------- | -------------------------------- | ------------------ | | 37 | 当前日期时间 | LocalDateTime | now() | 无 | 获取当前日期时间 | | 38 | 指定日期时间 | LocalDateTime | of() | year, month, day, hour, minute | 创建指定日期时间 | | 39 | 日期时间 | LocalDateTime | parse() | CharSequence text | 解析字符串 | | 40 | 日期 | LocalDate | toLocalDate() | 无 | 提取日期部分 | | 41 | 时间 | LocalTime | toLocalTime() | 无 | 提取时间部分 | | 42 | 新对象 | LocalDateTime | plusDays() | long days | 增加天数 | | 43 | 新对象 | LocalDateTime | plusHours() | long hours | 增加小时 | | 44 | 新对象 | LocalDateTime | plusMinutes() | long minutes | 增加分钟 | | 45 | 新对象 | LocalDateTime | minusDays() | long days | 减少天数 | | 46 | 是否更早 | boolean | isBefore() | ChronoLocalDateTime<?> other | 比较日期时间 | | 47 | 是否更晚 | boolean | isAfter() | ChronoLocalDateTime<?> other | 比较日期时间 | | 48 | 带时区时间 | ZonedDateTime | atZone() | ZoneId zone | 给日期时间绑定时区 |

4.2 示例

import java.time.LocalDateTime;

public class LocalDateTimeDemo {

    public static void main(String[] args) {

        LocalDateTime now =
                LocalDateTime.now();

        LocalDateTime time =
                LocalDateTime.of(
                        2026,
                        9,
                        17,
                        10,
                        30
                );

        System.out.println(now);

        System.out.println(
                time.toLocalDate()
        );

        System.out.println(
                time.toLocalTime()
        );

        System.out.println(
                time.plusDays(7)
        );

        System.out.println(
                time.plusHours(2)
        );

        System.out.println(
                time.plusMinutes(30)
        );

        System.out.println(
                time.minusDays(1)
        );

        System.out.println(
                now.isBefore(time)
        );

        System.out.println(
                now.isAfter(time)
        );
    }
}

五、日期时间对象为什么都是不可变对象

LocalDateLocalTimeLocalDateTime 等核心类都是不可变对象。

这和 String 非常相似。

例如:

LocalDate date =
        LocalDate.of(2026, 9, 17);

date.plusDays(1);

System.out.println(date);

结果仍然是:

2026-09-17

因为:

date.plusDays(1);

不会修改原对象,而是返回一个新的 LocalDate

正确:

date = date.plusDays(1);

或者:

LocalDate tomorrow =
        date.plusDays(1);

所以一定要形成条件反射:

plusXxx()minusXxx()withXxx() 大多数情况下都会返回一个新对象。

六、DateTimeFormatter:格式化

DateTimeFormatter 负责:

时间对象 ⇄ String

这是 Java 日期时间开发必须熟练掌握的工具。

6.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | ------------ | ------------------- | ----------------------- | ------------------------------------------------ | ---------------------- | | 49 | 格式器 | DateTimeFormatter | ofPattern() | String pattern | 根据模式创建格式器 | | 50 | 格式化字符串 | String | format() | TemporalAccessor temporal | 时间对象转字符串 | | 51 | 日期 | LocalDate | LocalDate.parse() | CharSequence text, DateTimeFormatter formatter | 按指定格式解析日期 | | 52 | 日期时间 | LocalDateTime | LocalDateTime.parse() | CharSequence text, DateTimeFormatter formatter | 按指定格式解析日期时间 |

6.2 常用格式符号

| 格式字符 | 含义 | 示例 | | -------- | ------------- | ------ | | yyyy | 年 | 2026 | | MM | 月 | 09 | | dd | 日 | 17 | | HH | 24 小时制小时 | 18 | | hh | 12 小时制小时 | 06 | | mm | 分钟 | 30 | | ss | 秒 | 45 |

必须注意:

MM = 月
mm = 分钟

HH = 24 小时制
hh = 12 小时制

6.3 时间对象 → String

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class FormatterDemo {

    public static void main(String[] args) {

        LocalDateTime now =
                LocalDateTime.now();

        DateTimeFormatter formatter =
                DateTimeFormatter.ofPattern(
                        "yyyy-MM-dd HH:mm:ss"
                );

        String result =
                now.format(formatter);

        System.out.println(result);
    }
}

结果:

2026-09-17 10:30:21

6.4 String → LocalDateTime

String str =
        "2026-09-17 18:30:00";

DateTimeFormatter formatter =
        DateTimeFormatter.ofPattern(
                "yyyy-MM-dd HH:mm:ss"
        );

LocalDateTime time =
        LocalDateTime.parse(
                str,
                formatter
        );

System.out.println(time);

必须记住这条转换链:

String
  ↓ parse()
LocalDateTime

LocalDateTime
  ↓ format()
String

七、Period:日期之间相差多久

Period 主要用于:

年
月
日

适合:

年龄
入职年限
两个日期之间相差多少年月日

7.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | ------ | ---------- | ------------- | ---------------------------------------- | -------------------------- | | 53 | 日期差 | Period | between() | LocalDate startDate, LocalDate endDate | 计算两个日期之间的年月日差 | | 54 | 年 | int | getYears() | 无 | 获取年份部分 | | 55 | 月 | int | getMonths() | 无 | 获取月份部分 | | 56 | 日 | int | getDays() | 无 | 获取天数部分 |

7.2 示例:计算年龄

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

public class PeriodDemo {

    public static void main(String[] args) {

        LocalDate birthday =
                LocalDate.of(2004, 5, 20);

        LocalDate today =
                LocalDate.now();

        Period period =
                Period.between(
                        birthday,
                        today
                );

        System.out.println(
                period.getYears()
        );

        System.out.println(
                period.getMonths()
        );

        System.out.println(
                period.getDays()
        );
    }
}

八、Duration:精确时间间隔

Duration 主要计算:

时
分
秒
纳秒

适合:

任务耗时
视频时长
接口执行时间
登录持续时间
订单倒计时

8.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | -------- | ---------- | -------------- | ------------------------------------------------ | --------------------- | | 57 | 时间差 | Duration | between() | Temporal startInclusive, Temporal endExclusive | 计算时间间隔 | | 58 | Duration | Duration | ofSeconds() | long seconds | 根据秒数创建 Duration | | 59 | 小时数 | long | toHours() | 无 | 转换为完整小时数 | | 60 | 分钟数 | long | toMinutes() | 无 | 转换为完整分钟数 | | 61 | 天数 | long | toDays() | 无 | 转换为完整天数 | | 62 | 秒数 | long | getSeconds() | 无 | 获取总秒数 |

8.2 示例

import java.time.Duration;
import java.time.LocalDateTime;

public class DurationDemo {

    public static void main(String[] args) {

        LocalDateTime start =
                LocalDateTime.of(
                        2026, 9, 17,
                        10, 0
                );

        LocalDateTime end =
                LocalDateTime.of(
                        2026, 9, 17,
                        12, 30
                );

        Duration duration =
                Duration.between(
                        start,
                        end
                );

        System.out.println(
                duration.toHours()
        );

        System.out.println(
                duration.toMinutes()
        );

        System.out.println(
                duration.getSeconds()
        );
    }
}

结果:

2
150
9000

注意:

duration.toHours();

返回的是完整小时数

所以:

2小时30分钟

得到:

2

而不是:

2.5

九、ChronoUnit:直接计算时间单位差

ChronoUnit 非常适合直接问:

相差多少天?
相差多少小时?
相差多少分钟?

9.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | ------ | ---------- | ------------------------------ | ------------------------------ | ---------- | | 63 | 差值 | long | ChronoUnit.YEARS.between() | Temporal start, Temporal end | 计算年份差 | | 64 | 差值 | long | ChronoUnit.MONTHS.between() | Temporal start, Temporal end | 计算月份差 | | 65 | 差值 | long | ChronoUnit.DAYS.between() | Temporal start, Temporal end | 计算天数差 | | 66 | 差值 | long | ChronoUnit.HOURS.between() | Temporal start, Temporal end | 计算小时差 | | 67 | 差值 | long | ChronoUnit.MINUTES.between() | Temporal start, Temporal end | 计算分钟差 | | 68 | 差值 | long | ChronoUnit.SECONDS.between() | Temporal start, Temporal end | 计算秒数差 |

9.2 示例

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class ChronoUnitDemo {

    public static void main(String[] args) {

        LocalDate start =
                LocalDate.of(
                        2026,
                        9,
                        1
                );

        LocalDate end =
                LocalDate.of(
                        2026,
                        9,
                        17
                );

        long days =
                ChronoUnit.DAYS.between(
                        start,
                        end
                );

        System.out.println(days);
    }
}

结果:

16

计算分钟:

long minutes =
        ChronoUnit.MINUTES.between(
                LocalDateTime.of(
                        2026, 9, 17,
                        10, 0
                ),
                LocalDateTime.of(
                        2026, 9, 17,
                        12, 30
                )
        );

System.out.println(minutes);

结果:

150

十、Period、Duration、ChronoUnit 怎么选

| 场景 | 推荐 API | | -------------- | -------------------- | | 22 年 3 个月 | Period | | 2 小时 30 分钟 | Duration | | 相差多少天 | ChronoUnit.DAYS | | 相差多少分钟 | ChronoUnit.MINUTES | | 计算年龄 | Period | | 接口执行时间 | Duration | | 两日期相差天数 | ChronoUnit |

形成这个理解:

Period
→ 人类日期单位:年月日

Duration
→ 精确时间间隔:时分秒

ChronoUnit
→ 直接得到某个单位的数字差值

十一、Instant:时间戳与绝对时间点

Instant 表示:

UTC 时间线上的一个绝对时间点。

例如:

Instant.now();

可能得到:

2026-09-17T02:30:00Z

其中:

Z = UTC

11.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | ---------- | ---------- | ---------------- | ------------------- | -------------------- | | 69 | 当前时间点 | Instant | now() | 无 | 获取当前 UTC 时间点 | | 70 | Instant | Instant | ofEpochMilli() | long epochMilli | 毫秒时间戳转 Instant | | 71 | 毫秒时间戳 | long | toEpochMilli() | 无 | Instant 转毫秒时间戳 | | 72 | 新 Instant | Instant | plusSeconds() | long secondsToAdd | 增加秒数 | | 73 | 是否更早 | boolean | isBefore() | Instant other | 比较绝对时间点 | | 74 | 是否更晚 | boolean | isAfter() | Instant other | 比较绝对时间点 |

11.2 示例

import java.time.Instant;

public class InstantDemo {

    public static void main(String[] args) {

        Instant now =
                Instant.now();

        System.out.println(now);

        long timestamp =
                now.toEpochMilli();

        System.out.println(timestamp);

        Instant instant =
                Instant.ofEpochMilli(
                        timestamp
                );

        System.out.println(instant);

        Instant future =
                now.plusSeconds(60);

        System.out.println(
                now.isBefore(future)
        );

        System.out.println(
                future.isAfter(now)
        );
    }
}

十二、ZoneId:时区

世界各地时区不同,例如:

北京:UTC+8
东京:UTC+9
纽约:UTC-4 / UTC-5
洛杉矶:UTC-7 / UTC-8

LocalDateTime 本身并不知道自己的时区。

例如:

LocalDateTime.of(
        2026,
        9,
        17,
        10,
        0
);

只表示:

2026-09-17 10:00

但不知道它究竟是:

北京 10:00
东京 10:00
纽约 10:00

所以需要:

ZoneId

12.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | ------------ | ------------- | ----------------------- | --------------- | ----------------------- | | 75 | 系统默认时区 | ZoneId | systemDefault() | 无 | 获取 JVM 默认时区 | | 76 | 指定时区 | ZoneId | of() | String zoneId | 根据地区名称获取时区 | | 77 | 所有时区 ID | Set<String> | getAvailableZoneIds() | 无 | 获取 Java 支持的时区 ID |

12.2 示例

import java.time.ZoneId;

public class ZoneIdDemo {

    public static void main(String[] args) {

        ZoneId system =
                ZoneId.systemDefault();

        System.out.println(system);

        ZoneId shanghai =
                ZoneId.of(
                        "Asia/Shanghai"
                );

        ZoneId tokyo =
                ZoneId.of(
                        "Asia/Tokyo"
                );

        ZoneId newYork =
                ZoneId.of(
                        "America/New_York"
                );

        System.out.println(shanghai);
        System.out.println(tokyo);
        System.out.println(newYork);
    }
}

实际开发更推荐:

Asia/Shanghai
America/New_York
Europe/London

而不是仅写:

GMT+8

因为地区型时区能够正确处理:

夏令时 DST
历史时区变化
地区时区规则

十三、ZonedDateTime:日期时间 + 时区

可以简单理解为:

LocalDateTime + ZoneId

例如:

2026-09-17T10:00+08:00[Asia/Shanghai]

13.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | -------------- | --------------- | ----------------------- | ------------------------------------------ | ------------------------ | | 78 | 当前带时区时间 | ZonedDateTime | now() | 无 | 获取当前系统时区日期时间 | | 79 | 带时区时间 | ZonedDateTime | of() | LocalDateTime localDateTime, ZoneId zone | 将日期时间绑定时区 | | 80 | 新时区时间 | ZonedDateTime | withZoneSameInstant() | ZoneId zone | 保持同一瞬间转换时区 | | 81 | 时区 | ZoneId | getZone() | 无 | 获取时区 | | 82 | 绝对时间点 | Instant | toInstant() | 无 | 转换为 Instant | | 83 | 本地日期时间 | LocalDateTime | toLocalDateTime() | 无 | 去掉时区信息 |

13.2 示例:北京时间转换为纽约时间

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZonedDateTimeDemo {

    public static void main(String[] args) {

        LocalDateTime local =
                LocalDateTime.of(
                        2026,
                        9,
                        17,
                        20,
                        0
                );

        ZoneId china =
                ZoneId.of(
                        "Asia/Shanghai"
                );

        ZonedDateTime chinaTime =
                local.atZone(china);

        System.out.println(chinaTime);

        ZonedDateTime newYorkTime =
                chinaTime.withZoneSameInstant(
                        ZoneId.of(
                                "America/New_York"
                        )
                );

        System.out.println(newYorkTime);
    }
}

其中:

withZoneSameInstant()

表示:

保持现实世界中的同一个瞬间不变,只改变观察这个时间点所使用的时区。

十四、OffsetDateTime:日期时间 + UTC 偏移量

OffsetDateTime 例如:

2026-09-17T10:30:00+08:00

它记录:

日期
+
时间
+
UTC 偏移量

ZonedDateTime 的区别:

OffsetDateTime
2026-09-17T10:00+08:00

ZonedDateTime
2026-09-17T10:00+08:00[Asia/Shanghai]

前者只知道:

+08:00

后者知道:

Asia/Shanghai

14.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | ---------------- | ---------------- | ------------- | -------- | ----------------------------- | | 84 | 当前偏移日期时间 | OffsetDateTime | now() | 无 | 获取当前日期时间和 UTC 偏移量 | | 85 | 绝对时间点 | Instant | toInstant() | 无 | 转换为 Instant |

14.2 示例

import java.time.Instant;
import java.time.OffsetDateTime;

public class OffsetDateTimeDemo {

    public static void main(String[] args) {

        OffsetDateTime now =
                OffsetDateTime.now();

        System.out.println(now);

        Instant instant =
                now.toInstant();

        System.out.println(instant);
    }
}

十五、TemporalAdjusters:日期规则计算

真实业务中经常需要:

本月第一天
本月最后一天
下个月第一天
今年最后一天
下一个星期一

这时可以使用:

TemporalAdjusters

15.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | ------ | ------------------ | ----------------------- | --------------------- | -------------------- | | 86 | 调整器 | TemporalAdjuster | firstDayOfMonth() | 无 | 本月第一天 | | 87 | 调整器 | TemporalAdjuster | lastDayOfMonth() | 无 | 本月最后一天 | | 88 | 调整器 | TemporalAdjuster | firstDayOfNextMonth() | 无 | 下个月第一天 | | 89 | 调整器 | TemporalAdjuster | firstDayOfYear() | 无 | 今年第一天 | | 90 | 调整器 | TemporalAdjuster | lastDayOfYear() | 无 | 今年最后一天 | | 91 | 调整器 | TemporalAdjuster | next() | DayOfWeek dayOfWeek | 下一个指定星期 | | 92 | 调整器 | TemporalAdjuster | nextOrSame() | DayOfWeek dayOfWeek | 当天或下一个指定星期 | | 93 | 调整器 | TemporalAdjuster | previous() | DayOfWeek dayOfWeek | 上一个指定星期 |

15.2 示例

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

public class AdjusterDemo {

    public static void main(String[] args) {

        LocalDate today =
                LocalDate.now();

        LocalDate firstDay =
                today.with(
                        TemporalAdjusters
                                .firstDayOfMonth()
                );

        LocalDate lastDay =
                today.with(
                        TemporalAdjusters
                                .lastDayOfMonth()
                );

        LocalDate nextMonday =
                today.with(
                        TemporalAdjusters.next(
                                DayOfWeek.MONDAY
                        )
                );

        System.out.println(firstDay);
        System.out.println(lastDay);
        System.out.println(nextMonday);
    }
}

十六、YearMonth:年月

YearMonth 很适合表示:

账单月份
工资月份
统计月份
某个月有多少天

例如:

2026-09

16.1 API 表

| ID | 返回值 | 返回值类型 | 方法名 | 参数列表 | 作用 | | -- | -------- | ----------- | ----------------- | --------------------- | ---------------- | | 94 | 指定年月 | YearMonth | of() | int year, int month | 创建指定年月 | | 95 | 月份天数 | int | lengthOfMonth() | 无 | 获取该月天数 | | 96 | 月末日期 | LocalDate | atEndOfMonth() | 无 | 获取该月最后一天 |

16.2 示例

import java.time.YearMonth;

public class YearMonthDemo {

    public static void main(String[] args) {

        YearMonth month =
                YearMonth.of(
                        2026,
                        2
                );

        System.out.println(month);

        System.out.println(
                month.lengthOfMonth()
        );

        System.out.println(
                month.atEndOfMonth()
        );
    }
}

结果:

2026-02
28
2026-02-28

十七、DayOfWeek 与 Month

DayOfWeekMonth 都属于枚举类型。

例如:

DayOfWeek.MONDAY
Month.JANUARY

示例:

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

public class EnumDemo {

    public static void main(String[] args) {

        LocalDate date =
                LocalDate.of(
                        2026,
                        9,
                        17
                );

        DayOfWeek week =
                date.getDayOfWeek();

        Month month =
                date.getMonth();

        System.out.println(week);
        System.out.println(month);

        if (date.getDayOfWeek()
                == DayOfWeek.SATURDAY) {

            System.out.println("星期六");
        }
    }
}

十八、日期时间类型转换关系

整个 Java 日期时间体系最重要的一张图:

               LocalDate
             2026-09-17
                  │
                  │ atStartOfDay()
                  ↓
            LocalDateTime
      2026-09-17T10:30:00
                  │
                  │ atZone()
                  ↓
           ZonedDateTime
2026-09-17T10:30+08:00[Asia/Shanghai]
                  │
                  │ toInstant()
                  ↓
               Instant
          全球绝对时间点
                  │
                  │ toEpochMilli()
                  ↓
                long
              时间戳

字符串转换关系:

String
 ↑      ↓
format  parse
 ↑      ↓
LocalDate / LocalDateTime

理解这张图,比死记大量方法更重要。

十九、Java Web 中常见日期字段

例如用户实体:

public class User {

    private Long id;

    private String username;

    private LocalDate birthday;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;
}

对应关系:

birthday
→ LocalDate

createTime
→ LocalDateTime

updateTime
→ LocalDateTime

如果系统涉及:

跨国业务
全球服务器
分布式系统
不同地区用户

则应进一步考虑:

Instant
OffsetDateTime
ZonedDateTime

二十、MySQL 与 Java 日期类型映射

常见对应关系:

| Java 类型 | MySQL 类型 | 常见业务 | | --------------- | ----------------------------- | ------------------ | | LocalDate | DATE | 生日、日期 | | LocalTime | TIME | 营业时间 | | LocalDateTime | DATETIME | 创建时间、更新时间 | | Instant | TIMESTAMP/ UTC 时间存储方案 | 跨时区绝对时间 |

例如:

private LocalDateTime createTime;

数据库:

create_time DATETIME

在 Spring Boot + MyBatis 中通常可以直接完成类型映射。

二十一、旧日期 API:Date、Calendar、SimpleDateFormat

旧 Java 项目经常看到:

java.util.Date
java.util.Calendar
java.text.SimpleDateFormat

现代 Java 更推荐:

Date
↓
Instant

Calendar
↓
LocalDateTime / ZonedDateTime

SimpleDateFormat
↓
DateTimeFormatter

这些旧 API 目前主要达到:

能看懂、能维护、会转换即可。

二十二、Date 与现代 API 转换

22.1 Date → Instant

Date date =
        new Date();

Instant instant =
        date.toInstant();

22.2 Instant → Date

Instant instant =
        Instant.now();

Date date =
        Date.from(instant);

22.3 Date → LocalDateTime

Date date =
        new Date();

LocalDateTime dateTime =
        LocalDateTime.ofInstant(
                date.toInstant(),
                ZoneId.systemDefault()
        );

二十三、为什么不推荐继续主学 SimpleDateFormat

旧代码:

SimpleDateFormat formatter =
        new SimpleDateFormat(
                "yyyy-MM-dd HH:mm:ss"
        );

一个重要问题:

SimpleDateFormat 不是线程安全的。

在 Web 应用中,如果多个线程共享同一个实例,可能发生线程安全问题。

现代 Java:

DateTimeFormatter formatter =
        DateTimeFormatter.ofPattern(
                "yyyy-MM-dd HH:mm:ss"
        );

DateTimeFormatter

不可变
线程安全

所以现代 Java 项目应优先使用:

java.time

二十四、看到业务应该立即想到什么类型

生日

LocalDate

每天 18:00

LocalTime

2026-09-17 18:30

LocalDateTime

全球唯一时间点

Instant

北京 / 东京 / 纽约时区

ZoneId

带时区日期时间

ZonedDateTime

REST API 中携带 UTC 偏移量

OffsetDateTime

日期格式转换

DateTimeFormatter

22 岁 3 个月

Period

运行了 2 小时 30 分钟

Duration

相差 16 天

ChronoUnit.DAYS

二十五、96 个核心 API 总结

本文共整理:

96 个核心日期时间 API / 常用调用

可以按优先级学习。

第一优先级:Java SE 必须熟练

LocalDate
LocalTime
LocalDateTime
DateTimeFormatter
Period
Duration
ChronoUnit

第二优先级:Java Web 必须理解并逐渐熟练

Instant
ZoneId
ZonedDateTime
OffsetDateTime

第三优先级:业务增强

TemporalAdjusters
YearMonth
DayOfWeek
Month

兼容旧代码

Date
Calendar
SimpleDateFormat

二十六、综合案例:订单支付超时系统

需求:

用户创建订单后,必须在 30 分钟内完成支付,否则订单自动过期。同时需要展示订单创建时间、过期时间以及剩余支付分钟数。

这个案例会综合使用:

LocalDateTime
plusMinutes()
isAfter()
Duration
DateTimeFormatter
format()

26.1 完整代码

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class OrderTimeDemo {

    public static void main(String[] args) {

        // =========================
        // 1. 创建订单
        // =========================

        LocalDateTime createTime =
                LocalDateTime.now();


        // =========================
        // 2. 30 分钟后过期
        // =========================

        LocalDateTime expireTime =
                createTime.plusMinutes(30);


        // =========================
        // 3. 获取当前时间
        // =========================

        LocalDateTime now =
                LocalDateTime.now();


        // =========================
        // 4. 判断订单是否过期
        // =========================

        boolean expired =
                now.isAfter(expireTime);


        // =========================
        // 5. 计算剩余时间
        // =========================

        long remainingMinutes = 0;

        if (!expired) {

            Duration duration =
                    Duration.between(
                            now,
                            expireTime
                    );

            remainingMinutes =
                    duration.toMinutes();
        }


        // =========================
        // 6. 创建格式器
        // =========================

        DateTimeFormatter formatter =
                DateTimeFormatter.ofPattern(
                        "yyyy-MM-dd HH:mm:ss"
                );


        // =========================
        // 7. 日期时间转字符串
        // =========================

        String createTimeText =
                createTime.format(formatter);

        String expireTimeText =
                expireTime.format(formatter);


        // =========================
        // 8. 输出订单信息
        // =========================

        System.out.println(
                "订单创建时间:" +
                createTimeText
        );

        System.out.println(
                "订单过期时间:" +
                expireTimeText
        );

        if (expired) {

            System.out.println(
                    "订单状态:已过期"
            );

        } else {

            System.out.println(
                    "订单状态:待支付"
            );

            System.out.println(
                    "剩余时间:" +
                    remainingMinutes +
                    "分钟"
            );
        }
    }
}

二十七、综合案例执行链

上面的程序,本质上就是:

LocalDateTime.now()
        ↓
获得订单创建时间

plusMinutes(30)
        ↓
计算过期时间

LocalDateTime.now()
        ↓
获得当前时间

isAfter()
        ↓
判断订单是否过期

Duration.between()
        ↓
计算剩余时间

DateTimeFormatter
        ↓
定义显示格式

format()
        ↓
转换成前端需要的字符串

这就是日期 API 在真实业务中的使用方式。

二十八、升级到真实 Spring Boot 业务

未来订单实体可能是:

public class Order {

    private Long id;

    private LocalDateTime createTime;

    private LocalDateTime expireTime;

    private LocalDateTime payTime;
}

创建订单:

LocalDateTime now =
        LocalDateTime.now();

order.setCreateTime(now);

order.setExpireTime(
        now.plusMinutes(30)
);

判断订单是否过期:

if (LocalDateTime.now()
        .isAfter(
                order.getExpireTime()
        )) {

    order.setStatus(
            "CANCELLED"
    );
}

支付:

order.setPayTime(
        LocalDateTime.now()
);

因此日期时间 API 的本质并不是:

“做日期计算题”。

而是:

描述业务世界中的时间状态、时间顺序和时间规则。

二十九、最终知识结构

最后把 Java 日期 API 压缩成这一张认知图:

日期
└── LocalDate

时间
└── LocalTime

日期 + 时间
└── LocalDateTime

格式转换
└── DateTimeFormatter

年月日差
└── Period

时分秒间隔
└── Duration

指定单位差值
└── ChronoUnit

绝对时间点
└── Instant

时区
└── ZoneId

日期时间 + 地区时区
└── ZonedDateTime

日期时间 + UTC偏移量
└── OffsetDateTime

复杂日期规则
└── TemporalAdjusters

年月
└── YearMonth

星期
└── DayOfWeek

月份
└── Month

真正掌握的标准不是能背出 96 个 API,而是面对业务需求时能够立即判断:

需要什么日期类型?
需要什么时间类型?
是否涉及时区?
是否需要格式化?
是否需要计算时间差?
是否需要比较先后?
是否需要转成时间戳?

当这些问题能够自然映射到对应 API 时,Java 日期时间体系就真正进入了你的长期记忆。

三十、自测题

学完本文后,可以不看答案完成下面几个任务:

  1. 获取今天日期。
  2. 获取当前日期时间。
  3. 创建 2026-12-01
  4. 计算 100 天后的日期。
  5. 判断两个日期谁更早。
  6. 2026-09-17 18:30:00 转成 LocalDateTime
  7. LocalDateTime 格式化成字符串。
  8. 根据生日计算年龄。
  9. 计算两个时间之间相差多少分钟。
  10. 获取本月最后一天。
  11. 将北京时间转换成纽约时间。
  12. Instant 转换成毫秒时间戳。
  13. 编写一个 30 分钟订单过期逻辑。

如果这些任务能够独立完成,Java 日期时间 API 的核心部分就已经掌握。