一個規範的 ULID 由 26 個 Crockford Base32 字元組成,其中前 10 個字元編碼 Unix 毫秒數,最後 16 個字元編碼 80 位元的隨機數,因此任何持有該字串的人都能讀取其中嵌入的毫秒時間戳,無需網路請求或伺服器端依賴。這使得「解碼 ULID 時間戳的 API 替代方案」這個問題變得實用:只需要從既有識別碼擷取建立時間的開發人員,可以跳過 HTTP 來回,在瀏覽器中直接執行轉換。ULID Generator 工具將兩個方向整合在同一個地方——從加密隨機數產生單調遞增的 ULID 批次,以及將任何規範的 26 字元字串解碼回其毫秒數與 ISO UTC 瞬間。由於產生、解碼、格式化與複製皆完全在瀏覽器中執行,不會上傳任何識別碼或時間戳,即使在離線機器上,頁面依然可用。本機路徑並非執行階段中正式函式庫的替代品,但卻是以無需啟動端點的方式滿足單次解碼需求最乾淨的做法。

瀏覽器工具何時能取代解碼端點
大多數公開的 ULID 函式庫會公開一個 decodeTime 函式,或一個小型 REST 端點,回傳嵌入的 Unix 毫秒數與 ISO 瞬間。對於已在 Node、Deno 或 Bun 中執行的正式程式碼,從 JavaScript 參考實作等套件呼叫 decodeTime,或閱讀規範 ULID 規格中的規則,才是正確的路徑。僅限瀏覽器的路徑在三種情況下有意義:支援工程師從記錄檔讀取客戶提供的 ULID、開發人員對已產生的 ID 進行離線審查,或審查人員稽核那些絕不應離開機器的 ID。在這三種情況下,目標都相同——將 26 個字元轉為可讀的時間戳,而無需發送 HTTP 請求或付出遠端查詢的延遲成本。
ULID Generator 在單一頁面中處理兩個方向:Generate 從加密隨機數建立單調遞增的批次,Decode 則從任何規範字串讀取時間戳與 ISO UTC 瞬間。由於每一步皆在本機執行,頁面永遠不會收到你的識別碼副本,這在你審查連結到私密資料的記錄時,是一項有意義的改善。該頁面也維持其初次渲染的確定性——Date.now 僅在 Generate 點擊處理程序內讀取,而非在伺服器渲染期間——因此重新整理分頁並不會將時鐘值洩漏到靜態版面中。
26 字元字串的結構解析
規範 ULID 固定為 26 個字元,取自 Crockford Base32 字集,該字集排除 I、L、O 與 U,以減少視覺上的混淆。分割正好是 10 加 16:前 10 個字元編碼 48 位元的 Unix 毫秒時間,最後 16 個字元編碼 80 位元的隨機數。字集由規格固定,任何在字集外的字元即使長度正確,字串也會被視為無效。
| 位置 | 字元數 | 欄位 | 範圍 / 大小 | 備註 |
|---|---|---|---|---|
| 1–10 | 10 | Unix 毫秒數 | 0 至 281,474,976,710,655 | 48 位元無符號;因此最大規範字串以 7 開頭 |
| 11–26 | 16 | 加密隨機數 | 每個 ID 80 位元,在批次內遞增 | 溢位時拒絕而非環繞 |
| 字集 | — | Crockford Base32 | 32 個符號,省略 I、L、O、U | 解碼不區分大小寫,但輸出為規範大寫 |
48 位元時間欄位正是讓識別碼能以 ASCII 字典序排序的原因——對於相同長度的字串,左側較大的整數也對應較大的字元序列。此特性正是多數團隊在有序主鍵、記錄索引與事件時間關聯上選擇 ULID 而非 UUID v4 的原因。同樣的特性,也使得解碼作業能僅憑字串本身回傳有意義的時間戳,而無需任何帶外詮釋資料。
在本機解碼 ULID 時間戳
透過 Decode 面板處理既有識別碼,是規範的讀取工作流程。該工具在接受 Crockford 正規化後,可處理任何 26 字元的規範字串,並同時回傳原始毫秒數與等價的 ISO UTC 瞬間。
- 開啟 ULID Generator 頁面,將模式切換至 Decode。
- 將規範的 26 字元 ULID 貼入輸入欄位;字集會在大小寫正規化後進行檢查。
- 讀取毫秒值,以及以 ISO 8601 格式呈現的對應 UTC 時間戳。
- 若你需要將該值作為 SQL 友善的 Unix 秒數,或希望與另一個時間戳進行比較,請將毫秒數交給 Unix 時間戳 SQL 指南,以取得對應方言中的精確轉換規則。
值得事先了解拒絕的情況。以 8 或以上開頭的 26 字元值包含超過 128 位元,因此會被拒絕,因為規格僅保留總共 128 位元。含有 Crockford Base32 以外字母的字串同樣會失敗——例如小寫 u 並不屬於該字集,也不會被正規化為 v。工具保持嚴格的驗證,以確保讀回的內容永遠與編碼內容一致。顯示的 ISO 時間以 UTC 呈現,而同一毫秒數的本地日曆檢視,在你的時區中可能落在不同的日期。
在本機產生單調遞增批次
產生的合約比解碼更為嚴格。每次點擊會擷取一個 Date.now 值,將其編碼進 10 字元的時間欄位,向瀏覽器加密隨機來源索取 80 位元,然後對同一批次中每個後續 ID 的隨機區域進行加一遞增——包括跨越整個 16 字元隨機後綴的進位傳遞。
- 開啟 ULID Generator,並將模式保留在 Generate。
- 選擇介於 1 至 100 之間的數量。超出此範圍的值將不被接受。
- 點擊 Generate 一次。整個批次共用擷取到的毫秒數,每個 ID 在 ASCII 順序上嚴格大於前一個。
- 複製完整的 26 字元識別碼,並將其儲存於保留大小寫的欄位中。為該欄位加上唯一性約束,讓資料庫而非工具成為唯一性的最終裁決者。
若批次內的 80 位元隨機欄位即將溢位,則產生作業會失敗而非環繞——這是有意為之的選擇,以確保回傳的字串嚴格遞增。該工具同樣拒絕與其他分頁、裝置、服務或先前產生的值進行協調,因此請勿依賴它來解決分散式系統間的唯一性問題。請將點擊視為本機熵加上擷取毫秒數的來源,僅此而已。
重要的限制與儲存規則
該工具在規格的 128 位元總預算上劃下明確界線。因此 48 位元時間戳可表示 0 至 281,474,976,710,655 毫秒,而最大規範字串以 7 開頭。任何需要 8 或更高前導值的內容,都包含規格外的位元模式,在 Generate 與 Decode 路徑上都會被拒絕。字典序排序僅在規範的 26 字元表示下,於一般的位元組或 ASCII 相容的定序下成立——大小寫摺疊、地區設定定序、截斷、填補,或儲存於過小的欄位中,皆會破壞預期的順序,因此在將 ULID 提升為主鍵之前,務必測試確切的資料庫定序。
可見的時間戳與隨機後綴,無法在缺乏存取控制時保護記錄不被列舉或揭露。ULID 並非密碼、API 金鑰、持有者權杖、授權規則或加密金鑰,而左側時間欄位刻意公開近似的建立時間。當 ULID 連結到個人或機密記錄時,應避免將其寫入記錄檔;任何需要保密的內容,請使用平台專屬的憑證系統。較短的 ULID 欄位或不區分大小寫的定序,將在索引填滿時悄悄地破壞順序。
瀏覽器工具與託管解碼端點的比較
| 考量 | 本機瀏覽器工具 | 託管解碼端點 |
|---|---|---|
| 網路來回 | 無——在同分頁中執行 | 每次解碼皆需 HTTP 請求 |
| 識別碼是否離開機器 | 不會上傳任何識別碼或時間戳 | 視提供者政策而定 |
| 離線可用性 | 頁面載入後即可使用 | 需要連線 |
| 驗證嚴格度 | 強制執行 128 位元上限與 Crockford 字集 | 視實作而異 |
| 批次吞吐量 | Generate 每次點擊最多產生 100 個 ID | 受主機速率限制 |
| 跨系統協調 | 無法與其他分頁或服務協調 | 可在呼叫者之間共用狀態 |
本機工作流程在隱私與離線使用方面勝出,且由於 Date.now 僅在 Generate 點擊處理程序內讀取,而非在伺服器渲染期間,因此保持確定性。託管端點在許多服務需要協調唯一性時仍然勝出,因此對於正式後端,請在執行階段中保留真正的 ULID 函式庫,並在需要快速、離線讀取時使用瀏覽器工具。八個外部固件鎖定了邊界值——零、Base32 邊緣、一秒、參考 decodeTime 範例,以及最大時間戳——因此你在此讀到的常數,與規格強制執行的常數一致。
出貨前的實用檢查清單
三項規則可消除人們從 UUID 轉換至 ULID 時遇到的大部分摩擦。首先,保留完整的大寫字串,並將其儲存於保留大小寫的 26 字元欄位中——絕不截斷為較少字元,也絕不因為認為前導字元可預測而將其移除。其次,強制執行資料庫唯一性約束,並以原子方式處理衝突;隨機性會降低碰撞機率,但永遠無法取代唯一性保證,而檢查後插入的競爭條件永遠不應作為最終控制手段。第三,將解碼後的時間視為詮釋資料而非已驗證的真相——它對於有序索引、記錄關聯與粗略的建立時間估計很有用,但它無法證明特定行為者在該瞬間建立了該記錄。
當問題是「解碼 ULID 時間戳的 API 替代方案」時,誠實的回答是:替代方案並非不同的規格——而是同樣的 Crockford Base32 編碼,只是改為在你的瀏覽器中讀取,而非透過 HTTP。這個單一的轉變,足以在不為技術堆疊新增外部依賴的情況下,涵蓋支援審查、離線稽核與單次查詢。對於高流量的程式碼路徑,請回到符合你執行階段的函式庫;對於偶爾的解碼,請維持在本機,並讓識別碼留在你的機器上。
如需更深入的了解,請參閱 在 JavaScript 中將 Unix 時間戳轉換為日期。