時區、語系與時間寫法設定來源

目的

整理時區、時間寫入方式、資料表時間欄位、語系、多語系 path、translation 相關設定來源。

這類設定很容易造成新舊 API response、日期顯示、預約時間文字、notification template 的差異。

時間規則先集中放在這份文件。若後續內容膨脹,再拆成獨立的 時間設定.md,並從這裡連過去。

pro360_api_82

主要來源:

Lib/Common/Configuration.php
docker/staging/Configuration.php
Lib/Common/View.php
Lib/Common/Controller.php
Lib/Common/RouterV3.php

常見 key 名稱:

date_default_timezone_set("UTC")
local_timezone
default_lang
use_multi_langs
use_multi_translation
seo_page_force_lang

相關行為:

  • Configuration.php / staging 覆蓋檔目前會把 PHP runtime timezone 設成 UTC。
  • local_timezone 目前是台北時間,常用於本地日期、使用者輸入時間、顯示或部分業務規則。
  • View.php 會處理 timezone display 與 translation lookup。
  • Controller.php 會解析 URL path 的 language segment。
  • RouterV3.php 會處理多語系 path routing。

get-lancer-php56

主要來源:

app/Config/core.php
app/Config/config.php
app/Config/settings.yml

常見設定:

date_default_timezone_set(...)
Configure::write('Config.timezone', ...)
site.language
user.is_allow_user_to_switch_language

目前掃描到 legacy core.php 使用 UTC;部分 quote 流程會明確使用 Asia/Taipei 解析使用者輸入或報表時間,再轉成 UTC 查詢 / 寫入。migration 時要以該 API / model 的實際寫法為準,不要只用全域設定推論。

時間來源分類

排查或移植時間相關邏輯時,先判斷目前流程用哪一種時間來源。

類型常見寫法意義排查重點
PHP runtime timedate('Y-m-d H:i:s'), time(), new DateTime()依目前 PHP default timezone 產生時間new API / legacy 多數 runtime 是 UTC,但可能被局部 DateTimeZone 覆蓋
local timezone timenew DateTime('now', new DateTimeZone(local_timezone)), Asia/Taipei以台北時間做日期邊界、使用者輸入、顯示或業務日判斷不要直接和 UTC DB 欄位做日期字串比較
DB timeNOW(), CURRENT_TIMESTAMP, SYSDATE()由 DB server 產生時間時間窗 SQL 要跟原本一樣用 DB time,避免 PHP / DB clock 差異
使用者輸入時間begin_date, end_date, 預約時間、工作時間前端或 request body 傳入的業務時間先看 legacy 是否以台北時間解析,再看儲存欄位是否轉 UTC
Redis TTLsetex, incrementKey, short TTL lock過期時間由 Redis TTL 決定不一定有 DB 時間欄位可查,要看 key TTL 與程式設定
queue / log timeevent_queue.created, event_queue_log_*, Monolog table非同步事件建立 / 搬移 / 寫 log 的時間queue 可能已被 worker 搬到 log,查詢時要接受 queue 或 log 任一存在

資料表時間欄位規則

不要只看欄位名稱就推論時區或語意。先查該欄位在流程中怎麼寫入,再看它用來判斷什麼。

常見欄位語意:

欄位型態常見例子語意
lifecycle timecreated, modifiedrow 建立 / 更新時間;常用於時間窗、排序、最近一次異動
event time*_on, *_at, *_time某個業務事件發生時間,例如聯絡、付款、讀取、通話
business date*_date, begin_date, end_date使用者指定或業務定義的日期 / 區間,不一定等同系統寫入時間
expiry timeexpired_date, expires, end_time有效期限或結束時間,常牽涉 UTC / local day boundary
payment timereal_pay_date, paid_time付款完成或實際入帳判斷;不能只看 transaction created

created / modified

  • 代表資料列生命週期,不一定代表業務事件完成。
  • migration 時要確認 legacy 是用 CakePHP 自動 timestamp、date('Y-m-d H:i:s')NOW() 還是 CURRENT_TIMESTAMP
  • 如果原 SQL 用 NOW()created > DATE_SUB(NOW(), INTERVAL 24 HOUR),新實作應維持同一種 DB time 判斷,除非有明確理由改成 PHP time。
  • 測試資料若手動改 created,要注意會影響 12 小時、24 小時、30 天等限制。

*_on / *_at / *_time

  • 通常代表業務事件時間,例如:
    • quote_bids.contact_provider_on:consumer 正式聯絡 provider 的時間。
    • quote_bids.quote_sent_on:provider quote / accept 成立時間。
    • transactions.real_pay_date:實際付款完成時間。
  • 這類欄位要和狀態欄位一起判讀;有時間不一定代表整個下游通知完成。
  • real_pay_date 有值通常才代表 transaction 已付款;transactions.created 只代表交易 row 建立。

begin_date / end_date / 預約時間

  • 這類通常來自使用者輸入,先看 API body / form field 的格式。
  • 若 legacy 以 Asia/Taipei 建立 DateTime 再轉 UTC,新 API 要照同一規則。
  • 若 legacy 直接保存傳入字串,新 API 不要自行轉 timezone,避免預約時間偏移。
  • 文件或測試要標明「輸入時間」、「DB 儲存時間」、「顯示時間」三者是否相同。

queue / log 時間

  • event_queue.created 代表事件 enqueue 時間,不代表 worker 已送出 notification。
  • event_queue 可能被搬到 event_queue_log_1..event_queue_log_4
  • task_event_queue 可能被搬到 task_event_queue_log
  • 排查 downstream event 時,不要只查原 queue table;要同時查 log table。

Migration 檢查清單

移植有時間欄位或時間窗的 API 時,文件至少要補這些資訊:

問題要確認的內容
現在時間來源是什麼?PHP date() / time()、DB NOW()CURRENT_TIMESTAMP、Redis TTL
使用哪個 timezone?UTC、local_timezone、寫死 Asia/Taipei、使用者 timezone
欄位語意是什麼?row lifecycle、事件發生、付款完成、使用者預約、有效期限
是否用於限制?12 小時、24 小時、30 天、每日重置、短 TTL
新舊是否一致?legacy controller/model 的寫入方式與 new API 是否相同
測試如何避免誤判?使用固定 baseline、放寬秒級時間窗、不要跨日邊界跑脆弱 assertion

排查 SQL 原則

  • 查 schema 後再查資料,不要猜欄位型態。
  • 查時間窗時,用和程式相同的時間來源:
    • 程式用 NOW(),SQL 排查也用 NOW()
    • 程式用 PHP 產生時間,排查時要記錄 PHP runtime timezone。
    • 程式以台北日期做區間,查 DB 時要先轉成實際儲存 timezone 的起訖時間。
  • 不要把 UI 顯示的台北時間直接拿去比 DB raw datetime,除非已確認該欄位就是用台北時間存。
  • 有 queue / worker 的時間,至少保留數分鐘容忍區間,避免 worker timing 造成誤判。

排查原則

  1. 先分清楚「DB 寫入時間」、「業務事件時間」、「畫面顯示時間」。
  2. 日期寫入 DB 和畫面顯示要分開看。
  3. notification / SMS / email 文字可能還會依 user language 或 template 設定變化。
  4. legacy settings.yml 的語系不一定等同 new API 的 default_lang
  5. 涉及預約時間文字時,要同時看 requestor 語系、timezone display helper、reserve table。
  6. 涉及時間窗限制時,要寫明使用 DB created、local timezone、PHP runtime time 還是 Redis TTL。