日期时间
概述
Java 中处理日期时间经历过两套 API:早期的 java.util.Date、Calendar 存在可读性差、非线程安全、时区与「日历」概念混在一起等问题;自 Java 8 起引入的 java.time 包(JSR 310)则提供不可变、线程安全、语义清晰的类型,是当前推荐的写法。本文先简要说明旧 API 的局限,再重点介绍 java.time 的常用类与用法。
旧 API 简要说明(了解即可)
Date 与 Calendar 的局限
- java.util.Date:名义上是「日期时间」,实际底层是「自 1970-01-01 00:00:00 UTC 以来的毫秒数」,且很多方法已弃用(deprecated),不推荐在新代码中使用。
- java.util.Calendar:可做日历计算与简单格式化,但 API 繁琐(月份从 0 开始等)、非线程安全,且与「时间点」「时区」混在一起,容易出错。
新项目应优先使用 java.time;仅在与旧代码或旧库交互时再接触 Date/Calendar。
Java 8+ 日期时间 API(java.time)
核心类型一览
| 类型 | 含义 | 典型用途 |
|---|---|---|
| LocalDate | 本地日期(无时间、无时区) | 生日、截止日期 |
| LocalTime | 本地时间(无日期、无时区) | 营业时间、闹钟 |
| LocalDateTime | 本地日期+时间(无时区) | 日志时间、预约时间(不关心时区时) |
| ZonedDateTime | 带时区的日期时间 | 会议时间、跨时区展示 |
| Instant | 时间轴上的瞬时点(UTC) | 时间戳、与旧 API 互转 |
| Period | 日期间隔(年/月/日) | 年龄、相差几天 |
| Duration | 时间间隔(时/分/秒/纳秒) | 耗时、相差几秒 |
提示
「本地」表示不包含时区信息,即「你所在时区的日期/时间」,不参与时区换算。需要跨时区时请用 ZonedDateTime 或 Instant。
基本用法
获取当前日期时间
java
import java.time.*;
// 当前日期(仅年月日)
LocalDate today = LocalDate.now();
// 当前时间(仅时分秒)
LocalTime nowTime = LocalTime.now();
// 当前日期+时间(本地,无时区)
LocalDateTime now = LocalDateTime.now();
// 当前时刻(UTC)
Instant instant = Instant.now();
// 当前日期时间(带系统默认时区)
ZonedDateTime zonedNow = ZonedDateTime.now();指定日期时间创建
java
// 指定日期:2025年2月7日
LocalDate date = LocalDate.of(2025, 2, 7);
// 指定时间:14时30分
LocalTime time = LocalTime.of(14, 30);
// 指定日期+时间
LocalDateTime dateTime = LocalDateTime.of(2025, 2, 7, 14, 30);
// 从 LocalDate + LocalTime 组合
LocalDateTime combined = LocalDateTime.of(date, time);解析字符串
使用 DateTimeFormatter 将字符串解析为日期时间类型;反之也可将日期时间格式化为字符串。
java
import java.time.format.DateTimeFormatter;
// 标准日期格式:yyyy-MM-dd
LocalDate d = LocalDate.parse("2025-02-07");
// 自定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate d2 = LocalDate.parse("2025年02月07日", formatter);
// 日期时间格式
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = LocalDateTime.parse("2025-02-07 14:30:00", dtf);格式化为字符串
java
LocalDate date = LocalDate.of(2025, 2, 7);
// 默认格式:2025-02-07
String s1 = date.toString();
// 自定义格式
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy/MM/dd");
String s2 = date.format(f); // "2025/02/07"使用示例
示例 1:本地日期与简单计算
java
import java.time.LocalDate;
import java.time.Period;
// 生日与今天
LocalDate birthday = LocalDate.of(1990, 5, 20);
LocalDate today = LocalDate.now();
// 计算年龄(相差多少年/月/日)
Period period = Period.between(birthday, today);
System.out.println("年龄: " + period.getYears() + " 岁");
// 加减天数
LocalDate nextWeek = today.plusDays(7);
LocalDate lastMonth = today.minusMonths(1);示例 2:格式化与解析(常用模式)
java
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// 格式:yyyy-MM-dd HH:mm:ss
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
String text = now.format(formatter); // 日期时间 -> 字符串
LocalDateTime parsed = LocalDateTime.parse(text, formatter); // 字符串 -> 日期时间示例 3:时区与 Instant(跨时区、时间戳)
java
import java.time.*;
// 当前时刻(UTC)
Instant instant = Instant.now();
// 系统默认时区的「当前日期时间」
ZonedDateTime zoned = ZonedDateTime.now();
// 指定时区:上海
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZonedDateTime inShanghai = ZonedDateTime.now(shanghai);
// Instant 与 ZonedDateTime 互转
Instant fromZoned = inShanghai.toInstant();
ZonedDateTime fromInstant = instant.atZone(shanghai);与旧 API 的互转
若必须与 Date 或 Calendar 交互,可通过 Instant 或 ZonedDateTime 做桥梁。
java
import java.time.*;
import java.util.Date;
// Date -> Instant -> LocalDateTime(使用系统默认时区)
Date oldDate = new Date();
Instant instant = oldDate.toInstant();
LocalDateTime localDt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
// LocalDateTime -> ZonedDateTime -> Instant -> Date
ZonedDateTime zoned = localDt.atZone(ZoneId.systemDefault());
Date date = Date.from(zoned.toInstant());注意
与 Date 互转时务必明确时区(如 ZoneId.systemDefault() 或 ZoneId.of("Asia/Shanghai")),否则容易产生「差一天」或「差几小时」的问题。
注意事项
- 新代码优先使用 java.time:
LocalDate、LocalTime、LocalDateTime、ZonedDateTime、Instant、DateTimeFormatter、Period、Duration等,避免再写new Date()或Calendar.getInstance()的业务逻辑。 - 明确「要的是日期、时间还是时刻」:只要日期用 LocalDate,要时间点(含时区)用 ZonedDateTime 或 Instant,避免用
LocalDateTime表达「某个时区的时刻」导致歧义。 - 格式化与解析要匹配:
ofPattern里的字母(如yyyy、MM、dd、HH、mm、ss)需与字符串一致;大小写含义不同(如MM月、mm分)。 - 线程安全:java.time 中的类型多为不可变,多线程共享同一实例是安全的;旧 API 的
Calendar等则非线程安全。
注意
不要用 LocalDateTime 存储或传递「带时区的真实时刻」(如会议开始时间)。应用 ZonedDateTime 或 Instant,否则跨时区时会出错。
相关链接
- 常用类(Object、包装类、Math) — 同属核心 API
- 正则表达式 — 若需用正则校验日期格式
- Oracle Java 教程 - Date Time
- Java 17 API - java.time