Java 时间类详解
一、时间类发展历程
┌─────────────────────────────────────────────────────────────┐
│ Java 时间 API 演进 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Java 1.0 Java 1.1 Java 8+ │
│ ┌─────────┐ ┌─────────┐ ┌────────────┐ │
│ │ Date │ │Calendar │ │LocalDateTime│ │
│ │(1970年) │ │ (1997) │ │ (2014) │ │
│ └─────────┘ └─────────┘ └────────────┘ │
│ │ │ │ │
│ └─────────────────────┴──────────────────────┘ │
│ │ │
│ 线程不安全、API复杂 │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ java.time 包 │ │
│ │ 线程安全、API优雅 │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘二、旧版时间 API(Date / Calendar)
2.1 Date 类
创建方式
java
// 当前时间
Date date1 = new Date(); // Mon Apr 13 15:30:45 CST 2026
Date date2 = new Date(System.currentTimeMillis()); // 等价
// 指定毫秒数(从1970-01-01开始)
Date date3 = new Date(1000); // 1970-01-01 00:00:01
// 已过时的构造方式(不推荐)
Date date4 = new Date(2026, 3, 13); // ⚠️ 年份要+1900,月从0开始
Date date5 = new Date(2026-1900, 3, 13, 15, 30); // ⚠️ 不推荐常用方法
java
Date date = new Date();
// 获取时间戳(毫秒)
long time = date.getTime(); // 1744534245000
// 设置时间戳(已过时)
date.setTime(1000); // 设置为 1970-01-01 00:00:01
// 比较方法
Date d1 = new Date();
Date d2 = new Date(1000);
d1.after(d2); // true d1在d2之后
d1.before(d2); // false d1在d2之前
d1.compareTo(d2); // 正数,d1更晚
// 判断相等(比较时间戳)
d1.equals(d2); // false时间戳概念
时间戳:从 1970-01-01 00:00:00 UTC 开始计算的毫秒数
┌─────────────────────────────────────────────────────────────┐
│ 1970-01-01 00:00:00 UTC │
│ │ │
│ │ +1秒 +1分 +1时 +1天 +1年 │
│ ▼ ▼ ▼ ▼ ▼ │
│ 1000 60000 3600000 86400000 31536000000 │
│ │
│ 时间戳 ────► 人类可读时间 │
│ 人类时间 ──► 时间戳(用于存储和计算) │
└─────────────────────────────────────────────────────────────┘Date 的问题
java
// 问题1:线程不安全
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 多个线程共享同一个 sdf,可能出现日期错乱
// 问题2:设计混乱
Date date = new Date(2026, 3, 13); // 实际是 3926年,不是2026年
// 月份从0开始(0=一月,3=四月)
// 年份需要减去1900
// 问题3:时区处理困难
date.toString() // 显示的是本地时区,容易混淆2.2 Calendar 类
创建方式
java
// 获取实例(推荐)
Calendar cal = Calendar.getInstance(); // 默认当前时间
// 通过 Date 创建
Calendar cal2 = Calendar.getInstance();
cal2.setTime(new Date());
// 通过毫秒创建
Calendar cal3 = Calendar.getInstance();
cal3.setTimeInMillis(System.currentTimeMillis());常用方法
java
Calendar cal = Calendar.getInstance();
// ┌─────────────────────────────────────────────────────────┐
// │ 获取时间分量 │
// └─────────────────────────────────────────────────────────┘
cal.get(Calendar.YEAR); // 2026
cal.get(Calendar.MONTH); // 3 (0=一月,所以是四月)
cal.get(Calendar.DAY_OF_MONTH); // 13
cal.get(Calendar.DAY_OF_WEEK); // 2 (1=周日,2=周一)
cal.get(Calendar.HOUR_OF_DAY); // 15 (24小时制)
cal.get(Calendar.HOUR); // 3 (12小时制)
cal.get(Calendar.MINUTE); // 30
cal.get(Calendar.SECOND); // 45
cal.get(Calendar.MILLISECOND); // 123
// ┌─────────────────────────────────────────────────────────┐
// │ 设置时间分量 │
// └─────────────────────────────────────────────────────────┘
cal.set(Calendar.YEAR, 2025); // 设置年份
cal.set(Calendar.MONTH, 0); // 设置月份(0=一月)
cal.set(Calendar.DAY_OF_MONTH, 1);// 设置日期
cal.set(Calendar.HOUR_OF_DAY, 10); // 设置小时
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
// 一次性设置多个字段
cal.set(2025, 0, 1, 10, 30, 0); // 2025-01-01 10:30:00
// ┌─────────────────────────────────────────────────────────┐
// │ 增加/减少时间 │
// └─────────────────────────────────────────────────────────┘
cal.add(Calendar.YEAR, 1); // 年份 +1
cal.add(Calendar.MONTH, -1); // 月份 -1
cal.add(Calendar.DAY_OF_MONTH, 7); // 日期 +7(加一周)
cal.add(Calendar.HOUR, 2); // 小时 +2
// ┌─────────────────────────────────────────────────────────┐
// │ 滚动(只影响单个字段) │
// └─────────────────────────────────────────────────────────┘
cal.roll(Calendar.DAY_OF_MONTH, 31); // 日期滚动,不影响月份
// ┌─────────────────────────────────────────────────────────┐
// │ 转换为 Date │
// └─────────────────────────────────────────────────────────┘
Date date = cal.getTime(); // Calendar → DateCalendar 字段常量速查
| 字段 | 说明 | 示例值 |
|---|---|---|
YEAR | 年 | 2026 |
MONTH | 月(0-11) | 3 |
DAY_OF_MONTH | 日期 | 13 |
DAY_OF_WEEK | 星期(1=周日) | 2 |
HOUR_OF_DAY | 小时(0-23) | 15 |
HOUR | 小时(1-12) | 3 |
MINUTE | 分钟 | 30 |
SECOND | 秒 | 45 |
MILLISECOND | 毫秒 | 123 |
Calendar 的问题
java
// 问题1:月份从0开始,不直观
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, 3); // 实际是四月,不是三月
// 问题2:月份要用常量,容易写错
cal.set(Calendar.MONTH, Calendar.APRIL); // 正确写法
// 问题3:无法直接格式化
// 必须先转 Date,再用 SimpleDateFormat
// 问题4:线程不安全
// 和 SimpleDateFormat 一样,多线程不安全2.3 SimpleDateFormat 日期格式化
格式化模式
java
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// ┌─────────────────────────────────────────────────────────┐
// │ 模式字符含义 │
// └─────────────────────────────────────────────────────────┘
// 年份
y // 年:2026
yy // 年:26
yyy // 年:2026
// 月份
M // 月:4
MM // 月:04
MMM // 月:四月
MMMM // 月:四月
// 日期
d // 日:13
dd // 日:13
// 小时
H // 时(24h):15
HH // 时(24h):15
h // 时(12h):3
hh // 时(12h):03
// 分钟
m // 分:5
mm // 分:05
// 秒
s // 秒:30
ss // 秒:30
// 毫秒
S // 毫秒:123
// 星期
E // 星期:周一
EEEE // 星期:星期一
// 上午/下午
a // 上午/下午
// 时区
z // 时区:CST
Z // 时区:+0800常用格式
java
SimpleDateFormat sdf = new SimpleDateFormat();
// 常用格式
"yyyy-MM-dd" // 2026-04-13
"yyyy-MM-dd HH:mm:ss" // 2026-04-13 15:30:45
"yyyy/MM/dd" // 2026/04/13
"yyyyMMddHHmmss" // 20260413153045
"yyyy年MM月dd日 HH:mm:ss" // 2026年04月13日 15:30:45
"yyyy-MM-dd'T'HH:mm:ss" // ISO格式:2026-04-13T15:30:45格式化与解析
java
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// ┌─────────────────────────────────────────────────────────┐
// │ 格式化:Date → String │
// └─────────────────────────────────────────────────────────┘
Date date = new Date();
String str = sdf.format(date);
System.out.println(str); // "2026-04-13 15:30:45"
// ┌─────────────────────────────────────────────────────────┐
// │ 解析:String → Date │
// └─────────────────────────────────────────────────────────┘
String dateStr = "2026-04-13 15:30:45";
Date parsedDate = sdf.parse(dateStr);
System.out.println(parsedDate); // Mon Apr 13 15:30:45 CST 2026线程安全问题
java
// ❌ 错误:SimpleDateFormat 线程不安全
// 多个线程共享同一个实例会导致问题
public class DateUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static String format(Date date) {
return sdf.format(date); // 危险!多线程不安全
}
}
// ✅ 正确做法1:每次创建新实例
public static String format(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date);
}
// ✅ 正确做法2:ThreadLocal
private static ThreadLocal<SimpleDateFormat> sdfThreadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static String format(Date date) {
return sdfThreadLocal.get().format(date);
}
// ✅ 正确做法3:Java 8+ 的 DateTimeFormatter
public static String format(Date date) {
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}三、新版时间 API(java.time)
架构总览
java.time
│
├── LocalDateTime 本地日期时间(无时区)
│ │
│ ├── LocalDate 本地日期(只有日期)
│ │
│ └── LocalTime 本地时间(只有时间)
│
├── ZonedDateTime 带时区的日期时间
│
├── Instant 时间戳(从1970计算)
│
├── Duration 时间段(用于时间)
│
├── Period 日期段(用于日期)
│
└── Clock 时钟(用于获取当前时间)3.1 LocalDateTime(本地日期时间)
创建方式
java
// ┌─────────────────────────────────────────────────────────┐
// │ 获取当前时间 │
// └─────────────────────────────────────────────────────────┘
LocalDateTime now = LocalDateTime.now(); // 当前时间
LocalDateTime now2 = LocalDateTime.of(2026, 4, 13, 15, 30); // 指定时间
// ┌─────────────────────────────────────────────────────────┐
// │ 通过年月日时分秒创建 │
// └─────────────────────────────────────────────────────────┘
LocalDateTime dt1 = LocalDateTime.of(2026, 4, 13, 15, 30);
LocalDateTime dt2 = LocalDateTime.of(2026, 4, 13, 15, 30, 45);
LocalDateTime dt3 = LocalDateTime.of(2026, 4, 13, 15, 30, 45, 123456789);
// ┌─────────────────────────────────────────────────────────┐
// │ 通过其他对象转换 │
// └─────────────────────────────────────────────────────────┘
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
// Date → LocalDateTime
Instant instant = new Date().toInstant();
LocalDateTime dt4 = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
// LocalDate + LocalTime → LocalDateTime
LocalDateTime dt5 = date.atTime(time);
LocalDateTime dt6 = time.atDate(date);获取时间分量
java
LocalDateTime dt = LocalDateTime.of(2026, 4, 13, 15, 30, 45);
dt.getYear(); // 2026
dt.getMonth(); // APRIL(枚举)
dt.getMonthValue(); // 4(数字)
dt.getDayOfMonth(); // 13
dt.getDayOfWeek(); // MONDAY(枚举)
dt.getDayOfYear(); // 103(一年中的第几天)
dt.getHour(); // 15
dt.getMinute(); // 30
dt.getSecond(); // 45
dt.getNano(); // 0(纳秒)修改时间(返回新对象)
java
LocalDateTime dt = LocalDateTime.of(2026, 4, 13, 15, 30);
// with开头:直接设置
dt.withYear(2025); // 2025-04-13T15:30
dt.withMonth(6); // 2026-06-13T15:30
dt.withDayOfMonth(1); // 2026-04-01T15:30
dt.withHour(10); // 2026-04-13T10:30
dt.withMinute(0); // 2026-04-13T15:00
dt.withSecond(0); // 2026-04-13T15:30
// plus开头:增加
dt.plusYears(1); // 2027-04-13T15:30
dt.plusMonths(2); // 2026-06-13T15:30
dt.plusDays(7); // 2026-04-20T15:30
dt.plusHours(2); // 2026-04-13T17:30
dt.plusMinutes(30); // 2026-04-13T16:00
dt.plusSeconds(60); // 2026-04-13T15:31
// minus开头:减少
dt.minusYears(1); // 2025-04-13T15:30
dt.minusDays(10); // 2026-04-03T15:30
// ⚠️ 注意:以上方法都返回新对象,原对象不变
System.out.println(dt); // 2026-04-13T15:30(不变)时间比较
java
LocalDateTime dt1 = LocalDateTime.of(2026, 4, 13, 15, 30);
LocalDateTime dt2 = LocalDateTime.of(2026, 4, 13, 15, 30);
LocalDateTime dt3 = LocalDateTime.of(2026, 4, 13, 16, 30);
// isEqual:是否相等
dt1.isEqual(dt2); // true
dt1.isEqual(dt3); // false
// isBefore:是否早于
dt1.isBefore(dt3); // true
dt3.isBefore(dt1); // false
// isAfter:是否晚于
dt1.isAfter(dt3); // false
dt3.isAfter(dt1); // true
// compareTo:比较(用于排序)
dt1.compareTo(dt3); // -1(dt1 < dt3)格式化和解析
java
// ┌─────────────────────────────────────────────────────────┐
// │ 格式化:LocalDateTime → String │
// └─────────────────────────────────────────────────────────┘
LocalDateTime dt = LocalDateTime.of(2026, 4, 13, 15, 30, 45);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = dt.format(formatter);
System.out.println(str); // "2026-04-13 15:30:45"
// 内置格式器
dt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); // "2026-04-13T15:30:45"
dt.format(DateTimeFormatter.ISO_LOCAL_DATE); // "2026-04-13"
dt.format(DateTimeFormatter.ISO_LOCAL_TIME); // "15:30:45"
// ┌─────────────────────────────────────────────────────────┐
// │ 解析:String → LocalDateTime │
// └─────────────────────────────────────────────────────────┘
String dateStr = "2026-04-13 15:30:45";
LocalDateTime parsed = LocalDateTime.parse(dateStr, formatter);
System.out.println(parsed); // 2026-04-13T15:30
// 使用内置格式器解析
LocalDateTime parsed2 = LocalDateTime.parse("2026-04-13T15:30:45");
LocalDate date = LocalDate.parse("2026-04-13");
LocalTime time = LocalTime.parse("15:30:45");常用格式模板
java
"yyyy-MM-dd" // 2026-04-13
"yyyy-MM-dd HH:mm:ss" // 2026-04-13 15:30:45
"yyyy/MM/dd HH:mm:ss" // 2026/04/13 15:30:45
"yyyy年MM月dd日 HH时mm分ss秒" // 2026年04月13日 15时30分45秒
"yyyy-MM-dd'T'HH:mm:ss" // ISO格式(JSON常用)
"yyyyMMddHHmmss" // 紧凑格式:202604131530453.2 LocalDate(本地日期)
创建方式
java
// 当前日期
LocalDate today = LocalDate.now(); // 2026-04-13
// 指定日期
LocalDate date1 = LocalDate.of(2026, 4, 13);
LocalDate date2 = LocalDate.of(2026, Month.APRIL, 13);
// 字符串解析
LocalDate date3 = LocalDate.parse("2026-04-13");
LocalDate date4 = LocalDate.parse("2026/04/13", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
// 从 LocalDateTime 获取
LocalDate date5 = LocalDateTime.now().toLocalDate();常用方法
java
LocalDate date = LocalDate.of(2026, 4, 13);
// 获取
date.getYear(); // 2026
date.getMonth(); // APRIL
date.getMonthValue(); // 4
date.getDayOfMonth(); // 13
date.getDayOfWeek(); // MONDAY
date.getDayOfYear(); // 103
// 修改
date.withYear(2025); // 2025-04-13
date.withMonth(6); // 2026-06-13
date.withDayOfMonth(1); // 2026-04-01
// 计算
date.plusDays(7); // 2026-04-20
date.plusMonths(1); // 2026-05-13
date.plusYears(1); // 2027-04-13
date.minusDays(1); // 2026-04-12
// 比较
date.isLeapYear(); // false(2026不是闰年)
date.isAfter(date2);
date.isBefore(date2);
date.isEqual(date2);
// 实用方法
date.lengthOfMonth(); // 30(四月有30天)
date.lengthOfYear(); // 365(2026年有365天)
// 日期边界
date.atStartOfDay(); // 2026-04-13T00:00
date.atTime(23, 59, 59); // 2026-04-13T23:59:593.3 LocalTime(本地时间)
创建方式
java
// 当前时间
LocalTime now = LocalTime.now();
// 指定时间
LocalTime time1 = LocalTime.of(15, 30); // 15:30
LocalTime time2 = LocalTime.of(15, 30, 45); // 15:30:45
LocalTime time3 = LocalTime.of(15, 30, 45, 123456789); // 15:30:45.123456789
// 特殊时间
LocalTime midnight = LocalTime.MIDNIGHT; // 00:00
LocalTime noon = LocalTime.NOON; // 12:00
// 字符串解析
LocalTime time4 = LocalTime.parse("15:30:45");
LocalTime time5 = LocalTime.parse("15.30.45", DateTimeFormatter.ofPattern("HH.mm.ss"));常用方法
java
LocalTime time = LocalTime.of(15, 30, 45);
// 获取
time.getHour(); // 15
time.getMinute(); // 30
time.getSecond(); // 45
time.getNano(); // 0
// 修改
time.withHour(10); // 10:30:45
time.withMinute(0); // 15:00:45
time.withSecond(30); // 15:30:30
// 计算
time.plusHours(2); // 17:30:45
time.plusMinutes(30); // 16:00:45
time.minusSeconds(60); // 15:29:45
// 比较
time.isAfter(time2);
time.isBefore(time2);3.4 Instant(时间戳)
概念
Instant 类似于 Date,但使用更好的设计
┌─────────────────────────────────────────────────────────────┐
│ │
│ Date Instant │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ Date(long ms) │ │ Instant(secs, │ │
│ │ │ │ nanos) │ │
│ │ getTime() │ │ toEpochMilli() │ │
│ │ setTime(long) │ │ toEpochSecond()│ │
│ └────────────────┘ └────────────────┘ │
│ │
│ Date: 毫秒精度 │
│ Instant: 秒 + 纳秒精度 │
│ │
└─────────────────────────────────────────────────────────────┘创建方式
java
// 当前时间戳
Instant now = Instant.now(); // 2026-04-13T07:30:45.123456Z (UTC时间)
// 从 Date 转换
Date date = new Date();
Instant instant = date.toInstant();
// 从毫秒转换
Instant ofEpochMilli = Instant.ofEpochMilli(System.currentTimeMillis());
// 从秒/纳秒创建
Instant ofEpochSecond = Instant.ofEpochSecond(1700000000);
Instant ofEpochSecond2 = Instant.ofEpochSecond(1700000000, 123456789);常用方法
java
Instant instant = Instant.now();
// 获取时间戳
instant.getEpochSecond(); // 秒:从1970-01-01的秒数
instant.toEpochMilli(); // 毫秒:1744534245000
instant.getNano(); // 纳秒:123456789
// 计算
instant.plusSeconds(3600); // 加1小时
instant.plusMillis(60000); // 加1分钟
instant.minusSeconds(86400); // 减1天
// 与 LocalDateTime 转换
// Instant → LocalDateTime(需要时区)
LocalDateTime dt = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
// LocalDateTime → Instant(需要时区)
Instant toInstant = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();3.5 Duration(时间段 - 时间)
用于计算两个时间之间的间隔,精度是秒和纳秒。
java
// ┌─────────────────────────────────────────────────────────┐
// │ 创建 Duration │
// └─────────────────────────────────────────────────────────┘
// 从两个时间创建
LocalDateTime start = LocalDateTime.of(2026, 4, 13, 10, 0);
LocalDateTime end = LocalDateTime.of(2026, 4, 13, 12, 30);
Duration duration = Duration.between(start, end);
// 从时间戳创建
Duration duration2 = Duration.ofHours(2);
Duration duration3 = Duration.ofMinutes(30);
Duration duration4 = Duration.ofSeconds(3600);
Duration duration5 = Duration.ofMillis(1500);
// ┌─────────────────────────────────────────────────────────┐
// │ 获取间隔 │
// └─────────────────────────────────────────────────────────┘
Duration d = Duration.between(
LocalDateTime.of(2026, 4, 13, 10, 0),
LocalDateTime.of(2026, 4, 13, 12, 30, 45)
);
d.toDays(); // 0(天数)
d.toHours(); // 2(小时数)
d.toMinutes(); // 150(分钟数)
d.toSeconds(); // 9000(秒数)
d.toMillis(); // 9000000(毫秒数)
d.toNanos(); // 9000000000000(纳秒数)
d.getSeconds(); // 9000(秒数)
d.getNano(); // 450000000(纳秒部分)
// ┌─────────────────────────────────────────────────────────┐
// │ 计算 │
// └─────────────────────────────────────────────────────────┘
Duration d = Duration.ofHours(2).plusMinutes(30); // 2:30:00
d.multipliedBy(2); // 5:00:00
d.dividedBy(2); // 1:15:00
d.negated(); // -2:30:00
// ⚠️ Duration 不能跨天计算(超过24小时用 Period)3.6 Period(时间段 - 日期)
用于计算两个日期之间的间隔,精度是年月日。
java
// ┌─────────────────────────────────────────────────────────┐
// │ 创建 Period │
// └─────────────────────────────────────────────────────────┘
// 从两个日期创建
LocalDate start = LocalDate.of(2026, 1, 1);
LocalDate end = LocalDate.of(2026, 4, 13);
Period period = Period.between(start, end);
// 直接创建
Period period2 = Period.ofYears(1);
Period period3 = Period.ofMonths(3);
Period period4 = Period.ofDays(15);
Period period5 = Period.of(1, 3, 15); // 1年3个月15天
// ┌─────────────────────────────────────────────────────────┐
// │ 获取间隔 │
// └─────────────────────────────────────────────────────────┘
Period p = Period.between(
LocalDate.of(2026, 1, 1),
LocalDate.of(2026, 4, 13)
);
p.getYears(); // 0
p.getMonths(); // 3
p.getDays(); // 12
p.toTotalMonths(); // 3(总月数)
// ┌─────────────────────────────────────────────────────────┐
// │ Duration vs Period │
// └─────────────────────────────────────────────────────────┘
// Duration:用于时间(秒、毫秒)
Duration d = Duration.ofHours(2); // 2小时
// Period:用于日期(年、月、日)
Period p = Period.ofMonths(3); // 3个月
// ⚠️ 区别
LocalDate date1 = LocalDate.of(2026, 1, 31);
LocalDate date2 = LocalDate.of(2026, 2, 28);
Period.between(date1, date2); // P0M28D(28天)
Duration.between(date1.atStartOfDay(), date2.atStartOfDay()); // P28D3.7 ZonedDateTime(带时区的日期时间)
创建方式
java
// 当前时区时间
ZonedDateTime now = ZonedDateTime.now();
// 指定时区
ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime now3 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// 指定时间
ZonedDateTime zdt = ZonedDateTime.of(2026, 4, 13, 15, 30, 0, 0, ZoneId.of("Asia/Shanghai"));
// 从 Instant 创建
Instant instant = Instant.now();
ZonedDateTime zdt2 = instant.atZone(ZoneId.systemDefault());常用方法
java
ZonedDateTime zdt = ZonedDateTime.now();
zdt.getYear(); // 2026
zdt.getMonth(); // APRIL
zdt.getDayOfMonth(); // 13
zdt.getHour(); // 15(或根据时区)
zdt.getMinute(); // 30
zdt.getSecond(); // 45
zdt.getNano(); // 0
zdt.getZone(); // Asia/Shanghai
zdt.getOffset(); // +08:00
// 时区转换
zdt.withZoneSameInstant(ZoneId.of("America/New_York")); // 转换时间(保持同一时刻)
zdt.withZoneSameLocal(ZoneId.of("America/New_York")); // 转换时间(保持同一本地时间)时区操作
java
// 全球主要时区
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZoneId newYork = ZoneId.of("America/New_York");
ZoneId utc = ZoneId.of("UTC");
// 北京时间 → 东京时间
ZonedDateTime beijing = ZonedDateTime.of(2026, 4, 13, 15, 0, 0, 0, shanghai);
ZonedDateTime tokyoTime = beijing.withZoneSameInstant(tokyo);
System.out.println(beijing); // 2026-04-13T15:00+08:00[Asia/Shanghai]
System.out.println(tokyoTime); // 2026-04-13T16:00+09:00[Asia/Tokyo]
// 同一时刻,物理时间差1小时
// Instant 在不同时区显示不同
Instant instant = Instant.now();
System.out.println(instant.atZone(ZoneId.of("UTC"))); // UTC时间
System.out.println(instant.atZone(ZoneId.of("Asia/Shanghai"))); // 北京时间四、日期时间转换
转换关系图
┌─────────────────────────────────────────────────────────────┐
│ │
│ Date (java.util) │
│ │ │
│ │ toInstant() │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ LocalDate ◄──► LocalDateTime ◄──► LocalTime │ │
│ │ │ │ │ │ │
│ │ │ ┌────┴────┐ │ │ │
│ │ │ │ │ │ │ │
│ │ │ ZonedDateTime │ │ │ │
│ │ │ │ │ │ │ │
│ │ └──────────┴─────────┴────────────┘ │ │
│ │ │ │ │
│ │ │ atZone() │ │
│ │ ▼ │ │
│ │ Instant (java.time) │ │
│ │ │ │ │
│ │ │ toEpochMilli() │ │
│ │ ▼ │ │
│ │ long (时间戳) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘常用转换代码
java
// ┌─────────────────────────────────────────────────────────┐
// │ Date ↔ LocalDateTime │
// └─────────────────────────────────────────────────────────┘
// Date → LocalDateTime
Date date = new Date();
LocalDateTime ldt = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
// LocalDateTime → Date
LocalDateTime ldt2 = LocalDateTime.now();
Date date2 = Date.from(ldt2.atZone(ZoneId.systemDefault()).toInstant());
// ┌─────────────────────────────────────────────────────────┐
// │ Date ↔ LocalDate │
// └─────────────────────────────────────────────────────────┘
// Date → LocalDate
LocalDate ld = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
// LocalDate → Date
LocalDate ld2 = LocalDate.of(2026, 4, 13);
Date date3 = Date.from(ld2.atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant());
// ┌─────────────────────────────────────────────────────────┐
// │ Date ↔ Instant │
// └─────────────────────────────────────────────────────────┘
// Date → Instant
Instant instant = date.toInstant();
// Instant → Date
Date date4 = Date.from(instant);
// ┌─────────────────────────────────────────────────────────┐
// │ long (时间戳) ↔ Instant │
// └─────────────────────────────────────────────────────────┘
// long → Instant
Instant instant2 = Instant.ofEpochMilli(System.currentTimeMillis());
Instant instant3 = Instant.ofEpochSecond(1700000000);
// Instant → long
long milli = instant.toEpochMilli();
long second = instant.getEpochSecond();
// ┌─────────────────────────────────────────────────────────┐
// │ String ↔ LocalDateTime │
// └─────────────────────────────────────────────────────────┘
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// LocalDateTime → String
String str = ldt.format(formatter);
// String → LocalDateTime
LocalDateTime ldt2 = LocalDateTime.parse(str, formatter);五、实用工具类
5.1 DateUtils(工具类封装)
java
public class DateUtils {
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern("HH:mm:ss");
// ┌─────────────────────────────────────────────────────┐
// │ 获取当前时间 │
// └─────────────────────────────────────────────────────┘
public static LocalDateTime now() {
return LocalDateTime.now();
}
public static LocalDate today() {
return LocalDate.now();
}
public static long currentTimeMillis() {
return System.currentTimeMillis();
}
// ┌─────────────────────────────────────────────────────┐
// │ 格式化 │
// └─────────────────────────────────────────────────────┘
public static String formatDateTime(LocalDateTime dateTime) {
return dateTime.format(DATE_TIME_FORMATTER);
}
public static String formatDate(LocalDate date) {
return date.format(DATE_FORMATTER);
}
public static String format(LocalDateTime dateTime, String pattern) {
return dateTime.format(DateTimeFormatter.ofPattern(pattern));
}
// ┌─────────────────────────────────────────────────────┐
// │ 解析 │
// └─────────────────────────────────────────────────────┘
public static LocalDateTime parseDateTime(String dateStr) {
return LocalDateTime.parse(dateStr, DATE_TIME_FORMATTER);
}
public static LocalDate parseDate(String dateStr) {
return LocalDate.parse(dateStr, DATE_FORMATTER);
}
public static LocalDateTime parse(String dateStr, String pattern) {
return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
}
// ┌─────────────────────────────────────────────────────┐
// │ 计算 │
// └─────────────────────────────────────────────────────┘
// 增加天数
public static LocalDate plusDays(LocalDate date, long days) {
return date.plusDays(days);
}
// 增加月份
public static LocalDate plusMonths(LocalDate date, long months) {
return date.plusMonths(months);
}
// 计算天数差
public static long daysBetween(LocalDate start, LocalDate end) {
return Duration.between(start.atStartOfDay(), end.atStartOfDay()).toDays();
}
// ┌─────────────────────────────────────────────────────┐
// │ 判断 │
// └─────────────────────────────────────────────────────┘
public static boolean isWeekend(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
return dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
}
public static boolean isBefore(LocalDateTime dt1, LocalDateTime dt2) {
return dt1.isBefore(dt2);
}
public static boolean isAfter(LocalDateTime dt1, LocalDateTime dt2) {
return dt1.isAfter(dt2);
}
}5.2 常用日期计算
java
// 获取今天开始时间
LocalDateTime startOfToday = LocalDate.now().atStartOfDay();
LocalDateTime endOfToday = LocalDate.now().atTime(23, 59, 59);
// 获取本周第一天(周一)
LocalDate monday = LocalDate.now().with(DayOfWeek.MONDAY);
LocalDate sunday = LocalDate.now().with(DayOfWeek.SUNDAY);
// 获取本月第一天
LocalDate firstDayOfMonth = LocalDate.now().withDayOfMonth(1);
// 获取本月最后一天
LocalDate lastDayOfMonth = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
// 获取今年第一天
LocalDate firstDayOfYear = LocalDate.now().withDayOfYear(1);
// 判断是否闰年
LocalDate.now().isLeapYear();
// 获取下个月同一天(如果不存在,则取最后一天)
LocalDate nextMonthSameDay = LocalDate.now().plusMonths(1);
// 获取指定月有多少天
LocalDate.now().lengthOfMonth();
// 计算年龄
public static int calculateAge(LocalDate birthDate) {
return Period.between(birthDate, LocalDate.now()).getYears();
}六、线程安全对比
| 类 | 线程安全 | 原因 |
|---|---|---|
java.util.Date | ❌ 不安全 | 可变对象,多线程共享会出问题 |
Calendar | ❌ 不安全 | 可变对象,多线程共享会出问题 |
SimpleDateFormat | ❌ 不安全 | 多线程共享格式化器会出问题 |
LocalDateTime | ✅ 安全 | 不可变对象,无状态 |
Instant | ✅ 安全 | 不可变对象,无状态 |
DateTimeFormatter | ✅ 安全 | 不可变对象,但解析时可能不安全(建议每次new或用ThreadLocal) |
java
// ❌ 错误示例
public class BadDateUtil {
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");
public static Date parse(String date) {
return SDF.parse(date); // 线程不安全!
}
}
// ✅ 正确示例
public class GoodDateUtil {
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static LocalDate parse(String date) {
return LocalDate.parse(date, FMT); // 线程安全!
}
}七、总结
API 选择建议
┌─────────────────────────────────────────────────────────────┐
│ 选择建议 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 新代码(Java 8+):使用 java.time 包 │
│ ├── 只需要日期:LocalDate │
│ ├── 只需要时间:LocalTime │
│ ├── 日期+时间:LocalDateTime │
│ ├── 需要时区:ZonedDateTime │
│ ├── 时间戳计算:Instant │
│ └── 格式化/解析:DateTimeFormatter │
│ │
│ 旧代码维护: │
│ ├── 尽量转换为新 API │
│ ├── 必须使用 Date 时:每次 new,不共享 │
│ └── 必须使用 SimpleDateFormat:每次 new 或 ThreadLocal │
│ │
└─────────────────────────────────────────────────────────────┘常用速查表
| 需求 | Java 8+ 代码 |
|---|---|
| 当前时间 | LocalDateTime.now() |
| 当前日期 | LocalDate.now() |
| 当前时间戳 | Instant.now() |
| 字符串格式化 | dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) |
| 字符串解析 | LocalDateTime.parse(str, formatter) |
| 计算日期差 | Duration.between(start, end).toDays() |
| 增加天数 | date.plusDays(7) |
| 判断先后 | dt1.isBefore(dt2) |
| Date 转 LocalDateTime | date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime() |
| LocalDateTime 转 Date | Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()) |
相关标签
#Java #时间类 #LocalDateTime #LocalDate #LocalTime #Instant #Date #Calendar #DateTimeFormatter #Period #Duration