時區問題源於兩個獨立系統的配置差異:
system_time_zone(OS 級)、time_zone(會話級)。config.time_zone(應用級)、config.active_record.default_timezone(存儲級)。-- 查看當前時區 SHOW VARIABLES LIKE '%time_zone%'; -- 臨時設置會話時區(推薦 +8:00) SET time_zone = '+8:00'; -- 永久生效需修改 my.cnf [mysqld] default-time-zone='+8:00'
# config/application.rb # 無特殊配置時 # 寫入:created_at = Time.now.utc # 讀取:返回 UTC Time 對象 # 視圖:<%= post.created_at %> → "2012-11-01 05:13:45 UTC"
config.time_zone = 'Beijing' 的誤區僅影響 寫入時 將 Time.now 轉為本地時間存儲,但 讀取仍返回 UTC 對象,導致視圖顯示需手動轉換。
# config/application.rb config.active_record.default_timezone = :local # 存儲本地時間 config.time_zone = 'Beijing' # 應用級時區
效果:
INSERT ... created_at = '2012-11-01 13:13:45'(北京時間)。post.created_at 返回帶時區的 Thu, 01 Nov 2012 13:13:45 CST +08:00。| 代碼 | 輸出示例 | 說明 |
|---|---|---|
<%= post.created_at %> | 2012-11-01 13:39:26 +0800 | 含時區偏移,適合 API 返回 |
<%= post.updated_at.to_s(:db) %> | 2012-11-01 05:39:26 | ⚠️ 強制按 UTC 格式化,時間錯誤 |
<%= post.updated_at.localtime.to_s(:db) %> | 2012-11-01 13:39:26 | ✅ 正確:先轉本地再格式化 |
<%= l(post.created_at, format: :long) %> | 2012年11月01日 13:39 | 使用 I18n 本地化,最優雅 |
> p = Post.last # SQL 查詢日誌顯示 UTC:created_at: "2012-11-01 05:39:26" > p.created_at # 實際返回對象:Thu, 01 Nov 2012 13:39:26 CST +08:00
ActiveRecord 會根據 config.time_zone 自動將數據庫 UTC 值轉換為本地 ActiveSupport::TimeWithZone 對象,直接使用對象方法(如 strftime、localtime)即可。
date / timedatectl 確認 CST。SELECT NOW(); 對比 OS 時間。rails runner "puts Time.now, Time.zone.now" 雙輸出對比。ENV TZ=Asia/Shanghai、RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime。時區問題的核心是 存儲層、應用層、展示層三端統一。推薦策略:
default_timezone = :local),直觀易維護。config.time_zone),ActiveRecord 自動處理轉換。localtime 或 I18n l 助手,拒絕手動 + 8.hours 硬編碼。「UTC 存儲、本地顯示」雖是學院派推薦,但在中小型國內項目中,「本地時間全鏈路」能大幅降低認知負擔與 Bug 率。選擇權在團隊,關鍵是全鏈路一致。