找回密碼
 立即註冊
惟家LINE群QRCODE
    查看: 0|回覆: 0

    HA 的時間函式與時區:now、utcnow、today_at 的坑

    [複製鏈接]

    !lvup!   100%

    363

    主題

    27

    回帖

    2200萬

    積分

    管理員

    積分
    22005752
    發表於 3 天前 | 顯示全部樓層 |閱讀模式
    時間相關的 bug 有個特色:寫的時候測起來都正常,
    半夜或跨月的時候才爆掉


    這篇把 HA 裡跟時間有關的東西整理清楚。



    先確認時區設好了

    設定 → 系統 → 一般 → 時區,要是 Asia/Taipei。

    沒設對的話,所有東西都會差 8 小時,而且錯得很隱晦 ——
    排程會在錯的時間跑,但日誌看起來「時間是對的」。

    確認方法,在開發者工具的範本貼:
    1. {{ now() }}
    2. {{ utcnow() }}
    3. {{ now().tzinfo }}
    複製代碼

    now() 應該是台灣時間,tzinfo 應該顯示 CST +8。

    now() vs utcnow()


    • now() — 本地時間(台灣),你 99% 的情況要用這個
    • utcnow() — UTC 時間,跟資料庫、API 比對時才用


    常見錯誤
    1. # 錯:now() 跟 UTC 的時間戳混著比
    2. {{ as_timestamp(now()) - as_timestamp(states.sensor.x.last_changed) }}
    複製代碼

    last_changed 和 last_updated 都是 UTC。
    as_timestamp() 出來是 Unix 秒數(本來就沒時區),
    所以上面這個其實是對的 —— 這就是為什麼比時間差一律轉成時間戳再比

    比較安全的寫法:
    1. {{ (now() - states.sensor.x.last_changed).total_seconds() }}
    複製代碼

    Python 的 datetime 相減會自動處理時區,結果是 timedelta。

    today_at():最實用的一個
    1. {{ today_at('23:00:00') }}      → 今天的 23:00
    2. {{ today_at('06:00:00') }}      → 今天的 06:00
    3. {{ today_at() }}                → 今天的 00:00
    複製代碼

    拿來做時段判斷:
    1. {{ now() >= today_at('22:00') or now() < today_at('06:00') }}
    複製代碼

    跨午夜一定要用 or,不能用 and ——
    「大於 22:00 而且小於 06:00」永遠不成立。這是最常見的時間 bug。



    timedelta:加減時間
    1. {{ now() - timedelta(hours=1) }}
    2. {{ now() + timedelta(days=7) }}
    3. {{ now() - timedelta(minutes=30, seconds=15) }}
    複製代碼

    配 selectattr 找出「超過一小時沒更新」的實體:
    1. {{ states.sensor
    2.    | selectattr('last_updated', 'lt', now() - timedelta(hours=1))
    3.    | map(attribute='name') | list }}
    複製代碼

    格式化輸出
    1. {{ now().strftime('%Y-%m-%d %H:%M') }}       → 2026-09-21 08:45
    2. {{ now().strftime('%H 點 %M 分') }}           → 08 點 45 分
    3. {{ now().strftime('%A') }}                   → Monday(英文)
    複製代碼

    要中文星期的話 strftime 沒辦法,自己對照:
    1. {% set w = ['一','二','三','四','五','六','日'] %}
    2. 星期{{ w[now().weekday()] }}
    複製代碼

    now().weekday() 星期一是 0,星期日是 6。
    另外有 isoweekday() 是星期一 1、星期日 7,兩個容易搞混。

    relative_time:講人話的時間差
    1. {{ relative_time(states.binary_sensor.前門.last_changed) }}
    2. → 「2 小時前」(介面語言是中文的話)
    複製代碼

    做通知很好用:
    1. message: >
    2.   前門上次開啟是 {{ relative_time(states.binary_sensor.前門.last_changed) }}
    複製代碼

    三個會咬人的地方

    坑一:範本不會自己重算
    1. # 這個範本感測器「不會」每分鐘更新
    2. template:
    3.   - sensor:
    4.       - name: 現在幾點
    5.         state: "{{ now().strftime('%H:%M') }}"
    複製代碼

    HA 的範本是靠實體變化觸發重算的。
    這個範本沒有引用任何實體,所以 HA 不知道什麼時候該重算。

    解法一:加一個 trigger(新版寫法,推薦)
    1. template:
    2.   - trigger:
    3.       - trigger: time_pattern
    4.         minutes: "/1"
    5.     sensor:
    6.       - name: 現在幾點
    7.         state: "{{ now().strftime('%H:%M') }}"
    複製代碼

    解法二:引用內建的 sensor.time(要先啟用 time_date 整合)

    坑二:日期比較不要用字串
    1. # 錯:字串比大小,2026-9-5 會大於 2026-10-1
    2. {{ states('sensor.某日期') > '2026-10-01' }}
    3. # 對:轉成 datetime 再比
    4. {{ as_datetime(states('sensor.某日期')) > today_at() }}
    複製代碼

    坑三:time_pattern 的斜線寫法
    1. triggers:
    2.   - trigger: time_pattern
    3.     minutes: "/5"        # 每 5 分鐘(0,5,10,...)
    4.     # minutes: 5         ← 這是「每小時的第 5 分」,完全不一樣
    複製代碼

    少一個斜線,從一天 288 次變成一天 24 次。
    而且兩種寫法都合法,不會報錯。

    另外 seconds: "/1"(每秒)會讓 HA 很喘,不要這樣做。

    實用範例:營業時間判斷
    1. template:
    2.   - trigger:
    3.       - trigger: time_pattern
    4.         minutes: "/5"
    5.     binary_sensor:
    6.       - name: 上班時間
    7.         state: >
    8.           {{ now().weekday() < 5
    9.              and today_at('09:00') <= now() < today_at('18:00') }}
    複製代碼

    Python 的鏈式比較(a <= b < c)在 Jinja 裡也能用,寫起來比較乾淨。

    ---

    我自己被時間坑過最慘的一次:
    夜間模式寫成 and,結果整整兩個月夜間模式從來沒啟動過,
    而我一直以為是感應器的問題,在那邊換電池、調位置。

    後來是去看追蹤紀錄才發現條件根本沒過。
    您需要登入後纔可以回帖 登入 | 立即註冊

    本版積分規則

    Archiver|手機版|惟家的智能論壇

    GMT+8, 2026-9-24 10:29 , Processed in 0.081456 second(s), 26 queries .

    快速回覆 返回頂部 返回列表