| 類 | 用途 | 線程安全 |
|---|---|---|
java.util.Calendar | 日期運算 (增減年月日、取欄位) | ❌ (需自行同步或用 ThreadLocal) |
java.text.SimpleDateFormat | 格式化/解析 字串↔Date | ❌ (極不安全,必須每線程一實例) |
java.util.Date | 時間戳載體 (毫秒精度) | ✅ (不可變) |
public class DateTimeUtils {
// 線程安全的格式化器持有者
private static final ThreadLocal<SimpleDateFormat> FMT_YMD_HMS =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
private static final ThreadLocal<SimpleDateFormat> FMT_YMD =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd"));
/** 當前時間格式化 */
public static String now(String pattern) {
return new SimpleDateFormat(pattern).format(new Date());
}
/** 字串解析為 Date */
public static Date parse(String str, String pattern) throws ParseException {
return new SimpleDateFormat(pattern).parse(str);
}
/** 日期加減 (天/月/年) */
public static Date addDays(Date date, int days) {
Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(Calendar.DAY_OF_MONTH, days);
return c.getTime();
}
/** 設定時分秒為 00:00:00 (取日期起始) */
public static Date startOfDay(Date date) {
Calendar c = Calendar.getInstance();
c.setTime(date);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
return c.getTime();
}
/** 取指定欄位值 (年/月/日/時/分/秒) */
public static int getField(Date date, int field) {
Calendar c = Calendar.getInstance();
c.setTime(date);
return c.get(field);
}
/** 兩日期相差天數 */
public static long daysBetween(Date d1, Date d2) {
long diff = Math.abs(d1.getTime() - d2.getTime());
return TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
}
}
public static void main(String[] args) throws ParseException {
// 1. 格式化
System.out.println("現在: " + DateTimeUtils.now("yyyy-MM-dd HH:mm:ss"));
// 2. 解析
Date d = DateTimeUtils.parse("2024-12-25 08:30:00", "yyyy-MM-dd HH:mm:ss");
// 3. 日期運算
Date before100 = DateTimeUtils.addDays(d, -100);
Date after100 = DateTimeUtils.addDays(d, 100);
System.out.println("100天前: " + FMT_YMD_HMS.get().format(before100));
System.out.println("100天后: " + FMT_YMD_HMS.get().format(after100));
// 4. 截取日期部分
Date midnight = DateTimeUtils.startOfDay(new Date());
System.out.println("今日零點: " + FMT_YMD_HMS.get().format(midnight));
// 5. 差值
System.out.println("相差天數: " + DateTimeUtils.daysBetween(before100, after100)); // 200
}
Calendar.JANUARY == 0,Calendar.DECEMBER == 11。設定月份時務必用常量。SimpleDateFormat 非線程安全:並發環境下共用實例會導致數據錯亂或拋 NumberFormatException。解決:ThreadLocal、每次新建、或用 DateTimeFormatter (Java 8+)。Date 無時區概念:只存毫秒數。格式化/解析時才套用預設時區,跨時區業務請顯式設定 TimeZone。Calendar.getInstance() 返回的是具體子類 (GregorianCalendar),直接轉型安全。雖然 Java 8+ 推薦使用 java.time 包,但大量存量系統仍依賴 Calendar/SimpleDateFormat。掌握其「月份偏移、線程不安全、時區隱性」三大特性,並配合 ThreadLocal 封裝工具類,即可在舊代碼庫中穩健處理日期業務。