跨語言系統整合 (Java ↔ .NET) 時,時間戳差異是經典坑。本文揭示 System.currentTimeMillis() 與 .NET DateTime.Now 的本質差異,提供三種 JVM 級與代碼級指定時區方案,並給出最佳實踐建議。
// Java
long ts = System.currentTimeMillis(); // 相對 UTC 1970-01-01 00:00:00 的毫秒數,與時區無關!
// .NET
long ticks = DateTime.Now.Ticks; // 本地時區下的 100ns ticks,含時區偏移
**關鍵結論**:currentTimeMillis() 返回的是 **UTC 瞬時點**,非本地時間。若直接除 1000 給 .NET,雙方解析時若未統一時區,會產生「時區偏移 × 2」的誤差 (如 CST +8 小時 → 差 16 小時)。
java -Duser.timezone=GMT+08 -jar app.jar
# 或
java -Duser.timezone=Asia/Shanghai -jar app.jar
TimeZone.getDefault()、Calendar、SimpleDateFormat、java.time 等所有時間 APIENV TZ=Asia/Shanghai + ENTRYPOINT ["java", "-Duser.timezone=Asia/Shanghai", ...]// 必須在任何時間 API 使用前調用,建議在 main() 首行或靜態初始化塊
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
// 或 Java 8+
ZoneId.systemDefault(); // 僅讀,不可設定
// 需用反射或 -Duser.timezone 修改 java.time 默認時區
⚠️ **陷阱**:java.time (JSR-310) 類在類加載時快取默認時區,運行時 TimeZone.setDefault **不影響** LocalDateTime.now() 等新 API。生產環境**強烈建議方案一**。
// 粗暴加 8 小時毫秒數
long timestamp = System.currentTimeMillis() + 8 * 3600 * 1000;
// 或
long timestamp = Instant.now().atZone(ZoneId.of("Asia/Shanghai")).toEpochSecond();
缺點:夏令時、時區規則變更需手動維護,分散在業務代碼中極難審計。
| 場景 | Java 端 | .NET 端 | 說明 |
|---|---|---|---|
| 傳遞時間戳 | Instant.now().toEpochMilli() | DateTimeOffset.FromUnixTimeMilliseconds(ms).UtcDateTime | 雙方統一用 **UTC 毫秒數**,零時區歧義 |
| 傳遞字串 | OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) | DateTimeOffset.ParseExact(str, "O", CultureInfo.InvariantCulture) | ISO-8601 含偏移量,雙方原生支援 |
| 資料庫存儲 | TIMESTAMP WITH TIME ZONE / DATETIMEOFFSET | 同左 | 資料庫層統一時區感知類型 |
// 獲取當前瞬間 (UTC)
Instant now = Instant.now();
// 轉為指定時區的本地時間
ZonedDateTime shanghai = now.atZone(ZoneId.of("Asia/Shanghai"));
LocalDateTime local = shanghai.toLocalDateTime(); // 2024-06-15T14:30:00
// 格式化輸出 (含偏移)
String iso = shanghai.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); // 2024-06-15T14:30:00+08:00
// 解析帶偏移字串
OffsetDateTime parsed = OffsetDateTime.parse("2024-06-15T14:30:00+08:00");
Instant utc = parsed.toInstant(); // 回到 UTC 瞬間
-Duser.timezone=Asia/Shanghai (或 UTC)TIMESTAMPTZ / DATETIMEOFFSET)%d{yyyy-MM-dd HH:mm:ss.SSS Z} 含時區時間處理的核心原則:**內部統一 UTC,邊界顯式轉換**。JVM 參數 -Duser.timezone 是最低成本的全局保障,配合 java.time API 的不可變、時區感知設計,能徹底杜絕「時間跳躍」類 Bug。