首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 日期時間處理大全:從 Date、Calendar 到 LocalDateTime 實戰工具類

     編輯:Java 相關知識

前言

Java 日期時間 API 經歷了三代演進:java.util.Date(1.0,缺陷多)、java.util.Calendar(1.1,設計混亂)、java.time.*(Java 8,不可變、線程安全、領域驅動設計)。雖然新項目應優先使用 Java 8+ API,但維護舊系統時仍需面對 Date 與 Calendar。本文匯總三代 API 核心用法,並提供一個生產級的 DateUtils 工具類,涵蓋格式化、解析、計算、比較、時區、閏年等高頻場景。

一、三代 API 快速對比

特性Date (1.0)Calendar (1.1)java.time (8+)
線程安全❌ 可變❌ 可變✅ 不可變
時區處理❌ 弱⚠️ 複雜✅ 強
API 直觀度⚠️ 方法名誤導❌ get/set 混亂✅ 領域建模
格式化SimpleDateFormat (非線程安全)同左DateTimeFormatter (線程安全)
互操作基礎基礎toInstant()、Date.from()

二、核心場景代碼示例

1. 獲取當前時間戳與格式化

// Date/Calendar 時代
long ts = System.currentTimeMillis(); // 推薦
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formatted = sdf.format(date);

// Java 8+ 推薦
Instant now = Instant.now(); // UTC 時間戳
long epochMilli = now.toEpochMilli();
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted8 = ldt.format(dtf);

2. 字符串解析為日期

// 舊 API(注意 SimpleDateFormat 非線程安全,建議 ThreadLocal 或每次 new)
String str = "2024-06-15 14:30:00";
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(str);

// Java 8+(DateTimeFormatter 線程安全,可靜態常量)
DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime ldt = LocalDateTime.parse(str, FMT);
ZonedDateTime zdt = ZonedDateTime.parse("2024-06-15T14:30:00+08:00", DateTimeFormatter.ISO_ZONED_DATE_TIME);

3. 日期計算:加減天/月/年、差值

// Calendar
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 7); // 7 天后
cal.add(Calendar.MONTH, -1);       // 1 月前

// Java 8+ 鏈式調用,語義清晰
LocalDate today = LocalDate.now();
LocalDate nextWeek = today.plusWeeks(1);
LocalDate lastMonth = today.minusMonths(1);
long daysBetween = ChronoUnit.DAYS.between(today, nextWeek); // 7
Period period = Period.between(today, nextWeek); // P7D

4. 判斷閏年、月底日期、同周判斷

// Java 8+
Year year = Year.of(2024);
boolean leap = year.isLeap(); // true

YearMonth ym = YearMonth.of(2024, 2);
int lastDay = ym.lengthOfMonth(); // 29

// 判斷兩日期是否同一周(以 ISO 周為準)
LocalDate d1 = LocalDate.of(2024, 6, 10); // 週一
LocalDate d2 = LocalDate.of(2024, 6, 16); // 週日
boolean sameWeek = d1.get(WeekFields.ISO.weekOfYear()) == d2.get(WeekFields.ISO.weekOfYear())
                && d1.getYear() == d2.getYear();

5. 時區轉換

ZonedDateTime shanghai = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime newYork = shanghai.withZoneSameInstant(ZoneId.of("America/New_York"));
// 或指定時區解析
ZonedDateTime.parse("2024-06-15 14:30:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("UTC")));

三、生產級 DateUtils 工具類(Java 8+ 版)

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.WeekFields;
import java.util.Locale;

public final class DateUtils {
    private DateUtils() {}

    // 線程安全格式化器常量
    public static final DateTimeFormatter FMT_SECOND = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static final DateTimeFormatter FMT_DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    public static final DateTimeFormatter FMT_MILLIS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
    public static final DateTimeFormatter FMT_COMPACT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    /** 當前系統時間 (LocalDateTime) */
    public static LocalDateTime now() { return LocalDateTime.now(); }

    /** 當前日期 (LocalDate) */
    public static LocalDate today() { return LocalDate.now(); }

    /** 格式化 LocalDateTime -> String */
    public static String format(LocalDateTime dt, DateTimeFormatter fmt) {
        return dt == null ? "" : dt.format(fmt);
    }

    /** 解析 String -> LocalDateTime */
    public static LocalDateTime parse(String str, DateTimeFormatter fmt) {
        return str == null || str.isEmpty() ? null : LocalDateTime.parse(str, fmt);
    }

    /** 日期加減天數 */
    public static LocalDate plusDays(LocalDate date, long days) {
        return date == null ? null : date.plusDays(days);
    }

    /** 兩日期相差天數 */
    public static long daysBetween(LocalDate start, LocalDate end) {
        if (start == null || end == null) return 0;
        return ChronoUnit.DAYS.between(start, end);
    }

    /** 判斷閏年 */
    public static boolean isLeapYear(int year) {
        return Year.of(year).isLeap();
    }

    /** 獲取某年某月最後一天 */
    public static int lastDayOfMonth(int year, int month) {
        return YearMonth.of(year, month).lengthOfMonth();
    }

    /** 判斷兩日期是否同一周 (ISO-8601 週一為周首) */
    public static boolean isSameWeek(LocalDate d1, LocalDate d2) {
        if (d1 == null || d2 == null) return false;
        WeekFields wf = WeekFields.ISO;
        return d1.getYear() == d2.getYear()
            && d1.get(wf.weekOfYear()) == d2.get(wf.weekOfYear());
    }

    /** 獲取某日期所在周的周一日期 */
    public static LocalDate mondayOfWeek(LocalDate date) {
        return date.with(WeekFields.ISO.dayOfWeek(), 1); // 1=週一
    }

    /** 時間戳 <-> LocalDateTime (系統默認時區) */
    public static LocalDateTime toLocalDateTime(long epochMilli) {
        return Instant.ofEpochMilli(epochMilli).atZone(ZoneId.systemDefault()).toLocalDateTime();
    }
    public static long toEpochMilli(LocalDateTime ldt) {
        return ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    /** 生成唯一流水號:yyyyMMddHHmmss + 隨機數 */
    public static String genSerial(int randomDigits) {
        String base = LocalDateTime.now().format(FMT_COMPACT);
        if (randomDigits <= 0) return base;
        StringBuilder sb = new StringBuilder(base);
        for (int i = 0; i < randomDigits; i++) sb.append((int)(Math.random() * 10));
        return sb.toString();
    }
}

四、舊系統遷移建議

  1. 邊界轉換:Controller/Service 入口統一用 DateUtils.toLocalDateTime(date.getTime()) 轉為 LocalDateTime,內部業務邏輯全用新 API,出口再 Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()) 轉回 Date。
  2. 資料庫層:MyBatis/JPA 2.2+ 已原生支援 LocalDateTime、LocalDate、Instant,直接映射 DATETIME/DATE/TIMESTAMP,無需手動轉換。
  3. 序列化:Jackson 2.10+ 預設將 LocalDateTime 序列化為 ISO-8601 字符串(2024-06-15T14:30:00),前端解析友好。
  4. 棄用 SimpleDateFormat:全量替換為 DateTimeFormatter,消除線程安全隱患。

五、總結

Java 8 java.time 包以不可變、線程安全、領域驅動設計徹底解決了舊 API 的痛點。新項目無理由不使用;舊項目通過邊界轉換漸進式遷移。掌握 LocalDate、LocalDateTime、ZonedDateTime、Instant、Duration/Period、DateTimeFormatter 核心類,配合 ChronoUnit、TemporalAdjusters 完成 90% 以上的日期時間業務場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved