分佈式系統中,不同節點時區設定不一致會導致 System.currentTimeMillis() 產生的時間戳含義模糊。本文給出「本地時間 ↔ UTC 毫秒」雙向轉換的標準代碼,並說明為什麼資料庫查詢、日誌追蹤、跨機房同步**必須統一 UTC**。
Calendar.getInstance().getTimeInMillis() 寫入資料庫,同一瞬間不同節點存入的 Long 值**相差整點小時數**。import java.util.*;
public final class UtcUtil {
private UtcUtil() {}
/** 本地日期時間 → UTC 毫秒時間戳 */
public static long toUtcMillis(int year, int month, int day,
int hour, int minute, int second) {
// month: 1-12 → Calendar 0-11
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.set(year, month - 1, day, hour, minute, second);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTimeInMillis();
}
/** UTC 毫秒 → 本地 Calendar (保留時區資訊) */
public static Calendar fromUtcMillis(long utcMillis) {
Calendar cal = Calendar.getInstance(); // 本地預設時區
cal.setTimeInMillis(utcMillis);
return cal;
}
/** UTC 毫秒 → 指定時區 Calendar */
public static Calendar fromUtcMillis(long utcMillis, String zoneId) {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(zoneId));
cal.setTimeInMillis(utcMillis);
return cal;
}
/** 格式化 UTC 毫秒為 ISO-8601 字串 (含時區) */
public static String formatIso8601(long utcMillis) {
return new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
.format(new Date(utcMillis));
}
}
| 策略 | 做法 | 優點 |
|---|---|---|
| 存儲 UTC TIMESTAMP | MySQL: TIMESTAMP (自動轉換) / PostgreSQL: TIMESTAMPTZ | 資料庫層自動處理時區,應用層無感 |
| 存儲 BIGINT (UTC ms) | 統一用 BIGINT 存 UTC 毫秒,應用層轉換 | 跨資料庫可移植、無時區歧義、索引效能高 |
| 存儲 VARCHAR ISO-8601 | 2024-01-01T08:00:00+08:00 | 人類可讀、自帶時區、JSON 友好 |
import java.time.*;
// 本地 → UTC Instant
Instant utc = LocalDateTime.of(2024, 1, 1, 8, 0)
.atZone(ZoneId.of("Asia/Shanghai"))
.toInstant();
long ms = utc.toEpochMilli();
// UTC Instant → 任意時區
ZonedDateTime sh = Instant.ofEpochMilli(ms)
.atZone(ZoneId.of("Asia/Shanghai"));
ZonedDateTime ny = Instant.ofEpochMilli(ms)
.atZone(ZoneId.of("America/New_York"));
// 格式化
DateTimeFormatter fmt = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
System.out.println(sh.format(fmt)); // 2024-01-01T08:00:00+08:00
System.out.println(ny.format(fmt)); // 2024-01-01T19:00:00-05:00
Date.getTimezoneOffset():已棄用、依賴系統時區、不可靠。TimeZone/ZoneId。%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX},所有節點日誌可直接合併排序。@Scheduled 配置 zone = "UTC",避免夏令時跳變導致重跑/漏跑。