系統間交互常用 long 毫秒時間戳(13 位),日誌/界面/數據庫需可讀字符串。Java 8+ java.time 包為首選,舊版 SimpleDateFormat 線程不安全、性能差。
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
public final class TimeUtils {
private TimeUtils() {} // 工具類禁止實例化
// 線程安全、不可變、高性能
private static final DateTimeFormatter DEFAULT_FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
private static final ZoneId SYSTEM_ZONE = ZoneId.systemDefault();
/** 毫秒時間戳 → 格式化字符串 */
public static String toString(long epochMilli, String pattern) {
DateTimeFormatter fmt = pattern != null ? DateTimeFormatter.ofPattern(pattern) : DEFAULT_FMT;
return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), SYSTEM_ZONE).format(fmt);
}
/** 毫秒時間戳 → 默認格式字符串 */
public static String toString(long epochMilli) {
return toString(epochMilli, null);
}
/** 格式化字符串 → 毫秒時間戳 */
public static long toEpochMilli(String timeStr, String pattern) {
DateTimeFormatter fmt = pattern != null ? DateTimeFormatter.ofPattern(pattern) : DEFAULT_FMT;
LocalDateTime ldt = LocalDateTime.parse(timeStr, fmt);
return ldt.atZone(SYSTEM_ZONE).toInstant().toEpochMilli();
}
/** 當前時間毫秒戳 */
public static long now() {
return System.currentTimeMillis();
}
/** 當前時間格式化字符串 */
public static String nowString() {
return toString(now());
}
/** 當前時間格式化字符串(自定義模式) */
public static String nowString(String pattern) {
return toString(now(), pattern);
}
/** 相對時間計算 */
public static long plus(long epochMilli, long amount, ChronoUnit unit) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), SYSTEM_ZONE)
.plus(amount, unit).atZone(SYSTEM_ZONE).toInstant().toEpochMilli();
}
/** 解析 ISO-8601 標準格式 (2023-12-01T10:30:00Z) */
public static long parseIso(String isoStr) {
return Instant.parse(isoStr).toEpochMilli();
}
}
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;
public final class LegacyTimeUtils {
private LegacyTimeUtils() {}
private static final ThreadLocal FMT_TL = ThreadLocal.withInitial(() ->
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
public static String toString(long epochMilli) {
return FMT_TL.get().format(new Date(epochMilli));
}
public static long toEpochMilli(String timeStr) throws ParseException {
return FMT_TL.get().parse(timeStr).getTime();
}
// 若需自定義模式,建議每次 new SimpleDateFormat(pattern) 或用 FastDateFormat (Apache Commons Lang)
}
| 實現方式 | 耗時 (ms) | 線程安全 | 備註 |
|---|---|---|---|
| java.time (DateTimeFormatter) | ~120 | ✅ 不可變對象 | 首選 |
| ThreadLocal + SimpleDateFormat | ~180 | ✅ 線程隔離 | 維舊次選 |
| 共享 SimpleDateFormat + synchronized | ~450 | ✅ 鎖開銷大 | 不推薦 |
| 每次 new SimpleDateFormat | ~600 | ✅ 對象創建開銷 | 極差 |
yyyy-MM-dd HH:mm:ss.SSS // 2024-01-15 14:30:45.123 (默認)
yyyy-MM-dd HH:mm:ss // 2024-01-15 14:30:45
yyyyMMddHHmmssSSS // 20240115143045123 (無分隔符,文件名友好)
yyyy-MM-dd'T'HH:mm:ssXXX // 2024-01-15T14:30:45+08:00 (ISO-8601 帶時區)
yyyy-MM-dd // 2024-01-15 (僅日期)
HH:mm:ss.SSS // 14:30:45.123 (僅時間)
.atZone(ZoneId.of("Asia/Shanghai"))ZoneId,容器化部署時區可能不一致DATETIME 無時區,TIMESTAMP 自動轉 UTC 存儲// 1. 接口參數解析
@GetMapping("/logs")
public List query(@RequestParam("start") String startStr,
@RequestParam("end") String endStr) {
long start = TimeUtils.toEpochMilli(startStr, "yyyy-MM-dd HH:mm:ss");
long end = TimeUtils.toEpochMilli(endStr, "yyyy-MM-dd HH:mm:ss");
return logService.queryByTimeRange(start, end);
}
// 2. 日誌鏈路追蹤 ID 生成(時間戳 + 隨機)
String traceId = TimeUtils.nowString("yyyyMMddHHmmssSSS") + ThreadLocalRandom.current().nextInt(10000);
// 3. 定時任務 Cron 表達式轉換
// Cron: 0 0 2 * * ? → 每天 02:00 執行
// 下次執行時間計算可用 CronExpression (Spring) 或 Cron4j
// 4. 相對時間查詢:最近 30 分鐘
long now = TimeUtils.now();
long halfHourAgo = TimeUtils.plus(now, -30, ChronoUnit.MINUTES);
| 坑點 | 後果 | 解決 |
|---|---|---|
| SimpleDateFormat 共享實例 | 線程不安全,日期錯亂、拋異常 | ThreadLocal 或 java.time |
| 忽略時區導致跨時區錯誤 | 服務器遷移/容器化後時間偏移 | 顯式指定 ZoneId,存儲用 UTC |
| 解析異常未處理 | 脏數據導致服務不可用 | try-catch 返回 Optional/默認值 |
| 用秒級時間戳 (10 位) 混用毫秒級 | 時間偏移 1000 倍 | 統一 13 位毫秒,接口文檔明確標註 |
| 格式化模式大小寫錯誤 (mm vs MM) | 分鐘/月份混淆 | mm=分鐘, MM=月份, ss=秒, SS=毫秒 |
Java 8+ 首選 java.time 包:Instant、LocalDateTime、DateTimeFormatter 線程安全、不可變、高性能、API 清晰。舊項目用 ThreadLocal 隔離。核心原則:**存儲傳輸用 UTC 毫秒長整型、展示轉本地時區格式化字符串、顯式指定時區與模式、統一工具類封裝**。