日期時間處理是業務開發的高頻需求。雖然 JDK 8+ 推出全新 java.time 套件,但大量存量專案仍使用 java.util.Date/Calendar。本文提供一個生產級 DateUtil 工具類,涵蓋格式化、解析、運算、比較、邊界計算等 40+ 實用方法,並對比新舊 API 用法。
package com.util;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.*;
public class DateUtil {
private static final Logger log = LoggerFactory.getLogger(DateUtil.class);
// ===== 常用格式常量 =====
public static final String DATE_PATTERN = "yyyy-MM-dd";
public static final String TIME_PATTERN = "HH:mm:ss";
public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String DATETIME_MS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS";
public static final String MONTH_PATTERN = "yyyy-MM";
public static final String COMPACT_DATETIME = "yyyyMMddHHmmss";
// ===== 執行緒安全的格式化器快取 =====
private static final ThreadLocal DATE_FMT =
ThreadLocal.withInitial(() -> new SimpleDateFormat(DATE_PATTERN));
private static final ThreadLocal DATETIME_FMT =
ThreadLocal.withInitial(() -> new SimpleDateFormat(DATETIME_PATTERN));
private static final ThreadLocal TIME_FMT =
ThreadLocal.withInitial(() -> new SimpleDateFormat(TIME_PATTERN));
// ===== 1. 當前時間獲取 =====
public static Date now() { return new Date(); }
public static String nowDate() { return DATE_FMT.get().format(now()); }
public static String nowTime() { return TIME_FMT.get().format(now()); }
public static String nowDateTime() { return DATETIME_FMT.get().format(now()); }
public static String nowCompact() {
return new SimpleDateFormat(COMPACT_DATETIME).format(now());
}
// ===== 2. 格式化:Date → String =====
public static String format(Date date, String pattern) {
if (date == null) return "";
try {
return new SimpleDateFormat(pattern).format(date);
} catch (Exception e) {
log.error("格式化失敗", e);
return "";
}
}
public static String formatDate(Date date) { return format(date, DATE_PATTERN); }
public static String formatDateTime(Date date) { return format(date, DATETIME_PATTERN); }
public static String formatTime(Date date) { return format(date, TIME_PATTERN); }
// ===== 3. 解析:String → Date =====
public static Date parse(String dateStr, String pattern) {
if (StringUtils.isBlank(dateStr)) return null;
try {
return new SimpleDateFormat(pattern).parse(dateStr);
} catch (ParseException e) {
log.error("解析失敗:{}", dateStr, e);
return null;
}
}
public static Date parseDate(String dateStr) { return parse(dateStr, DATE_PATTERN); }
public static Date parseDateTime(String dateStr) { return parse(dateStr, DATETIME_PATTERN); }
public static Date parseTime(String dateStr) { return parse(dateStr, TIME_PATTERN); }
// ===== 4. 日期運算 (基於 Calendar) =====
private static Calendar toCal(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
}
public static Date addDays(Date date, int days) {
Calendar cal = toCal(date);
cal.add(Calendar.DAY_OF_MONTH, days);
return cal.getTime();
}
public static Date addMonths(Date date, int months) {
Calendar cal = toCal(date);
cal.add(Calendar.MONTH, months);
return cal.getTime();
}
public static Date addYears(Date date, int years) {
Calendar cal = toCal(date);
cal.add(Calendar.YEAR, years);
return cal.getTime();
}
public static Date addHours(Date date, int hours) {
Calendar cal = toCal(date);
cal.add(Calendar.HOUR_OF_DAY, hours);
return cal.getTime();
}
public static Date addMinutes(Date date, int minutes) {
Calendar cal = toCal(date);
cal.add(Calendar.MINUTE, minutes);
return cal.getTime();
}
public static Date addSeconds(Date date, int seconds) {
Calendar cal = toCal(date);
cal.add(Calendar.SECOND, seconds);
return cal.getTime();
}
// ===== 5. 日期差值計算 =====
public static long diffDays(Date start, Date end) {
if (start == null || end == null) return 0;
long diffMs = end.getTime() - start.getTime();
return diffMs / (24 * 60 * 60 * 1000);
}
public static double diffDaysPrecise(Date start, Date end) {
if (start == null || end == null) return 0;
return (end.getTime() - start.getTime()) / (24.0 * 60 * 60 * 1000);
}
public static int diffMonths(Date start, Date end) {
if (start == null || end == null) return 0;
Calendar s = toCal(start);
Calendar e = toCal(end);
return (e.get(Calendar.YEAR) - s.get(Calendar.YEAR)) * 12
+ (e.get(Calendar.MONTH) - s.get(Calendar.MONTH));
}
// ===== 6. 邊界時間 (當天 00:00:00 / 23:59:59) =====
public static Date startOfDay(Date date) {
Calendar cal = toCal(date);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
public static Date endOfDay(Date date) {
Calendar cal = toCal(date);
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 999);
return cal.getTime();
}
public static Date startOfMonth(Date date) {
Calendar cal = toCal(date);
cal.set(Calendar.DAY_OF_MONTH, 1);
return startOfDay(cal.getTime());
}
public static Date endOfMonth(Date date) {
Calendar cal = toCal(date);
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
return endOfDay(cal.getTime());
}
// ===== 7. 實用判斷 =====
public static boolean isSameDay(Date d1, Date d2) {
if (d1 == null || d2 == null) return false;
return formatDate(d1).equals(formatDate(d2));
}
public static boolean isLeapYear(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
public static int daysInMonth(int year, int month) {
Calendar cal = Calendar.getInstance();
cal.set(year, month - 1, 1);
return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
}
public static boolean isBefore(Date d1, Date d2) {
return d1 != null && d2 != null && d1.before(d2);
}
public static boolean isAfter(Date d1, Date d2) {
return d1 != null && d2 != null && d1.after(d2);
}
public static boolean isBetween(Date target, Date start, Date end) {
return isAfter(target, start) && isBefore(target, end);
}
// ===== 8. 字串比較 (避免解析開銷) =====
public static int compareDateStr(String d1, String d2) {
if (StringUtils.isBlank(d1) || StringUtils.isBlank(d2)) return 0;
return d1.compareTo(d2); // ISO 格式 yyyy-MM-dd 可直接字串比較
}
// ===== 9. 時間戳工具 =====
public static long currentTimestamp() { return System.currentTimeMillis(); }
public static String timestampToDateTime(long ts) {
return format(new Date(ts), DATETIME_PATTERN);
}
// ===== 10. 相對時間描述 (如 "3分鐘前", "2天前") =====
public static String relativeTime(Date date) {
if (date == null) return "";
long diff = System.currentTimeMillis() - date.getTime();
if (diff < 60_000) return "剛剛";
if (diff < 3_600_000) return (diff / 60_000) + "分鐘前";
if (diff < 86_400_000) return (diff / 3_600_000) + "小時前";
if (diff < 604_800_000) return (diff / 86_400_000) + "天前";
return formatDate(date);
}
}
| 舊 API (DateUtil) | 新 API (java.time) | 優勢 |
|---|---|---|
| DateUtil.nowDateTime() | LocalDateTime.now().format(DTF) | 不可變、執行緒安全、流式 API |
| DateUtil.addDays(d, 3) | localDate.plusDays(3) | 鏈式呼叫、不修改原物件 |
| DateUtil.diffDays(d1, d2) | ChronoUnit.DAYS.between(d1, d2) | 精確、支援納秒、時區感知 |
| DateUtil.startOfDay(d) | localDate.atStartOfDay() | 語意清晰 |
| DateUtil.parseDateTime(s) | LocalDateTime.parse(s, DTF) | 嚴格解析、異常資訊豐富 |
| Calendar 時區坑 | ZonedDateTime / OffsetDateTime | 正確處理夏令時、跨時區 |
// 現代日期工具類 (JDK 8+)
public class ModernDateUtil {
private static final DateTimeFormatter DT_FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter D_FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static String now() {
return LocalDateTime.now().format(DT_FMT);
}
public static LocalDateTime parse(String str) {
return LocalDateTime.parse(str, DT_FMT);
}
public static String format(LocalDateTime ldt) {
return ldt.format(DT_FMT);
}
public static long daysBetween(LocalDate start, LocalDate end) {
return ChronoUnit.DAYS.between(start, end);
}
public static LocalDateTime startOfDay(LocalDate date) {
return date.atStartOfDay();
}
public static String relative(LocalDateTime past) {
Duration d = Duration.between(past, LocalDateTime.now());
if (d.toMinutes() < 1) return "剛剛";
if (d.toHours() < 1) return d.toMinutes() + "分鐘前";
if (d.toDays() < 1) return d.toHours() + "小時前";
return d.toDays() + "天前";
}
}
public static void main(String[] args) {
System.out.println("現在:" + DateUtil.nowDateTime());
System.out.println("3天後:" + DateUtil.formatDate(DateUtil.addDays(new Date(), 3)));
System.out.println("本月最後一天:" + DateUtil.formatDate(DateUtil.endOfMonth(new Date())));
System.out.println("相對時間:" + DateUtil.relativeTime(DateUtil.addHours(new Date(), -2)));
System.out.println("是否閏年:" + DateUtil.isLeapYear(2024));
}