Java 提供了多套时间处理 API,从早期的 DateCalendar 到现代的 java.time 包。本文将系统解析各 API 的核心类、使用场景及最佳实践。

传统时间 API (Java 8 之前)

java.util.Date

  • 用途:表示特定的瞬间(毫秒精度)
  • 问题:
    • 月份从 0 开始(0 = 一月,11 = 十二月)
    • 年份从 1900 年开始计算
    • 非线程安全,大部分方法已过时
  • 示例:
    java
    1
    2
    3
    4
    5
    6
    
    // 创建表示当前时间的 Date 对象
    Date now = new Date();
    System.out.println(now); // 输出: Mon Mar 31 09:33:00 CST 2025
    
    // 创建特定时间的 Date 对象
    Date specificDate = new Date(125, 3, 31); // 2025  3  31  (方法已废弃)

java.util.Calendar

  • 用途:日期计算和字段操作
  • 问题:
    • API 设计复杂(如 Calendar.MONTH)
    • 可变对象,非线程安全
    • 月份仍从 0 开始
  • 示例:
    java
    1
    2
    3
    
    Calendar cal = Calendar.getInstance();
    cal.set(2025, Calendar.SEPTEMBER, 20);
    cal.add(Calendar.DAY_OF_MONTH, 7); // 增加 7 

现代时间 API (Java 8+ java.time 包)

核心不可变类

类名描述示例
LocalDate日期(无时间)LocalDate.of(2025,3,31)
LocalTime时间(无日期)LocalTime.parse("09:33:00")
LocalDateTime日期+时间(无时区)LocalDateTime.now()
ZonedDateTime带时区的完整日期时间ZonedDateTime.now(ZoneId.of("Asia/Shanghai"))
Instant时间戳(UTC 时间)Instant.now()

时区处理

  • ZoneId:IANA1 时区名(如 Asia/Shanghai
    java
    1
    2
    
    Set<String> zoneIds = ZoneId.getAvailableZoneIds(); // 所有可用时区
    ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
  • ZoneOffset:固定偏移(如 +08:00
    java
    1
    
    ZoneOffset offset = ZoneOffset.ofHours(8); // UTC+8

时间间隔与周期

  • Duration:基于时间的量(秒、纳秒)
    java
    1
    2
    
    Duration duration = Duration.between(startTime, endTime);
    Duration towHours = Duration.ofHours(2);
  • Period:基于日期的量(年、月、日)
    java
    1
    
    Period period = Period.between(LocalDate.now(), LocalDate.now().plusMonths(1));

格式化和解析

DateTimeFormatter:线程安全的格式化工具

java
1
2
3
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = LocalDateTime.now().format(formatter); // 2025-03-31 09:33:00
LocalDateTime parsed = LocalDateTime.parse("2025-03-31 09:33:00", formatter);

时间调整器(TemporalAdjuster

  • 预定义调整:如月末、下个周一
    java
    1
    2
    
    LocalDate nextMonday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY));
    LocalDate lastDayOfMonth = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());

示例:给定一个日期,如果是周一、周二、周三,则调整到当周周三;如果是周四、周五、周六、周日,则调整到当周周日。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;

public class DateAdjuster {
    
    // 核心逻辑:自定义 TemporalAdjuster
    public static TemporalAdjuster adjustToWednesdayOrSunday() {
        return temporal -> {
            LocalDate date = LocalDate.from(temporal);
            DayOfWeek day = date.getDayOfWeek();

            if (day == DayOfWeek.MONDAY || day == DayOfWeek.TUESDAY || day == DayOfWeek.WEDNESDAY) {
                // 周一/二/三 → 调整为当周周三
                return date.with(TemporalAdjusters.nextOrSame(DayOfWeek.WEDNESDAY));
            } else {
                // 周四/五/六/日 → 调整为当周周日
                return date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
            }
        };
    }

    // 使用示例
    public static void main(String[] args) {
        // 测试不同日期
        System.out.println(LocalDate.of(2025, 3, 31).with(adjustToWednesdayOrSunday())); // 周一 → 周三 (2025-04-02) (跨月)
        System.out.println(LocalDate.of(2024, 12, 31).with(adjustToWednesdayOrSunday())); // 周二 → 周三 (2025-01-01) (跨年)
        System.out.println(LocalDate.of(2025, 4, 2).with(adjustToWednesdayOrSunday())); // 周三 → 周三 (2025-04-02)
        System.out.println(LocalDate.of(2025, 4, 3).with(adjustToWednesdayOrSunday())); // 周四 → 周日 (2025-04-06)
        System.out.println(LocalDate.of(2025, 4, 4).with(adjustToWednesdayOrSunday())); // 周五 → 周日 (2025-04-06)
        System.out.println(LocalDate.of(2025, 4, 5).with(adjustToWednesdayOrSunday())); // 周六 → 周日 (2025-04-06)
        System.out.println(LocalDate.of(2025, 4, 6).with(adjustToWednesdayOrSunday())); // 周日 → 周日 (2025-04-06)
    }
}

新旧 API 转换

旧 → 新

java
1
2
3
4
5
// Date 转 Instant
Instant instant = new Date().toInstant();

// Calendar 转 ZonedDateTime
ZonedDateTime zdt = calendar.toInstant().atZone(calendar.getTimeZone().toZoneId());

新 → 旧

java
1
2
3
4
5
6
// ZonedDateTime 转 Date
Date date = Date.from(zonedDateTime.toInstant());
// LocalDateTime 转 Calendar
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(localDateTime.getYear(), localDateTime.getMonthValue() - 1, localDateTime.getDayOfMonth());

最佳实践与常用操作

时间计算

java
1
2
3
4
// 添加 10 天
LocalDate newDate = LocalDate.now().plusDays(10);
// 计算两个日期间的天数
long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);

时区转换

java
1
ZonedDateTime newYorkTime = ZonedDateTime.now().withZoneSameInstant(ZoneId.of("America/New_York"));

闰年判断

java
1
boolean isLeap = LocalDate.now().isLeapYear();

数据库交互

  • JDBC 类型映射
Java 类型JDBC 类型
LocalDateDATE
LocalTimeTime
LocalDateTimeTIMESTAMP
InstantTIMESTAMP WITH TIME ZONE

实用工具方法

工作日计算(跳过周末)

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public static LocalDate addWorkDays(LocalDate start, int workDays) {
    LocalDate date = start;
    int addedDays = 0;
    while (addedDays < workDays) {
        date = date.plusDays(1);
        if (date.getDayOfWeek() != DayOfWeek.SATURDAY 
            && date.getDayOfWeek() != DayOfWeek.SUNDAY) {
            addedDays++;
        }
    }
    return date;
}

时间段重叠检测

java
1
2
3
4
public static boolean isOverlap(LocalDateTime start1, LocalDateTime end1, 
                               LocalDateTime start2, LocalDateTime end2) {
    return start1.isBefore(end2) && start2.isBefore(end1);
}

注意事项

  1. 时区陷阱:始终使用 IANA 时区名 (如 Asia/Shanghai),而非所写(如 CST)。
  2. 不可变性:所有 java.time 类均为不可变,每次操作返回新对象。
  3. 性能优化:重用 DateTimeFormatter 实例(避免重复解析模式)

总结

  • 优先选择:始终使用 java.time 处理时间,避免传统 API。
  • 复杂场景:时区转换、夏令时/冬令时处理使用 ZonedDateTimeZoneRules
  • 扩展性:通过 TemporalAdjuster 实现自定义时间逻辑。

通过掌握现代 Java 时间 API,能够以更简洁、安全的方式处理复杂的日期时间需求。

附录: 常用第三方工具

一些常用的第三方工具均对日期时间有所封装,并提供了很多有用的方法。如果我们在工程中使用这些工具,可以避免重复造轮子。

Apache Commons Lang

Maven 依赖: Apache Commons Lang 3.17.0

xml
1
2
3
4
5
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>${apache-commons-lang3.version}</version>
</dependency>

Google Guava

Maven 依赖: Google Guava 33.4.6-jre

xml
1
2
3
4
5
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>${google-guava.version}</version>
</dependency>

Hutool

Maven 依赖: Hutool 5.8.36

xml
1
2
3
4
5
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>${hutool.version}</version>
</dependency>

  1. IANA 时区数据库 (Internet Assigned Numbers Authority Time Zone Database),官网地址:https://www.iana.org/ ↩︎