若想在 Java 中不撰寫日期運算程式碼就產生隨機日期,請開啟 隨機日期產生器,輸入 YYYY-MM-DD 格式的起始日期與結束日期,輸入介於 1 到 1,000 之間的數量,然後點擊「產生」。此工具會使用瀏覽器的 Web Crypto 從含頭尾的 UTC 日序數範圍中抽取日期,將其格式化為嚴格的 YYYY-MM-DD 字串,並讓你直接把清單複製到 LocalDate.parse() 呼叫、JUnit 測試資料,或任何其他 Java 日期輸入中。
許多搜尋「Java 隨機日期」的開發人員實際上更需要的是測試資料,而不是程式碼。在 Java 中撰寫正確的隨機日期比看起來要費力:你必須讓兩端點都含頭尾、避開日光節約時間的陷阱(會落在錯誤日期的 23:00 或 01:00),並在不把 4 月 31 日滾進 5 月的情況下驗證閏年。瀏覽器工具會處理這三點,讓你能專注於真正重要的測試邏輯。

為什麼 Java 內建的日期隨機化工具難以處理
Java 標準函式庫提供三種常見取得隨機數的途徑,當結果要作為日期時,每一種都帶著一些小陷阱。
- Math.random() 會回傳 [0.0, 1.0) 範圍內的 double 值。將其縮放至整數範圍時上界為互斥,因此挑選「介於 1 月 1 日到 12 月 31 日之間」的日期時,最後一天會被悄悄略過,除非你手動加一。
- java.util.Random 與 ThreadLocalRandom 會提供 nextInt(int bound),同樣也是上界互斥。若要兩端都含頭尾,就必須手動進行 bound + 1 的運算。
- Instant.plus(Duration.ofDays(k)) 看起來很安全,但 Duration 使用的是絕對的 24 小時長度。在非 UTC 時區跨越春令或秋令時間轉換時,會讓瞬間時間落在錯誤的時鐘小時,因此格式化後的日期可能會跳過或重複一個日曆日。
當你克服這些之後,下一個意外是閏年驗證。Java 的 LocalDate 很嚴格,會對「2023-02-29」或「2025-04-31」擲出 DateTimeException,但大多數天真的日期運算程式碼使用 ZonedDateTime 或 long 形式的毫秒數,會默默地把無效日期往前進位。用這類程式碼撰寫的測試資料,可能會遮蔽掉生產環境程式碼永遠看不到的 bug。
隨機日期產生器如何解決這個問題
隨機日期產生器將每個日期視為自 1970-01-01 起,以 86,400,000 毫秒為單位度量的 UTC 整數日位置。兩端點與其間所有日期都落在同一個含頭尾的格網上,因此 2024-02-28 到 2024-03-01 這個範圍可以等機率地回傳 2 月 28 日、閏日 2 月 29 日或 3 月 1 日。由於序數是 UTC 而非本地時間,日光節約時間的轉換無法將結果偏移一小時。
隨機性來自瀏覽器的 Web Crypto getRandomValues,使用無符號 32 位元字,並以拒絕抽樣消除模偏差——否則當範圍無法整除 2^32 時,某些日期會比其他日期多出一個底層值。此工具不使用 Math.random。格里高利曆規則遵循 ECMAScript 日期定義,因此能被 4 整除但不能被 400 整除的世紀年(例如 1900)會被正確拒絕,而 2000 與 2024 則會被接受。
所有運算都在瀏覽器內執行。日期範圍或結果清單都不會被傳送到伺服器,這讓此工具可用於內部截止日、薪資抽樣日期,或其他你不希望被上傳的資料。
逐步產生隨機日期
- 開啟隨機日期產生器,在第一個欄位以嚴格的 YYYY-MM-DD 格式輸入起始日期,例如 2024-01-01。
- 在第二個欄位以相同格式輸入結束日期,例如 2024-12-31。兩個端點都有機會被選中。
- 在第三個欄位輸入介於 1 到 1,000 的數量。任何空白、分數、零、負數或超過 1,000 的輸入都會被回報為錯誤,並清除先前的清單。
- 決定是否允許重複。讓重複開關保持開啟以進行獨立抽取;若需要不重複的日期則關閉它——不重複模式下,當要求的數量大於範圍內天數時,會回報錯誤而非回傳較短的清單。
- 點擊「產生」。此工具會抽出日期,以 UTC 格式化每一個,並在下方顯示清單。
- 確認結果區塊頂部顯示的範圍與數量,以確定數值符合你所設定的控制項,然後複製或記錄它們。
- 若你編輯任一端點、更改數量,或切換重複模式,舊的清單與先前的錯誤會自動清除,因此不會有過時結果留在畫面上,誤導人以為它仍對應目前的控制項。
將輸出貼回 Java 程式碼
由於輸出為純 YYYY-MM-DD 格式,清單可直接放入 Java 測試類別。將每一行讀入 String,並傳遞給 LocalDate.parse(line);該格式與 ISO_LOCAL_DATE 完全相符,因此不需要 DateTimeFormatter。如果你需要 long 基本型別的值,LocalDate.toEpochDay() 會回傳與工具抽樣時相同的整數序數,因此兩個系統的值可以互相比較。若要取得 UTC 午夜對應的 Instant,最乾淨的對應方式是 LocalDate.atStartOfDay(ZoneOffset.UTC).toInstant(),這也與工具的內部表示一致。
這個模式讓你的生產環境程式碼保持簡潔,也讓你的測試資料誠實可靠:工具回傳的日期就是測試實際看到的日期,沒有隱藏的時差、沒有 DST 的意外,也沒有差一的端點。
重複模式 vs 不重複模式
當每次抽取應為獨立事件時,重複模式是正確的選擇:從 365 天的範圍中抽樣 30 個日期,你可能合理地看到同一天出現兩次,因為每次要求的抽取都會對整個範圍獨立抽樣。這與 Math.random 或 Random.nextInt 的行為一致,只是已經處理好日期這一層。
不重複模式會執行稀疏的部分 Fisher-Yates 不放回抽樣。記憶體會隨要求的數量成長,而非隨日期範圍的大小成長,因此即使在跨數十年的範圍下工具仍能保持回應速度。當你要求的不重複日期數超過含頭尾範圍所含的天數時,工具會回報錯誤,而不會悄悄切回重複模式或回傳截斷後的清單。
輸入驗證與邊界情況
此工具會回報無效輸入,而不是自動修正它們。下表顯示常見的輸入及其結果。
| 輸入 | 結果 |
|---|---|
| 2000-02-29 到 2000-03-01 | 接受,可回傳閏日或任一端點 |
| 1900-02-29 到 1900-03-01 | 拒絕,依格里高利曆規則 1900 不是閏年 |
| 2025-04-31 到 2025-05-01 | 拒絕,4 月只有 30 天,不會滾進 5 月 |
| 2024-01-01 到 2024-01-01 | 接受,單日範圍在重複模式下必定回傳該日期 |
| 2024-12-31 到 2024-01-01 | 拒絕,起始日期晚於結束日期 |
| 數量 0 或 1,001 | 拒絕,允許範圍為 1 到 1,000 |
| 年份 0001 到 9999 | 接受,setUTCFullYear 會將其視為字面值 |
為了讓軟體測試可重現,請儲存產生的日期或使用你自己的具種子測試產生器;Web Crypto 在此介面中刻意不允許設定種子。等機率並不保證任何特定的執行結果看起來會均勻分布,且重複模式可能會合理地重複某些值。若抽樣涉及財務、法律、抽獎、安全或稽核等重要後果,請採用具備獨立監督與留存證據的正式程序,而非僅依賴單一的瀏覽器工具。
最後請記住,此清單不包含一天中的時間、沒有時區位移,也沒有排除週末、國定假日、歷史曆法轉換,或組織特定的停用日期。在數值進入實際流程之前,請另行檢查這些限制條件。
延伸閱讀:如何在 Google 試算表中建立日期清單。
延伸閱讀:讓 Excel 從產生器依序列出日期。