首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 時間戳統一用 UTC:跨時區系統的 Long 型 timecode 正確轉換姿勢

     編輯:Java 相關知識

分佈式系統中,不同節點時區設定不一致會導致 System.currentTimeMillis() 產生的時間戳含義模糊。本文給出「本地時間 ↔ UTC 毫秒」雙向轉換的標準代碼,並說明為什麼資料庫查詢、日誌追蹤、跨機房同步**必須統一 UTC**。

問題場景

  • 應用部署於不同時區伺服器 (如上海 UTC+8、紐約 UTC-5、倫敦 UTC+0)。
  • 直接用 Calendar.getInstance().getTimeInMillis() 寫入資料庫,同一瞬間不同節點存入的 Long 值**相差整點小時數**。
  • 查詢「2024-01-01 00:00:00 到 01:00:00 的數據」時,節點間條件不一致,導致數據缺漏/重複。

標準轉換工具類

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 TIMESTAMPMySQL: TIMESTAMP (自動轉換) / PostgreSQL: TIMESTAMPTZ資料庫層自動處理時區,應用層無感
存儲 BIGINT (UTC ms)統一用 BIGINT 存 UTC 毫秒,應用層轉換跨資料庫可移植、無時區歧義、索引效能高
存儲 VARCHAR ISO-86012024-01-01T08:00:00+08:00人類可讀、自帶時區、JSON 友好

Java 8+ 現代 API (強烈推薦)

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

避坑清單

  1. 勿用 Date.getTimezoneOffset():已棄用、依賴系統時區、不可靠。
  2. 勿手動加減小時:夏令時 (DST) 會導致錯誤,務必用 TimeZone/ZoneId。
  3. 日誌統一 UTC:Logback/Log4j2 設定 %d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX},所有節點日誌可直接合併排序。
  4. 定時任務用 UTC:Quartz/Spring @Scheduled 配置 zone = "UTC",避免夏令時跳變導致重跑/漏跑。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved