時間同步
/ntp 回傳伺服器收到請求與送出回應的時間(Unix 毫秒,精確到微秒)。伺服器每 30 秒與上游 NTP 伺服器對時,回傳的時間已經過校正。用戶端記下自己送出與收到的時間,就能用 NTP 的公式算出本機時鐘的偏差,適合需要毫秒級時間的應用(例如比對地震資料的時間戳)。
目前不需要 API Token。
取得伺服器時間
Section titled “取得伺服器時間”GET
https://api.lb.exptech.dev/ntp curl "https://api.lb.exptech.dev/ntp"curl "https://api.lb-tpe1.exptech.dev/ntp"curl "https://api.lb-khh1.exptech.dev/ntp"const res = await fetch("https://api.lb.exptech.dev/ntp");if (!res.ok) throw new Error(`HTTP ${res.status}`);console.log(await res.text());const res = await fetch("https://api.lb-tpe1.exptech.dev/ntp");if (!res.ok) throw new Error(`HTTP ${res.status}`);console.log(await res.text());const res = await fetch("https://api.lb-khh1.exptech.dev/ntp");if (!res.ok) throw new Error(`HTTP ${res.status}`);console.log(await res.text());import requests
res = requests.get("https://api.lb.exptech.dev/ntp", timeout=10)res.raise_for_status()print(res.text)import requests
res = requests.get("https://api.lb-tpe1.exptech.dev/ntp", timeout=10)res.raise_for_status()print(res.text)import requests
res = requests.get("https://api.lb-khh1.exptech.dev/ntp", timeout=10)res.raise_for_status()print(res.text)成功(text/plain)。本文是 T3:送出回應時的伺服器時間(與 X-NTP-T3 相同),Unix 毫秒、小數 3 位,結尾有換行
1794198117123.457試試看
本文只有 T3,完整的時間資訊在回應 header:
| 欄位 | 類型 | 說明 |
|---|---|---|
X-NTP-T2 | string | T2:伺服器收到請求的時間,Unix 毫秒,小數 3 位(例如 1794198117123.412)。 |
X-NTP-T3 | string | T3:伺服器送出回應的時間,格式同上。 |
X-NTP-Status | string | 伺服器自身的對時狀態:synced(已對時)、unsynced(啟動後尚未對時成功)、error(連續對時失敗)。不是 synced 時,T2、T3 可能沒有經過校正。 |
計算時鐘偏差
Section titled “計算時鐘偏差”設:
- T1:用戶端送出請求的時間(本機時鐘)
- T2:伺服器收到請求的時間(
X-NTP-T2) - T3:伺服器送出回應的時間(
X-NTP-T3) - T4:用戶端收到回應的時間(本機時鐘)
四個值都用毫秒。
偏差 offset = ((T2 − T1) + (T3 − T4)) / 2延遲 delay = (T4 − T1) − (T3 − T2)
標準時間 ≈ 本機時間 + offsetoffset為正,表示本機時鐘比伺服器慢。delay是網路往返時間(扣掉伺服器處理時間)。去程與回程不對稱時,offset的誤差最多delay / 2,所以delay越小的樣本越準。
建議做法:
- 連續取 5–10 個樣本,丟掉第一個(含建立連線的時間,去回程最不對稱)。
- 丟掉
X-NTP-Status不是synced的樣本。 - 取
delay最小的樣本的offset。 - T1、T4 用單調時鐘量測(例如
performance.timeOrigin + performance.now()),避免量測途中系統時間被調整。
依上面的建議做法實作對時:連續取樣、丟掉第一個樣本,取 delay 最小的樣本的 offset。
Node.js
Section titled “Node.js”Node.js 18 以上,伺服器端可以直接讀 header:
// ntp.mjs — 執行:node ntp.mjsconst BASE = "https://api.lb.exptech.dev";const now = () => performance.timeOrigin + performance.now();
async function sample() { const t1 = now(); const res = await fetch(`${BASE}/ntp`, { cache: "no-store" }); await res.text(); const t4 = now(); const t2 = Number(res.headers.get("X-NTP-T2")); const t3 = Number(res.headers.get("X-NTP-T3")); return { status: res.headers.get("X-NTP-Status"), offset: (t2 - t1 + (t3 - t4)) / 2, delay: t4 - t1 - (t3 - t2), };}
async function syncClock(count = 8) { await sample(); // 第一次包含建立連線的時間,丟掉 const samples = []; for (let i = 0; i < count; i++) { const s = await sample(); if (s.status === "synced") samples.push(s); } if (samples.length === 0) throw new Error("沒有可用的樣本"); return samples.reduce((best, s) => (s.delay < best.delay ? s : best));}
const { offset, delay } = await syncClock();console.log(`本機時鐘偏差 ${offset.toFixed(1)} ms(往返 ${delay.toFixed(1)} ms)`);console.log("校正後時間", new Date(Date.now() + offset).toISOString());伺服器處理時間通常遠小於 1 毫秒,可以視為 T2 ≈ T3,公式簡化成:
offset ≈ T3 − (T1 + T4) / 2delay ≈ T4 − T1const now = () => performance.timeOrigin + performance.now();
async function sample() { const t1 = now(); const res = await fetch("https://api.lb.exptech.dev/ntp", { cache: "no-store" }); const t3 = Number(await res.text()); const t4 = now(); return { offset: t3 - (t1 + t4) / 2, delay: t4 - t1 };}
async function syncClock(count = 8) { await sample(); // 丟掉第一個樣本 let best = null; for (let i = 0; i < count; i++) { const s = await sample(); if (!best || s.delay < best.delay) best = s; } return best;}
const { offset, delay } = await syncClock();console.log(`本機時鐘偏差 ${offset.toFixed(1)} ms(往返 ${delay.toFixed(1)} ms)`);這個做法無法排除 X-NTP-Status 不是 synced 的樣本。
- 時間單位是毫秒(不是秒),小數 3 位。
- 請求要加
cache: "no-store"(或Cache-Control: no-cache),避免拿到快取的舊時間。 - 不需要頻繁呼叫。對時一次後,以本機單調時鐘加上
offset計算即可;建議每幾分鐘重新校正一次。 - 這是 HTTP 上的估算,誤差上限是該樣本的
delay / 2,網路越慢、去回程越不對稱,誤差越大。需要更高精度時請使用系統的 NTP 用戶端。 - 也可以選擇特定節點的 Base URL(
https://api.lb-tpe1.exptech.dev、https://api.lb-khh1.exptech.dev),一次同步固定用同一個節點。
