Skip to content

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 → Date

Calendar 字段常量速查

字段说明示例值
YEAR2026
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
SECOND45
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"                // 紧凑格式:20260413153045

3.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:59

3.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()); // P28D

3.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 转 LocalDateTimedate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
LocalDateTime 转 DateDate.from(ldt.atZone(ZoneId.systemDefault()).toInstant())

相关标签

#Java #时间类 #LocalDateTime #LocalDate #LocalTime #Instant #Date #Calendar #DateTimeFormatter #Period #Duration

Knowledge4J — Java 知识库