iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Modern Web

微光採集瓶:用 Vue 3 打造每日美好記錄網頁系列 第 16 篇

【Day 16】重新定義「今天」:睡前才算一天結束的時間邏輯

  • 分享至 

  • xImage
  •  

你是夜貓子嗎?我是。所以我的「一天」其實不適合以凌晨十二點做分隔,我想應該也有很多人像我一樣吧!白天忙著工作、上課,晚上可能還要處理一些白天處理不完的事情,一直到接近深夜才有屬於自己的時間,所以我想要把系統的時間做一個小小的調整,讓一天的時間是從凌晨兩點開始算,而不是零點。

一、我想要的時光規則

我們要把「一天」的起訖點,往後順延至凌晨兩點(02:00):

從今天凌晨 02:00,到隔天凌晨 01:59:59 之前,都算作同一個「邏輯上的今天」。

以具體時間為例:

  • 9 月 29 日 23:00 寫下的紀錄,日期會被記作 2026-09-29。
  • 9 月 30 日 01:30 寫下的紀錄,因為還沒跨過凌晨 2 點,依然會被記作 2026-09-29。
  • 9 月 30 日 02:05 寫下的紀錄,此時跨越分界線,日期才會變成 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()

四、為什麼是「往回撥 2 小時」,而不是寫 if 判斷?

直覺上,很多人會想寫條件式:「如果當前小時小於 2,就把日期減一天;否則維持原樣」。

但這樣寫容易把程式碼變複雜,因為你必須考慮到:

  • 如果今天是 10 月 1 日凌晨 1 點,日期減一天要變成 9 月 30 日(9 月有 30 天)。
  • 如果是 3 月 1 日,減一天要變 2 月 28 還是 29 日?遇到閏年怎麼算?跨年 1 月 1 日呢?

換個數學角度思考:
與其特別去處理凌晨時段,不如「把時間基準往前調 2 小時」!

當時間整體減去 2 小時,原本的「凌晨 02:00」就會對齊到原本的「午夜 00:00」:

  • 原本 凌晨 01:30 減去 2 小時 ➔ 變成前一天的 23:30(算作前一天)。
  • 原本 凌晨 02:00 減去 2 小時 ➔ 變成當天的 00:00(正式邁入新的一天)。
  • 原本 晚上 23:00 減去 2 小時 ➔ 變成當天的 21:00(依然是當天)。

最大好處:跨月、跨年、大小月、閏年的計算,全部交由 JavaScript 底層的 Date.getTime() 毫秒運算去自動消化,我們一行邊界判斷都不用自己寫!

五、避開時區地雷

為什麼今天不用昨天的 toISOString(),而是手動用 getFullYear()、getMonth()、getDate()?

正如昨天提到的:toISOString() 輸出的是 UTC(零時區)。台灣處於 UTC+8,時區差了整整 8 小時。如果我們做時間點位移(往回調 2 小時),再經過 UTC 換算,極高機率會在午夜或清晨產生日期錯位。

改用 getFullYear() 等本地方法,讀取的是使用者電腦瀏覽器的本機系統時間,完全不會有時區轉換的困擾。

六、addGem 與過期鎖定自動銜接

因為我們輸出的變數名稱依然維持 todayStr,所以:

  1. addGem() 完全不用動:
const newGem = {
  id: Date.now(),
  date: todayStr, // 已經有「凌晨 2 點前算昨天」的邏輯!
  color: selectedColor.value,
  note: todayNote.value,
  offsetX: randomOffsetX,
  offsetY: randomOffsetY
}
  1. Day 15 的修改與撤回自動升級:

在彈出卡片中比對的 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 後,應該會看到正確輸出內容如下:

  • 【測試 1】凌晨 01:30(未滿兩點算昨天) -> 2026-09-29
  • 【測試 2】凌晨 02:00(剛好切換算當天) -> 2026-09-30
  • 【測試 3】凌晨 02:30(過兩點算當天) -> 2026-09-30
  • 【測試 4】深夜 23:00(當天深夜) -> 2026-09-30

https://ithelp.ithome.com.tw/upload/images/20260930/201780673JKNn1TJMI.png

看到這個結果,就代表你的「凌晨兩點跨日邏輯」完全正確無誤!

今日小結

  1. 設計源於生活:系統規則不一定要死板遵守日曆標準,貼合使用者心理狀態(睡前才算一天結束)的設計更符合使用者的作息。
  2. 數學位移勝過條件分支:用「整體回調 2 小時」取代多重 if-else,化解跨月與閏年的複雜計算。
  3. 時區本機化:捨棄 UTC 格式,採用瀏覽器本機時間 API,讓跨日邏輯純粹可靠。

明日預告

現在「一天」的定義變得更加寬容且貼心了。

但如果某一天,你過得太充實、玩得太瘋,或是太疲憊,連打開採集瓶記錄的時間都沒有呢?

我不想給使用者帶來「又忘了紀錄」的自責感,而是要讓玻璃瓶在感知到空白的日子時,主動留下一顆特別的光芒:「這天忘了寫,是因為正忙著快樂吧 ✨」,明天(Day 17)就要來實行這部分啦~


上一篇
【Day 15】時間的設置:當日編輯撤回與歷史過期鎖定
下一篇
【Day 17】忘了填寫怎麼辦:自動生成缺席的微光
系列文
微光採集瓶:用 Vue 3 打造每日美好記錄網頁 共 19 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言