土薯工具 Toolshu.com 登錄 用戶注冊

時間戳傳錯單位,日期就跑到 1970 年或者五萬年後,是怎麼回事?

作者:bhnw 於 2026-04-25 10:38 發佈 5次瀏覽 收藏 (0)

日期顯示 1970 年,或者年份跑到了五萬多年後

這兩個症狀,來自同一個 bug。

你的代碼裏有個時間戳,傳給了某個函數,結果要麼輸出 1970-01-20,要麼輸出某個幾萬年後的日期。翻來覆去找不到哪裏寫錯了,但其實原因就一個字:位。

時間戳有兩種精度——毫秒——差了 1000 倍。兩者混用,日期就會錯得離譜。


爲什麼 JavaScript 用毫秒,其他語言用秒?

Unix 標準定義時間戳是"從 1970-01-01 00:00:00 UTC 到現在經過的數",這是歷史標準,Python、PHP、Go、數據庫基本都遵循。

JavaScript 是個例外。Brendan Eich 在設計 JavaScript 的 Date 對象時,選擇了毫秒作爲單位,原因大概是當時認爲毫秒精度對前端更合適。這個決定定下來就改不了了,現在整個前端生態都是毫秒。

所以現狀是:

語言/系統 默認精度 典型位數
Unix / Linux 10位
Python time.time() 秒(浮點) 10位整數部分
PHP time() 10位
Go time.Now().Unix() 10位
MySQL UNIX_TIMESTAMP() 10位
JavaScript Date.now() 毫秒 13位
Java System.currentTimeMillis() 毫秒 13位

看位數是最快的判斷方式:10位是秒,13位是毫秒


四種混用場景和對應修復

場景1:把秒傳給了 JavaScript new Date()

// 後端返回了秒級時間戳
const ts = 1745000000;  // 10位,秒

// 錯誤寫法:new Date() 期望毫秒
const date = new Date(ts);
console.log(date.toISOString());
// 輸出: "1970-01-20T20:06:40.000Z"  ← 1970年!

// 正確寫法:乘以1000
const date = new Date(ts * 1000);
console.log(date.toISOString());
// 輸出: "2026-04-11T02:13:20.000Z"  ← 正確

場景2:把毫秒傳給了 Python datetime.fromtimestamp()

from datetime import datetime

ts = 1745000000000  # 13位,毫秒(比如從前端拿到的)

# 錯誤寫法
dt = datetime.fromtimestamp(ts)
# ValueError: year 55916 is out of range  ← 五萬多年後!

# 正確寫法:除以1000
dt = datetime.fromtimestamp(ts / 1000)
print(dt)  # 2026-04-11 02:13:20

場景3:前端發給後端的時間戳沒轉換

這是最容易被忽視的場景,因爲前後端各自測試都正常,聯調纔出問題。

// 前端:用 Date.now() 獲取當前時間戳
const payload = {
    created_at: Date.now()  // 1745000000000,毫秒
};

fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(payload)
});
# 後端 Python:以爲收到的是秒
from datetime import datetime

data = request.json
ts = data['created_at']  # 實際是 1745000000000
dt = datetime.fromtimestamp(ts)  # 報錯:year out of range

# 修復方案1:前端轉成秒再發
# JavaScript: Math.floor(Date.now() / 1000)

# 修復方案2:後端判斷位數自動處理
if ts > 1e10:  # 超過100億,說明是毫秒
    ts = ts / 1000
dt = datetime.fromtimestamp(ts)

場景4:數據庫存的是秒,Node.js 讀出來當毫秒用

// 從數據庫讀出來的是秒
const row = await db.query('SELECT created_at FROM users WHERE id = 1');
const ts = row.created_at;  // 1745000000,秒

// 直接用,結果顯示 1970 年
const date = new Date(ts);  // 錯!

// 正確
const date = new Date(ts * 1000);

怎麼判斷手上的時間戳是秒還是毫秒?

方法1:看位數

2026年的秒級時間戳是10位(1700000000 到 1800000000之間),毫秒是13位。這個規律在2001年之後到2286年之前都成立。

方法2:寫一個檢測函數

function detectTimestamp(ts) {
    if (ts > 1e12) return 'milliseconds';
    if (ts > 1e9)  return 'seconds';
    return 'unknown';
}

// 統一轉成毫秒給 Date 用
function toDate(ts) {
    return new Date(ts > 1e12 ? ts : ts * 1000);
}
def detect_timestamp(ts):
    if ts > 1e12:
        return 'milliseconds'
    if ts > 1e9:
        return 'seconds'
    return 'unknown'

def to_datetime(ts):
    from datetime import datetime, timezone
    if ts > 1e12:
        ts = ts / 1000
    return datetime.fromtimestamp(ts, tz=timezone.utc)

根治這個問題的兩條原則

原則1:API 邊界用 ISO 8601 字符串,不傳裸時間戳

ISO 8601 格式是 2026-04-11T02:13:20Z,帶時區、精度明確、人類可讀,不存在秒/毫秒歧義。如果你能控制接口設計,優先用這種格式傳時間。

// 前端發
const payload = {
    created_at: new Date().toISOString()  // "2026-04-11T02:13:20.123Z"
};

// 後端收
from datetime import datetime
dt = datetime.fromisoformat("2026-04-11T02:13:20.123Z".replace('Z', '+00:00'))

原則2:如果必須傳時間戳,在接口文檔裏明確寫單位

在接口文檔裏寫清楚 created_at: Unix timestamp (seconds) 還是 (milliseconds),並且在入口處做校驗:

def validate_timestamp(ts: int, unit: str = 'seconds'):
    if unit == 'seconds' and ts > 1e12:
        raise ValueError(f"期望秒級時間戳,收到了疑似毫秒的值: {ts}")
    if unit == 'milliseconds' and ts < 1e10:
        raise ValueError(f"期望毫秒級時間戳,收到了疑似秒的值: {ts}")

遇到不確定的時間戳,粘到 時間戳在線轉換工具 裏,自動識別秒還是毫秒,直接顯示對應的日期,比自己心算省事。

发现周边 发现周边
評論區

加載中...