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)
);
}
}
五、日期时间对象为什么都是不可变对象
LocalDate、LocalTime、LocalDateTime 等核心类都是不可变对象。
这和 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
DayOfWeek 和 Month 都属于枚举类型。
例如:
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 日期时间体系就真正进入了你的长期记忆。
三十、自测题
学完本文后,可以不看答案完成下面几个任务:
- 获取今天日期。
- 获取当前日期时间。
- 创建
2026-12-01。 - 计算 100 天后的日期。
- 判断两个日期谁更早。
- 把
2026-09-17 18:30:00转成LocalDateTime。 - 把
LocalDateTime格式化成字符串。 - 根据生日计算年龄。
- 计算两个时间之间相差多少分钟。
- 获取本月最后一天。
- 将北京时间转换成纽约时间。
- 将
Instant转换成毫秒时间戳。 - 编写一个 30 分钟订单过期逻辑。
如果这些任务能够独立完成,Java 日期时间 API 的核心部分就已经掌握。