你是夜貓子嗎?我是。所以我的「一天」其實不適合以凌晨十二點做分隔,我想應該也有很多人像我一樣吧!白天忙著工作、上課,晚上可能還要處理一些白天處理不完的事情,一直到接近深夜才有屬於自己的時間,所以我想要把系統的時間做一個小小的調整,讓一天的時間是從凌晨兩點開始算,而不是零點。
我們要把「一天」的起訖點,往後順延至凌晨兩點(02:00):
從今天凌晨 02:00,到隔天凌晨 01:59:59 之前,都算作同一個「邏輯上的今天」。
以具體時間為例:
2026-09-29。2026-09-29。2026-09-30。todayStr 只是機械式抓取系統日期回顧 Day 15 的程式碼:
const todayStr = new Date().toISOString().split('T')[0]
這行程式碼無論當下是幾點,都只會抓取系統當前的日曆日期。只要時鐘一跳過午夜 00:00,它就會把時間判成隔天。更不用說,昨天我們在卡片中寫的:
<!-- 只有今天採集的光珠才允許編輯與撤回 -->
<div v-if="activeGem.date === todayStr" class="gem-actions">
<button class="btn-sub edit" @click="editGem">編輯</button>
<button class="btn-sub delete" @click="deleteGem">撤回</button>
</div>
如果維持原本的做法,你昨晚 23:50 剛採集完,午夜 00:05 想修改個錯字,系統就會判定「這已經是昨天的事情了」而直接把你訪入的內容鎖定,這顯然不符合我們的需求。
我們需要一個具備邏輯判斷的全新日期計算函式。
getLogicalDate 函式在 <script setup> 裡,我們將原本那行靜態的 todayStr 宣告,改為呼叫新的時光計算函式:
// 1. 把 Date 物件轉換成本機 YYYY-MM-DD 格式的字串
function formatDate(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
// 2. 以凌晨 02:00 作為一天的起點,計算邏輯上的日期
function getLogicalDate(date = new Date()) {
// 關鍵:把時間整體往回撥 2 個小時 (2 * 60 * 60 * 1000 毫秒)
const adjusted = new Date(date.getTime() - 2 * 60 * 60 * 1000)
return formatDate(adjusted)
}
// 取得當前的邏輯日期
const todayStr = getLogicalDate()
if 判斷?直覺上,很多人會想寫條件式:「如果當前小時小於 2,就把日期減一天;否則維持原樣」。
但這樣寫容易把程式碼變複雜,因為你必須考慮到:
換個數學角度思考:
與其特別去處理凌晨時段,不如「把時間基準往前調 2 小時」!
當時間整體減去 2 小時,原本的「凌晨 02:00」就會對齊到原本的「午夜 00:00」:
最大好處:跨月、跨年、大小月、閏年的計算,全部交由 JavaScript 底層的 Date.getTime() 毫秒運算去自動消化,我們一行邊界判斷都不用自己寫!
為什麼今天不用昨天的 toISOString(),而是手動用 getFullYear()、getMonth()、getDate()?
正如昨天提到的:toISOString() 輸出的是 UTC(零時區)。台灣處於 UTC+8,時區差了整整 8 小時。如果我們做時間點位移(往回調 2 小時),再經過 UTC 換算,極高機率會在午夜或清晨產生日期錯位。
改用 getFullYear() 等本地方法,讀取的是使用者電腦瀏覽器的本機系統時間,完全不會有時區轉換的困擾。
addGem 與過期鎖定自動銜接因為我們輸出的變數名稱依然維持 todayStr,所以:
addGem() 完全不用動:const newGem = {
id: Date.now(),
date: todayStr, // 已經有「凌晨 2 點前算昨天」的邏輯!
color: selectedColor.value,
note: todayNote.value,
offsetX: randomOffsetX,
offsetY: randomOffsetY
}
在彈出卡片中比對的 activeGem.date === todayStr,現在也是跟著邏輯日期走!你在凌晨 01:30 點開昨晚 23:00 採集的光珠,系統依然會判斷它是「今天」的光珠,編輯與撤回按鈕依然保留著。
這裡有一個要分享的技術細節:const todayStr = getLogicalDate() 只有在網頁剛載入(或重新整理)的當下執行一次。
如果一位使用者在凌晨 01:55 打開網頁,放著發呆沒有重新整理,一直到 02:10 才點擊「放入微光」,此時記憶體裡的 todayStr 依然記錄著載入時的舊日期。
對於「開網頁 ➔ 記錄 ➔ 關閉」的常規使用情境來說,這個誤差發生的機率極低,不過還是有調整進步空間。
因為時間判定需要對應特定時刻,我們一樣在瀏覽器開發者工具的 Console 中塞入不同時段來驗證:直接在 Console 中輸入以下程式碼:
// 1. 定義格式化函式
function formatDate(date) {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
// 2. 定義往前調 2 小時的邏輯日期函式
function getLogicalDate(date = new Date()) {
const adjusted = new Date(date.getTime() - 2 * 60 * 60 * 1000)
return formatDate(adjusted)
}
// 3. 執行 4 個關鍵時段測試
console.log('【測試 1】凌晨 01:30(未滿兩點算昨天) ->', getLogicalDate(new Date('2026/09/30 01:30:00')))
console.log('【測試 2】凌晨 02:00(剛好切換算當天) ->', getLogicalDate(new Date('2026/09/30 02:00:00')))
console.log('【測試 3】凌晨 02:30(過兩點算當天) ->', getLogicalDate(new Date('2026/09/30 02:30:00')))
console.log('【測試 4】深夜 23:00(當天深夜) ->', getLogicalDate(new Date('2026/09/30 23:00:00')))
按 Enter 後,應該會看到正確輸出內容如下:

看到這個結果,就代表你的「凌晨兩點跨日邏輯」完全正確無誤!
if-else,化解跨月與閏年的複雜計算。現在「一天」的定義變得更加寬容且貼心了。
但如果某一天,你過得太充實、玩得太瘋,或是太疲憊,連打開採集瓶記錄的時間都沒有呢?
我不想給使用者帶來「又忘了紀錄」的自責感,而是要讓玻璃瓶在感知到空白的日子時,主動留下一顆特別的光芒:「這天忘了寫,是因為正忙著快樂吧 ✨」,明天(Day 17)就要來實行這部分啦~