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

Java 日期時間處理完全指南:Date、Calendar 核心操作與最佳實踐

     編輯:Java 相關知識

前言

Java 日期時間 API 經歷了三代演進:java.util.Date/Calendar (1.0/1.1,**缺陷眾多**)、Joda-Time (事實標準)、java.time (JDK 8+,**現代標準**)。本文聚焦**舊 API (Date/Calendar) 核心操作實戰**,並給出**遷移至 java.time 的最佳實踐建議**,滿足維護老項目與新開發雙重需求。

一、 Date 與 Calendar 核心類速覽

類包特點線程安全
Datejava.util時間戳毫秒數包裝,大多方法已棄用,僅作數據傳遞可變,非線程安全
Calendarjava.util抽象基類,GregorianCalendar 實現,字段操作、日期運算可變,非線程安全
SimpleDateFormatjava.text格式化/解析,**非線程安全**,多線程需 ThreadLocal 或局部變量非線程安全

二、 核心操作代碼大全 (工具類風格)


import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

public class DateUtils {
    // 線程安全的格式化器持有方式:ThreadLocal
    private static final ThreadLocal FMT_SECOND = ThreadLocal.withInitial(
        () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)
    );
    private static final ThreadLocal FMT_DAY = ThreadLocal.withInitial(
        () -> new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA)
    );
    
    // 1. Date -> String
    public static String format(Date date, String pattern) {
        if (date == null) return "";
        return new SimpleDateFormat(pattern, Locale.CHINA).format(date);
    }
    public static String formatFull(Date date) { return FMT_SECOND.get().format(date); }
    public static String formatDay(Date date) { return FMT_DAY.get().format(date); }
    
    // 2. String -> Date
    public static Date parse(String dateStr, String pattern) throws ParseException {
        if (dateStr == null || dateStr.isEmpty()) return null;
        return new SimpleDateFormat(pattern, Locale.CHINA).parse(dateStr);
    }
    public static Date parseFull(String dateStr) throws ParseException { return parse(dateStr, "yyyy-MM-dd HH:mm:ss"); }
    public static Date parseDay(String dateStr) throws ParseException { return parse(dateStr, "yyyy-MM-dd"); }
    
    // 3. Date <-> Calendar 互轉
    public static Calendar toCalendar(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return cal;
    }
    public static Date toDate(Calendar cal) { return cal.getTime(); }
    
    // 4. 日期運算 (Calendar 強項)
    public static Calendar addDays(Calendar cal, int days) {
        cal.add(Calendar.DAY_OF_MONTH, days);
        return cal;
    }
    public static Calendar addMonths(Calendar cal, int months) {
        cal.add(Calendar.MONTH, months);
        return cal;
    }
    public static Calendar addYears(Calendar cal, int years) {
        cal.add(Calendar.YEAR, years);
        return cal;
    }
    
    // 5. 獲取本月第一天/最後一天
    public static Calendar firstDayOfMonth(Calendar cal) {
        cal.set(Calendar.DAY_OF_MONTH, 1);
        return cal;
    }
    public static Calendar lastDayOfMonth(Calendar cal) {
        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
        return cal;
    }
    public static int getDaysInMonth(int year, int month) { // month 1-12
        Calendar cal = Calendar.getInstance();
        cal.set(year, month - 1, 1);
        return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
    }
    
    // 6. 獲取本周一 (Locale 相關)
    public static Calendar mondayOfWeek(Calendar cal) {
        cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
        return cal;
    }
    
    // 7. 計算兩日期差值 (天/月/年)
    public static long diffDays(Date start, Date end) {
        return (end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000);
    }
    public static int[] diffYMD(Date start, Date end) {
        Calendar c1 = toCalendar(start);
        Calendar c2 = toCalendar(end);
        int years = c2.get(Calendar.YEAR) - c1.get(Calendar.YEAR);
        int months = years * 12 + c2.get(Calendar.MONTH) - c1.get(Calendar.MONTH);
        long days = diffDays(start, end);
        return new int[]{years, months, (int)days};
    }
    
    // 8. 判斷是否同一天/同月/同年
    public static boolean isSameDay(Date d1, Date d2) {
        return FMT_DAY.get().format(d1).equals(FMT_DAY.get().format(d2));
    }
}

三、 避坑指南:Date/Calendar 十大雷區

雷區後果正確姿勢
SimpleDateFormat 定為 static 共享多線程下解析/格式化錯誤、拋異常、數據錯亂用 ThreadLocal、局部變量、或 JDK 8 DateTimeFormatter (線程安全)
月份 0-11 陷阱1月=0,12月=11,極易寫錯常量 Calendar.JANUARY... 或 month - 1 寫死註釋
Date 可變性被意外修改導致業務邏輯錯誤防禦性拷貝:new Date(date.getTime());或遷移不可變 LocalDate
Calendar.getInstance() 默認時區跨時區部署/服務器時區變更導致邏輯偏差顯式設置時區 cal.setTimeZone(TimeZone.getTimeZone("UTC"))
年份 getYear() 返回年份-19002023 變 123 (已棄用)用 cal.get(Calendar.YEAR)
解析異常 ParseException 未處理運行時崩潰try-catch 包裝為非受檢異常或 Optional 返回
跨夏令時 (DST) 計算一天非 24h,加減天數出錯用 Calendar.add(DAY_OF_MONTH) 自動處理,勿手算毫秒
毫秒精度丟失 (數據庫 Timestamp)Date 只有毫秒,DB 可能微秒/納秒用 java.sql.Timestamp 或 JDK 8 LocalDateTime
字符串格式不匹配yyyy-MM-dd 解析 2023/01/01 失敗嚴格校驗輸入格式,或多模式嘗試解析

四、 現代化遷移:拥抱 java.time (JDK 8+)

新項目強制、老項目漸進式遷移。核心類對應:

舊 API新 API (java.time)特性
DateInstant (時間戳)、LocalDateTime (本地日期時間)不可變、線程安全、納秒精度
CalendarZonedDateTime (帶時區)、LocalDate/LocalTime領域模型清晰、API 豐富
SimpleDateFormatDateTimeFormatter線程安全、預定義格式、解析更嚴格
TimeZoneZoneId/ZoneOffsetIANA 時區庫、歷史規則完整

新 API 典型代碼


// 當前時間
LocalDateTime now = LocalDateTime.now();
ZonedDateTime nowZ = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));

// 格式化 (線程安全!)
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String str = now.format(fmt);
LocalDateTime parsed = LocalDateTime.parse(str, fmt);

// 日期運算 (流式、不可變)
LocalDateTime nextWeek = now.plusWeeks(1);
LocalDate firstDay = now.with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDay = now.with(TemporalAdjusters.lastDayOfMonth());

// 時區轉換
Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
ZonedDateTime shanghai = instant.atZone(ZoneId.of("Asia/Shanghai"));

// 期間/持續時間
Period period = Period.between(startDate, endDate); // 年月日
Duration duration = Duration.between(startTime, endTime); // 時分秒納秒

// 舊新互轉
Date oldDate = Date.from(instant);
Instant inst = oldDate.toInstant();

五、 總結

  1. 維護老代碼:用 ThreadLocal、防禦性拷貝、注意月份 0 基、時區顯式化。
  2. 新代碼/重構:直接用 java.time (LocalDateTime, ZonedDateTime, DateTimeFormatter),不可變、線程安全、API 直觀、功能強大。
  3. 數據庫交互:JDBC 4.2+ 直接支持 LocalDateTime/Instant (setObject/getObject),無需轉 Date。
  4. 序列化/傳輸:ISO-8601 字符串 (2023-10-01T12:00:00+08:00) 作為標準格式,Jackson/Gson 默認支持 java.time。

日期時間處理看似瑣碎,實則是業務正確性的基石。**拋棄可變舊 API,擁抱不可變 java.time**,是現代 Java 開發者的必修課。

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