業務常需計算兩個時間點的差值並以「時:分:秒」格式顯示。本文提供健壯、可複用的工具類實作,處理邊界條件、格式化細節與效能考量。
import java.util.Date;
import java.text.MessageFormat;
public class TimeUtils {
/**
* 計算時間差,返回 "HH:mm:ss" 或 "mm:ss" 或 "ss" 格式
* @param begin 開始時間
* @param end 結束時間
* @return 格式化時間差字符串
*/
public static String getTimeDifference(Date begin, Date end) {
if (begin == null || end == null) {
throw new IllegalArgumentException("時間參數不能為 null");
}
if (end.before(begin)) {
throw new IllegalArgumentException("結束時間不能早於開始時間");
}
long diffMillis = end.getTime() - begin.getTime();
long totalSeconds = diffMillis / 1000;
long hours = totalSeconds / 3600;
long minutes = (totalSeconds % 3600) / 60;
long seconds = totalSeconds % 60;
StringBuilder sb = new StringBuilder();
if (hours > 0) {
sb.append(hours).append(':');
// 分鐘、秒雙位數補零
sb.append(String.format("%02d:%02d", minutes, seconds));
} else if (minutes > 0) {
sb.append(minutes).append(':');
sb.append(String.format("%02d", seconds));
} else {
sb.append(seconds);
}
return sb.toString();
}
/**
* 通用版本:支援天、時、分、秒、毫秒
*/
public static String formatDuration(long millis) {
if (millis < 0) throw new IllegalArgumentException("毫秒數不能為負");
long days = millis / (24 * 3600 * 1000);
millis %= 24 * 3600 * 1000;
long hours = millis / (3600 * 1000);
millis %= 3600 * 1000;
long minutes = millis / (60 * 1000);
millis %= 60 * 1000;
long seconds = millis / 1000;
long ms = millis % 1000;
StringBuilder sb = new StringBuilder();
if (days > 0) sb.append(days).append("天");
if (hours > 0) sb.append(hours).append("時");
if (minutes > 0) sb.append(minutes).append("分");
if (seconds > 0) sb.append(seconds).append("秒");
if (ms > 0 || sb.length() == 0) sb.append(ms).append("毫秒");
return sb.toString();
}
/**
* Java 8+ 現代寫法:Duration API
*/
public static String formatDurationModern(java.time.Duration duration) {
long seconds = duration.getSeconds();
long hours = seconds / 3600;
long minutes = (seconds % 3600) / 60;
long secs = seconds % 60;
int nanos = duration.getNano();
return String.format("%02d:%02d:%02d.%03d", hours, minutes, secs, nanos / 1_000_000);
}
}
public class TimeUtilsTest {
@Test
public void testBasic() {
Date start = new Date(1000 * 60 * 60); // 1小時
Date end = new Date(1000 * 60 * 60 * 2 + 1000 * 30 + 1000 * 15); // 2小時30分15秒
assertEquals("1:30:15", TimeUtils.getTimeDifference(start, end));
}
@Test
public void testLessThanHour() {
Date start = new Date(0);
Date end = new Date(1000 * 45 + 500); // 45.5秒
assertEquals("45", TimeUtils.getTimeDifference(start, end)); // 僅秒
}
@Test
public void testFormatDuration() {
assertEquals("1天2時3分4秒500毫秒",
TimeUtils.formatDuration(1*24*3600*1000 + 2*3600*1000 + 3*60*1000 + 4*1000 + 500));
}
}
| 陷阱 | 後果 | 修正 |
|---|---|---|
直接用 Date 相減 | 得到毫秒數,非直觀 | 封裝工具方法 |
| 忽略時區影響 | 跨天/跨 DST 計算錯誤 | 使用 Instant/ZonedDateTime |
| 手動拼接字符串 | 易漏補零、格式不一致 | String.format/DateTimeFormatter |
| 不驗證參數順序 | 負值導致邏輯錯誤 | 前置檢查拋出異常 |
import java.time.*;
import java.time.temporal.ChronoUnit;
public class ModernTimeUtils {
public static String between(Instant start, Instant end) {
Duration d = Duration.between(start, end);
return formatDuration(d);
}
public static String between(LocalDateTime start, LocalDateTime end, ZoneId zone) {
Duration d = Duration.between(start.atZone(zone), end.atZone(zone));
return formatDuration(d);
}
private static String formatDuration(Duration d) {
long hours = d.toHours();
long minutes = d.toMinutesPart(); // Java 9+
long seconds = d.toSecondsPart(); // Java 9+
int millis = d.toMillisPart(); // Java 9+
if (hours > 0) return String.format("%d:%02d:%02d.%03d", hours, minutes, seconds, millis);
if (minutes > 0) return String.format("%d:%02d.%03d", minutes, seconds, millis);
return String.format("%d.%03d秒", seconds, millis);
}
}
時間差計算看似簡單,格式化細節、邊界條件、時區處理卻是 Bug 高發區。封裝通用工具類、編寫完整測試、優先使用 java.time,是避坑關鍵。