在 Java 开发中,日期时间处理是必备核心技能,但早期的
java.util.Date、java.util.Calendar存在线程不安全、设计混乱、API 难用等痛点,极易引发生产 bug。Java 8 正式推出了全新的日期时间 API(java.time 包),遵循 ISO-8601 标准,线程安全、设计清晰、功能强大,彻底解决了旧 API 的弊端。
本文将从基础概念、核心类使用、格式化解析、日期计算、时区处理、新旧 API 转换六大维度,带你彻底吃透 Java 日期时间 API,轻松应对所有开发场景。
一、旧日期 API 的痛点(为什么要弃用 Date/Calendar)
在学习新 API 之前,先了解旧 API 的缺陷,才能明白新 API 的设计优势:
- 线程不安全:
Date、Calendar、SimpleDateFormat都是可变类,多线程环境下使用会引发数据错乱、格式化异常。 - 设计混乱:月份从 0 开始(1 月 = 0)、年份从 1900 开始,API 命名不规范,可读性极差。
- 功能缺失:缺乏对日期时间的精细化操作(如计算两个日期相差天数、获取下周几),代码冗余。
- 时区支持差:时区处理繁琐,容易出错。
反例(旧 API 坑点):
java
运行
// 旧API创建2026年3月20日,月份必须写2(0=1月)
Date date = new Date(2026-1900, 2, 20);
// SimpleDateFormat多线程下直接报错
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
正是这些缺陷,Java 8 推出了不可变、线程安全、语义清晰的
java.time核心 API。二、Java 8+ 日期时间 API 核心类概览
新 API 所有类都在
java.time包下,核心类全是不可变类(final 修饰),线程安全,分工明确:表格
| 类名 | 作用 | 格式示例 |
|---|---|---|
LocalDate |
本地日期(年月日) | 2026-03-20 |
LocalTime |
本地时间(时分秒毫秒) | 15:30:25.123 |
LocalDateTime |
本地日期时间(年月日时分秒) | 2026-03-20T15:30:25 |
ZonedDateTime |
带时区的日期时间 | 2026-03-20T15:30:25+08:00[Asia/Shanghai] |
Instant |
时间戳(UTC 时间戳) | 1742446225123 |
Duration |
时间间隔(时分秒毫秒) | 2 天 3 小时 15 分钟 |
Period |
日期间隔(年月日) | 1 年 2 个月 3 天 |
DateTimeFormatter |
日期格式化 / 解析(线程安全) | yyyy-MM-dd HH:mm:ss |
三、核心 API 基础使用(必学)
1. LocalDate:本地日期(仅年月日)
LocalDate表示不带时区的本地日期,适用于生日、节假日、合同日期等场景。java
运行
import java.time.LocalDate;
public class LocalDateDemo {
public static void main(String[] args) {
// 1. 获取当前日期(系统默认时区)
LocalDate now = LocalDate.now();
System.out.println("当前日期:" + now);
// 2. 指定日期(年、月、日),月份直接写数字,无需减1
LocalDate指定日期 = LocalDate.of(2026, 3, 20);
System.out.println("指定日期:" + 指定日期);
// 3. 获取日期的年、月、日、星期
int year = now.getYear(); // 年
int month = now.getMonthValue(); // 月
int day = now.getDayOfMonth(); // 日
// 星期:MONDAY、TUESDAY...SUNDAY
String week = now.getDayOfWeek().toString();
// 4. 日期判断:是否闰年、是否在指定日期之前/之后
boolean isLeapYear = now.isLeapYear(); // 是否闰年
boolean isBefore = now.isBefore(LocalDate.of(2025, 1, 1));
}
}
2. LocalTime:本地时间(仅时分秒)
LocalTime表示不带时区的本地时间,精确到纳秒,适用于打卡时间、定时任务等场景。java
运行
import java.time.LocalTime;
public class LocalTimeDemo {
public static void main(String[] args) {
// 1. 获取当前时间
LocalTime now = LocalTime.now();
System.out.println("当前时间:" + now);
// 2. 指定时间(时、分、秒、纳秒)
LocalTime 指定时间 = LocalTime.of(15, 30, 25);
System.out.println("指定时间:" + 指定时间);
// 3. 获取时间的时、分、秒
int hour = now.getHour();
int minute = now.getMinute();
int second = now.getSecond();
}
}
3. LocalDateTime:本地日期时间(最常用)
LocalDateTime = LocalDate + LocalTime,是开发中最常用的类,表示不带时区的日期时间。java
运行
import java.time.LocalDateTime;
public class LocalDateTimeDemo {
public static void main(String[] args) {
// 1. 获取当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("当前日期时间:" + now);
// 2. 指定日期时间
LocalDateTime 指定时间 = LocalDateTime.of(2026, 3, 20, 15, 30, 25);
System.out.println("指定日期时间:" + 指定时间);
// 3. 拆分:转LocalDate / LocalTime
LocalDate date = now.toLocalDate();
LocalTime time = now.toLocalTime();
}
}
4. Instant:时间戳(UTC 标准)
Instant是UTC 时区的时间戳,对应旧 API 的System.currentTimeMillis(),适用于日志记录、分布式系统时间统一。java
运行
import java.time.Instant;
public class InstantDemo {
public static void main(String[] args) {
// 1. 获取当前时间戳(UTC时间)
Instant now = Instant.now();
System.out.println("当前UTC时间戳:" + now);
// 2. 转毫秒值(等价于System.currentTimeMillis())
long milli = now.toEpochMilli();
System.out.println("毫秒值:" + milli);
// 3. 毫秒值转Instant
Instant ofEpochMilli = Instant.ofEpochMilli(milli);
}
}
四、日期格式化与解析(DateTimeFormatter)
旧 API 的
SimpleDateFormat线程不安全,新 API 的DateTimeFormatter不可变、线程安全,直接在多线程中使用。1. 日期格式化(LocalDateTime → String)
java
运行
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class FormatDemo {
public static void main(String[] args) {
// 1. 获取当前日期时间
LocalDateTime now = LocalDateTime.now();
// 2. 创建格式化器(常用格式:yyyy-MM-dd HH:mm:ss)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 3. 格式化:日期 → 字符串
String formatTime = now.format(formatter);
System.out.println("格式化后:" + formatTime);
}
}
2. 日期解析(String → LocalDateTime)
java
运行
public class ParseDemo {
public static void main(String[] args) {
// 待解析的字符串
String timeStr = "2026-03-20 15:30:25";
// 格式化器(必须与字符串格式一致)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 解析:字符串 → 日期时间
LocalDateTime parseTime = LocalDateTime.parse(timeStr, formatter);
System.out.println("解析后:" + parseTime);
}
}
常用格式化模板
表格
| 模板 | 含义 | 示例输出 |
|---|---|---|
| yyyy-MM-dd | 年月日 | 2026-03-20 |
| yyyy-MM-dd HH:mm:ss | 年月日时分秒 | 2026-03-20 15:30:25 |
| yyyy/MM/dd HH:mm | 年月日时分(斜杠) | 2026/03/20 15:30 |
| E | 星期 | 星期五 |
五、日期时间计算(加减、比较、间隔)
新 API 提供了极简的日期计算方法,无需手动处理闰年、大小月,一行代码搞定。
1. 日期加减(plus /minus)
java
运行
LocalDateTime now = LocalDateTime.now();
// 加1天
LocalDateTime plusDays = now.plusDays(1);
// 减2小时
LocalDateTime minusHours = now.minusHours(2);
// 加1个月
LocalDateTime plusMonths = now.plusMonths(1);
// 加1年
LocalDateTime plusYears = now.plusYears(1);
2. 日期比较(isBefore /isAfter/isEqual)
java
运行
LocalDate date1 = LocalDate.of(2026, 3, 20);
LocalDate date2 = LocalDate.of(2025, 12, 31);
// date1是否在date2之前
boolean before = date1.isBefore(date2);
// date1是否在date2之后
boolean after = date1.isAfter(date2);
// 日期是否相等
boolean equal = date1.isEqual(date2);
3. 时间间隔计算(Duration / Period)
- Period:计算年月日间隔(适用于 LocalDate)
- Duration:计算时分秒毫秒间隔(适用于 LocalTime/LocalDateTime)
java
运行
import java.time.Duration;
import java.time.Period;
public class IntervalDemo {
public static void main(String[] args) {
// 1. 日期间隔(年月日)
LocalDate startDate = LocalDate.of(2025, 1, 1);
LocalDate endDate = LocalDate.of(2026, 3, 20);
Period period = Period.between(startDate, endDate);
System.out.println("间隔:" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "日");
// 2. 时间间隔(时分秒)
LocalTime startTime = LocalTime.of(10, 0, 0);
LocalTime endTime = LocalTime.of(15, 30, 0);
Duration duration = Duration.between(startTime, endTime);
System.out.println("间隔小时:" + duration.toHours()); // 5小时
System.out.println("间隔分钟:" + duration.toMinutes()); // 330分钟
}
}
六、时区处理(ZonedDateTime)
ZonedDateTime是带时区的日期时间,适用于跨境电商、国际业务、跨时区系统对接。1. 常用时区 ID
- 亚洲 / 上海:
Asia/Shanghai(东八区,北京时间) - 美国纽约:
America/New_York - 英国伦敦:
Europe/London
2. 时区使用示例
java
运行
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class ZonedDemo {
public static void main(String[] args) {
// 1. 获取当前时区的日期时间
ZonedDateTime now = ZonedDateTime.now();
System.out.println("当前时区时间:" + now);
// 2. 指定时区(北京时间)
ZonedDateTime 北京时间 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("北京时间:" + 北京时间);
// 3. 时区转换(纽约时间 → 北京时间)
ZonedDateTime 纽约时间 = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime 转换北京时间 = 纽约时间.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
}
}
七、新旧日期 API 转换(兼容旧项目)
实际开发中,难免需要与旧 API(Date、Calendar)转换,新 API 提供了便捷的转换方法:
表格
| 新 API | 转换为旧 API | 旧 API 转新 API |
|---|---|---|
| LocalDateTime | ↔ Date | Date → Instant → LocalDateTime |
| Instant | ↔ Date | Date → Instant |
| ZonedDateTime | ↔ GregorianCalendar | Calendar → ZonedDateTime |
核心转换代码
java
运行
import java.util.Date;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
public class ConvertDemo {
public static void main(String[] args) {
// 1. Date → LocalDateTime
Date date = new Date();
LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
// 2. LocalDateTime → Date
LocalDateTime now = LocalDateTime.now();
Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
Date newDate = Date.from(instant);
}
}
八、实战:开发中常用工具类(直接复制使用)
封装日常开发最常用的日期工具方法,线程安全、开箱即用:
java
运行
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 日期时间工具类(Java 8+ 线程安全)
*/
public class DateUtils {
// 标准日期格式:yyyy-MM-dd
public static final String YYYY_MM_DD = "yyyy-MM-dd";
// 标准日期时间格式:yyyy-MM-dd HH:mm:ss
public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
/**
* LocalDateTime 转 字符串
*/
public static String format(LocalDateTime dateTime, String pattern) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return dateTime.format(formatter);
}
/**
* 字符串 转 LocalDateTime
*/
public static LocalDateTime parse(String dateStr, String pattern) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return LocalDateTime.parse(dateStr, formatter);
}
/**
* 获取当前日期字符串(yyyy-MM-dd)
*/
public static String getNowDate() {
return LocalDate.now().format(DateTimeFormatter.ofPattern(YYYY_MM_DD));
}
/**
* 获取当前日期时间字符串(yyyy-MM-dd HH:mm:ss)
*/
public static String getNowDateTime() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS));
}
}
九、总结与避坑指南
1. 核心总结
- 优先使用新 API:Java 8+ 强制使用
java.time包下的类,抛弃Date/Calendar/SimpleDateFormat。 - 线程安全:新 API 所有核心类都是不可变类,多线程环境直接使用,无需加锁。
- 分工明确:
- 仅日期:
LocalDate - 仅时间:
LocalTime - 日期时间:
LocalDateTime - 时间戳:
Instant - 格式化:
DateTimeFormatter
- 仅日期:
- 时区必备:国际业务使用
ZonedDateTime。
2. 避坑指南
- 格式化 / 解析时,字符串格式必须与模板一致,否则抛
DateTimeParseException。 Instant是 UTC 时间,直接转北京时间需要 + 8 小时。- 新旧 API 转换时,必须指定时区,否则时间偏差 8 小时。
- 不要手动计算日期加减(如闰年、大小月),直接使用
plus/minus方法。
结语
Java 日期时间 API 是后端开发的基础核心技能,新 API 设计优雅、功能强大、线程安全,彻底解决了旧 API 的所有痛点。
本文从基础到实战,覆盖了开发中所有日期时间场景,建议收藏备用,熟练掌握后能大幅提升开发效率,避免日期相关 bug。
如果本文对你有帮助,欢迎点赞、收藏、关注,后续持续更新 Java 实战干货!