|
|
時間相關的 bug 有個特色:寫的時候測起來都正常,
半夜或跨月的時候才爆掉。
這篇把 HA 裡跟時間有關的東西整理清楚。

先確認時區設好了
設定 → 系統 → 一般 → 時區,要是 Asia/Taipei。
沒設對的話,所有東西都會差 8 小時,而且錯得很隱晦 ——
排程會在錯的時間跑,但日誌看起來「時間是對的」。
確認方法,在開發者工具的範本貼:
- {{ now() }}
- {{ utcnow() }}
- {{ now().tzinfo }}
複製代碼
now() 應該是台灣時間,tzinfo 應該顯示 CST +8。
now() vs utcnow()
- now() — 本地時間(台灣),你 99% 的情況要用這個
- utcnow() — UTC 時間,跟資料庫、API 比對時才用
常見錯誤:
- # 錯:now() 跟 UTC 的時間戳混著比
- {{ as_timestamp(now()) - as_timestamp(states.sensor.x.last_changed) }}
複製代碼
last_changed 和 last_updated 都是 UTC。
但 as_timestamp() 出來是 Unix 秒數(本來就沒時區),
所以上面這個其實是對的 —— 這就是為什麼比時間差一律轉成時間戳再比。
比較安全的寫法:
- {{ (now() - states.sensor.x.last_changed).total_seconds() }}
複製代碼
Python 的 datetime 相減會自動處理時區,結果是 timedelta。
today_at():最實用的一個
- {{ today_at('23:00:00') }} → 今天的 23:00
- {{ today_at('06:00:00') }} → 今天的 06:00
- {{ today_at() }} → 今天的 00:00
複製代碼
拿來做時段判斷:
- {{ now() >= today_at('22:00') or now() < today_at('06:00') }}
複製代碼
跨午夜一定要用 or,不能用 and ——
「大於 22:00 而且小於 06:00」永遠不成立。這是最常見的時間 bug。

timedelta:加減時間
- {{ now() - timedelta(hours=1) }}
- {{ now() + timedelta(days=7) }}
- {{ now() - timedelta(minutes=30, seconds=15) }}
複製代碼
配 selectattr 找出「超過一小時沒更新」的實體:
- {{ states.sensor
- | selectattr('last_updated', 'lt', now() - timedelta(hours=1))
- | map(attribute='name') | list }}
複製代碼
格式化輸出
- {{ now().strftime('%Y-%m-%d %H:%M') }} → 2026-09-21 08:45
- {{ now().strftime('%H 點 %M 分') }} → 08 點 45 分
- {{ now().strftime('%A') }} → Monday(英文)
複製代碼
要中文星期的話 strftime 沒辦法,自己對照:
- {% set w = ['一','二','三','四','五','六','日'] %}
- 星期{{ w[now().weekday()] }}
複製代碼
now().weekday() 星期一是 0,星期日是 6。
另外有 isoweekday() 是星期一 1、星期日 7,兩個容易搞混。
relative_time:講人話的時間差
- {{ relative_time(states.binary_sensor.前門.last_changed) }}
- → 「2 小時前」(介面語言是中文的話)
複製代碼
做通知很好用:
- message: >
- 前門上次開啟是 {{ relative_time(states.binary_sensor.前門.last_changed) }}
複製代碼
三個會咬人的地方
坑一:範本不會自己重算
- # 這個範本感測器「不會」每分鐘更新
- template:
- - sensor:
- - name: 現在幾點
- state: "{{ now().strftime('%H:%M') }}"
複製代碼
HA 的範本是靠實體變化觸發重算的。
這個範本沒有引用任何實體,所以 HA 不知道什麼時候該重算。
解法一:加一個 trigger(新版寫法,推薦)
- template:
- - trigger:
- - trigger: time_pattern
- minutes: "/1"
- sensor:
- - name: 現在幾點
- state: "{{ now().strftime('%H:%M') }}"
複製代碼
解法二:引用內建的 sensor.time(要先啟用 time_date 整合)
坑二:日期比較不要用字串
- # 錯:字串比大小,2026-9-5 會大於 2026-10-1
- {{ states('sensor.某日期') > '2026-10-01' }}
- # 對:轉成 datetime 再比
- {{ as_datetime(states('sensor.某日期')) > today_at() }}
複製代碼
坑三:time_pattern 的斜線寫法
- triggers:
- - trigger: time_pattern
- minutes: "/5" # 每 5 分鐘(0,5,10,...)
- # minutes: 5 ← 這是「每小時的第 5 分」,完全不一樣
複製代碼
少一個斜線,從一天 288 次變成一天 24 次。
而且兩種寫法都合法,不會報錯。
另外 seconds: "/1"(每秒)會讓 HA 很喘,不要這樣做。
實用範例:營業時間判斷
- template:
- - trigger:
- - trigger: time_pattern
- minutes: "/5"
- binary_sensor:
- - name: 上班時間
- state: >
- {{ now().weekday() < 5
- and today_at('09:00') <= now() < today_at('18:00') }}
複製代碼
Python 的鏈式比較(a <= b < c)在 Jinja 裡也能用,寫起來比較乾淨。
---
我自己被時間坑過最慘的一次:
夜間模式寫成 and,結果整整兩個月夜間模式從來沒啟動過,
而我一直以為是感應器的問題,在那邊換電池、調位置。
後來是去看追蹤紀錄才發現條件根本沒過。 |
|