業務開發中頻繁遇到「兩個日期誰早誰晚」、「相差幾天/幾小時」、「兩個時間區間是否重疊」等需求。本文整理一套開箱即用的 DateUtils 工具類,基於 SimpleDateFormat + Calendar 實現,適配 Java 7+ 環境;文末另附 Java 8 java.time 現代寫法供升級參考。
public final class DateUtils {
private DateUtils() {}
private static final String FMT_DT = "yyyy-MM-dd HH:mm:ss";
private static final String FMT_D = "yyyy-MM-dd";
private static final String FMT_H = "yyyy-MM-dd HH";
/** 1. 比較兩個日期字符串大小
* @return 1: dt1>dt2, -1: dt1 overlap(String s1, String e1, String s2, String e2) {
try {
SimpleDateFormat sdf = new SimpleDateFormat(FMT_D);
Date d1 = sdf.parse(s1), d2 = sdf.parse(e1);
Date d3 = sdf.parse(s2), d4 = sdf.parse(e3);
// 區間標準化:確保 start <= end
if (d1.after(d2)) { Date tmp = d1; d1 = d2; d2 = tmp; }
if (d3.after(d4)) { Date tmp = d3; d3 = d4; d4 = tmp; }
// 無重疊判斷
if (d2.before(d3) || d4.before(d1)) return Collections.emptyMap();
// 有重疊:取較晚的開始、較早的結束
Date overlapStart = d1.after(d3) ? d1 : d3;
Date overlapEnd = d2.before(d4) ? d2 : d4;
Map res = new HashMap<>();
res.put("startDate", sdf.format(overlapStart));
res.put("endDate", sdf.format(overlapEnd));
return res;
} catch (ParseException ex) {
throw new IllegalArgumentException("日期格式錯誤", ex);
}
}
}
public class DateUtilsTest {
@Test
public void testCompare() {
assertEquals(1, DateUtils.compare("2023-05-01 12:00:00", "2023-04-30 12:00:00"));
assertEquals(0, DateUtils.compare("2023-05-01 12:00:00", "2023-05-01 12:00:00"));
}
@Test
public void testDaysBetween() {
assertEquals(30, DateUtils.daysBetween("2023-04-01", "2023-05-01"));
assertEquals(-5, DateUtils.daysBetween("2023-05-10", "2023-05-05")); // 負數表示前後順序
}
@Test
public void testOverlap() {
Map ov = DateUtils.overlap(
"2023-05-01", "2023-05-10",
"2023-05-05", "2023-05-15");
assertEquals("2023-05-05", ov.get("startDate"));
assertEquals("2023-05-10", ov.get("endDate"));
}
}
Java 8 引入的 java.time 包線程安全、API 更直觀,建議新項目直接使用:
public final class ModernDateUtils {
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 int compare(String dt1, String dt2) {
LocalDateTime l1 = LocalDateTime.parse(dt1, DT_FMT);
LocalDateTime l2 = LocalDateTime.parse(dt2, DT_FMT);
return l1.compareTo(l2);
}
public static long daysBetween(String d1, String d2) {
LocalDate ld1 = LocalDate.parse(d1, D_FMT);
LocalDate ld2 = LocalDate.parse(d2, D_FMT);
return ChronoUnit.DAYS.between(ld1, ld2);
}
public static long hoursBetween(String dt1, String dt2) {
LocalDateTime l1 = LocalDateTime.parse(dt1, DT_FMT);
LocalDateTime l2 = LocalDateTime.parse(dt2, DT_FMT);
return ChronoUnit.HOURS.between(l1, l2);
}
public static Map overlap(String s1, String e1, String s2, String e2) {
LocalDate a1 = LocalDate.parse(s1, D_FMT), a2 = LocalDate.parse(e1, D_FMT);
LocalDate b1 = LocalDate.parse(s2, D_FMT), b2 = LocalDate.parse(e2, D_FMT);
if (a1.isAfter(a2)) { LocalDate t = a1; a1 = a2; a2 = t; }
if (b1.isAfter(b2)) { LocalDate t = b1; b1 = b2; b2 = t; }
if (a2.isBefore(b1) || b2.isBefore(a1)) return Collections.emptyMap();
LocalDate start = a1.isAfter(b1) ? a1 : b1;
LocalDate end = a2.isBefore(b2) ? a2 : b2;
return Map.of("startDate", start.format(D_FMT), "endDate", end.format(D_FMT));
}
}
ThreadLocal 或每次 new 實例,或直接遷移至 DateTimeFormatter(線程安全)。TimeZone / ZoneId,避免夏令時導致天數計算偏差。將上述工具類納入項目公共模塊,即可用一行代碼解決 90% 的日期比較計算需求,減少重複造輪子與 Bug。