Skip to content

日期时间

概述

Java 中处理日期时间经历过两套 API:早期的 java.util.DateCalendar 存在可读性差、非线程安全、时区与「日历」概念混在一起等问题;自 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时间间隔(时/分/秒/纳秒)耗时、相差几秒

提示

「本地」表示不包含时区信息,即「你所在时区的日期/时间」,不参与时区换算。需要跨时区时请用 ZonedDateTimeInstant


基本用法

获取当前日期时间

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 的互转

若必须与 DateCalendar 交互,可通过 InstantZonedDateTime 做桥梁。

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.timeLocalDateLocalTimeLocalDateTimeZonedDateTimeInstantDateTimeFormatterPeriodDuration 等,避免再写 new Date()Calendar.getInstance() 的业务逻辑。
  • 明确「要的是日期、时间还是时刻」:只要日期用 LocalDate,要时间点(含时区)用 ZonedDateTimeInstant,避免用 LocalDateTime 表达「某个时区的时刻」导致歧义。
  • 格式化与解析要匹配ofPattern 里的字母(如 yyyyMMddHHmmss)需与字符串一致;大小写含义不同(如 MM 月、mm 分)。
  • 线程安全:java.time 中的类型多为不可变,多线程共享同一实例是安全的;旧 API 的 Calendar 等则非线程安全。

注意

不要用 LocalDateTime 存储或传递「带时区的真实时刻」(如会议开始时间)。应用 ZonedDateTimeInstant,否则跨时区时会出错。


相关链接

基于 VitePress 构建