Data i czas#

Do pracy z czasem masz moduł datetime (strefa czasowa, przedziały godzin, formatowanie) oraz standardowe funkcje Lua os.time(), os.date() i os.clock(). Czas pochodzi z zegara hosta (Raspberry Pi, serwer).

Moduł datetime#

Strefa czasowa: setTimezone#

Zegar serwera często chodzi w UTC, więc datetime.now().hour pokazywałby godzinę przesuniętą o 1-2 h względem Polski. Ustaw strefę raz, w pierwszej linii modules/init.lua:

-- modules/init.lua
datetime.setTimezone("Europe/Warsaw")

Od tej chwili datetime.now, fromTimestamp, parse (dla napisów bez przesunięcia), format, add, diff i between działają w tej strefie, razem ze zmianą czasu letni/zimowy. Tabele daty mają pole tz (np. CEST). Za tą strefą idą też cechy daty i czasu obiektu CLU vCLU, które czytają skrypty Grentona (FEATURE_HOUR, FEATURE_TIME, FEATURE_DATE, FEATURE_DAY_OF_WEEK itd.).

print(datetime.timezone())   -- "Europe/Warsaw"; "" gdy strefy nie ustawiono

os.date() i os.time(tabela) działają w strefie serwera (os.time() bez argumentu zwraca znacznik Unix, niezależny od strefy). W automatykach zależnych od godziny używaj datetime.

Bez wywołania setTimezone nic się nie zmienia: datetime działa w czasie serwera, jak wcześniej. Baza stref czasowych jest wbudowana w program, nie zależy od systemu.

Przedział godzin: between#

datetime.between(od, do) mówi, czy bieżąca godzina mieści się w przedziale [od, do). Przedział może przechodzić przez północ:

if datetime.between("21:00", "06:00") then
    -- noc
end

datetime.between("07:30", "08:15:30")   -- format "HH:MM" albo "HH:MM:SS"
datetime.between("06:00", "06:00")      -- równe końce = cała doba

Trzeci argument sprawdza inny moment niż „teraz”: tabelę z datetime albo timestamp (w sekundach lub milisekundach):

local last = light:lastChange()
if last and datetime.between("21:00", "06:00", last.atMs) then
    -- ostatnia zmiana była w nocy
end

Kilka zasad:

  • przedział liczy się w strefie z setTimezone, więc „21:00” to 21:00 polskiego czasu także po zmianie czasu,
  • koniec doby to "00:00", nie "24:00",
  • w dzień przestawienia zegara na czas letni przedział mieszczący się w „zjedzonej” godzinie (np. 02:15-02:45) nie zadziała, a jesienią powtórzona godzina pasuje dwa razy.

Prawdziwy przykład: w nocy czujka w wiatrołapie zapala światło na minutę. Pełny kod jest w tutorialu Automatyki w Lua.

StateBus:watch("satel.zone.2", function()
    if not datetime.between("21:00", "06:00") then return end
    _:get("CLU220000592.DOU4992"):switchOn(60 * 1000)
end, { to = 1 })

Pozostałe funkcje#

FunkcjaOpis
datetime.now()Bieżąca data i czas jako tabela: year, month, day, hour, minute, second, timestamp, weekday (1 = poniedziałek, 7 = niedziela), yearday, tz
datetime.fromTimestamp(ts)Tabela daty z timestampu w sekundach albo milisekundach
datetime.parse("2026-01-15 14:30:00")Tabela daty z napisu
datetime.format(dt, "2006-01-02 15:04")Napis z daty. Wzorzec jak w Go: 2006 rok, 01 miesiąc, 02 dzień, 15 godzina, 04 minuta, 05 sekunda
datetime.add(dt, {days = 1, hours = 2, minutes = 30})Nowa data przesunięta o podany czas
datetime.diff(dt1, dt2)Różnica dt2 - dt1: days, hours, minutes, seconds, totalSeconds, totalMinutes, totalHours

W testach automatyk w sandboxie (vclu_draft_test) datetime.now(), os.time() i timery idą za wirtualnym zegarem scenariusza, więc noc da się przetestować w dzień.

os.time() - aktualny czas#

Zwraca aktualny czas jako Unix timestamp (liczba sekund od 1 stycznia 1970 UTC).

local now = os.time()
log.info("Timestamp: %d", now)
-- Timestamp: 1740268800

Można też uzyskać timestamp dla konkretnej daty:

-- Timestamp dla 1 marca 2026, 14:30:00
local ts = os.time({year = 2026, month = 3, day = 1, hour = 14, min = 30, sec = 0})

os.date() - formatowanie daty#

Formatuje timestamp do czytelnej postaci. Bez argumentu formatuje aktualny czas.

Tabela daty#

local t = os.date("*t")
-- t = {
--   year = 2026, month = 2, day = 22,
--   hour = 14, min = 30, sec = 15,
--   wday = 1,   -- dzien tygodnia (1=niedziela, 2=poniedzialek, ..., 7=sobota)
--   yday = 53,  -- dzien roku
--   isdst = false
-- }

log.info("Godzina: %d:%02d", t.hour, t.min)
log.info("Dzien tygodnia: %d", t.wday)

Formatowanie stringów#

-- Pelna data i czas
log.info(os.date("%Y-%m-%d %H:%M:%S"))
-- "2026-02-22 14:30:15"

-- Tylko godzina
log.info(os.date("%H:%M"))
-- "14:30"

-- Tylko data
log.info(os.date("%Y-%m-%d"))
-- "2026-02-22"

-- Formatowanie historycznego timestampu
local ts = os.time() - 3600  -- godzine temu
log.info("Godzine temu: %s", os.date("%H:%M:%S", ts))

Kody formatu#

KodOpisPrzykład
%YRok (4 cyfry)2026
%mMiesiąc (01-12)02
%dDzień miesiąca (01-31)22
%HGodzina 24h (00-23)14
%MMinuta (00-59)30
%SSekunda (00-59)15
%ANazwa dnia tygodniaSunday
%aSkrót dniaSun
%BNazwa miesiącaFebruary
%bSkrót miesiącaFeb
%wDzień tygodnia (0=niedziela)0
%jDzień roku (001-366)053
%XCzas lokalny14:30:15
%xData lokalna02/22/26
%%Literalny %%

Czas UTC#

Prefix ! daje czas UTC zamiast lokalnego:

local utc = os.date("!*t")
local local_time = os.date("*t")

log.info("UTC: %s", os.date("!%H:%M:%S"))
log.info("Lokalny: %s", os.date("%H:%M:%S"))

os.clock() - czas CPU#

Zwraca czas CPU procesu w sekundach (z dokładnością do milisekund). Przydatny do mierzenia czasu wykonania.

local start = os.clock()
-- ... jakas operacja ...
local elapsed = os.clock() - start
log.info("Czas wykonania: %.3f ms", elapsed * 1000)

os.clock() mierzy czas CPU, nie czas ścienny. Do mierzenia odstępów czasu w sekundach użyj os.time(). Do precyzyjnych pomiarów w milisekundach użyj os.clock() * 1000.

os.difftime(t2, t1) - różnica czasu#

Zwraca różnicę między dwoma timestampami w sekundach.

local start = os.time()
-- ... po jakims czasie ...
local elapsed = os.difftime(os.time(), start)
log.info("Minelo %d sekund", elapsed)

Przykłady#

Akcje o określonej godzinie#

-- Sprawdzaj co minute czy jest pora na akcje
every(60000, function()
    local t = os.date("*t")

    -- Wlacz swiatla o 18:00
    if t.hour == 18 and t.min == 0 then
        runScene("wieczor")
    end

    -- Wylacz swiatla o 23:00
    if t.hour == 23 and t.min == 0 then
        runScene("wylacz_wszystko")
    end
end)

Harmonogram dzienny#

local schedule = {
    {hour = 7, min = 0, scene = "poranek"},
    {hour = 18, min = 0, scene = "wieczor"},
    {hour = 22, min = 30, scene = "noc"},
}

every(60000, function()
    local t = os.date("*t")
    for _, entry in ipairs(schedule) do
        if t.hour == entry.hour and t.min == entry.min then
            runScene(entry.scene)
            log.info("Harmonogram: %s o %02d:%02d", entry.scene, t.hour, t.min)
        end
    end
end)

Akcje tylko w dni robocze#

local function isWeekday()
    local wday = os.date("*t").wday
    -- 1=niedziela, 7=sobota
    return wday >= 2 and wday <= 6
end

every(60000, function()
    local t = os.date("*t")

    -- Pobudka o 6:30 tylko w dni robocze
    if isWeekday() and t.hour == 6 and t.min == 30 then
        runScene("pobudka")
    end
end)

Tryb nocny (zakres godzin)#

Prościej i z poprawną strefą czasową: datetime.between("22:00", "06:00"), opisane wyżej.

local function isNightTime()
    local hour = os.date("*t").hour
    -- Noc = 22:00 - 06:00
    return hour >= 22 or hour < 6
end

-- Czujnik ruchu - inne zachowanie w nocy
_:get("CLU.DIN1"):add_event(DIN.EVENT_ON_CLICK, function()
    if isNightTime() then
        -- Noc: delikatne swiatlo
        _:get("CLU.DIMM1"):set(0, 20)
    else
        -- Dzien: pelna jasnosc
        _:get("CLU.DIMM1"):set(0, 100)
    end
end)

Logowanie z timestamp#

local function logWithTime(msg, ...)
    local time = os.date("%H:%M:%S")
    local formatted = string.format(msg, ...)
    log.info("[%s] %s", time, formatted)
end

logWithTime("Temperatura: %.1f°C", 22.5)
-- [14:30:15] Temperatura: 22.5°C

Obliczanie czasu do zdarzenia#

-- Ile sekund do polnocy?
local function secondsToMidnight()
    local t = os.date("*t")
    local midnight = os.time({
        year = t.year, month = t.month, day = t.day + 1,
        hour = 0, min = 0, sec = 0
    })
    return os.difftime(midnight, os.time())
end

log.info("Do polnocy: %d minut", secondsToMidnight() / 60)

Cykliczny raport#

-- Raport co godzine o pelnej godzinie
every(60000, function()
    local t = os.date("*t")
    if t.min == 0 then
        local report = string.format(
            "Raport %02d:00 - Relay1: %d, Temp: %.1f",
            t.hour,
            _:get("CLU.DOU5048"):get(DOUT.FEATURE_VALUE),
            _:get("CLU.ANA1"):get(0)
        )
        log.info(report)

        -- Wyslij przez HTTP
        HttpClient:asyncPOST("https://hooks.example.com/report",
            JSON.encode({message = report, time = os.time()}),
            function() end
        )
    end
end)

Przechowywanie timestampów w KV#

-- Zapisz czas ostatniej aktywnosci
_:get("CLU.DIN1"):add_event(DIN.EVENT_ON_CLICK, function()
    kv:set("last_motion", os.time())
    kv:set("last_motion_formatted", os.date("%Y-%m-%d %H:%M:%S"))
end)

-- Sprawdz ile minelo od ostatniego ruchu
local function minutesSinceLastMotion()
    local last = kv:get("last_motion")
    if not last then return 999999 end
    return os.difftime(os.time(), last) / 60
end

-- Wylacz swiatla jezeli brak ruchu > 30 minut
every(60000, function()
    if minutesSinceLastMotion() > 30 then
        runScene("wylacz_swiatla")
    end
end)

Sunrise/sunset (przybliżony)#

-- Uproszczony kalkulator wschodu/zachodu slonca
-- Dla dokladnych wartosci uzyj zewnetrznego API
local function estimateSunset()
    local t = os.date("*t")
    local dayOfYear = t.yday

    -- Przyblizenie dla Warszawy (52°N)
    -- Lato ~21:00, zima ~15:30
    local baseHour = 16
    local amplitude = 2.5
    local offset = math.sin((dayOfYear - 80) / 365 * 2 * math.pi)

    return baseHour + amplitude * offset
end

every(60000, function()
    local t = os.date("*t")
    local sunsetHour = estimateSunset()
    local currentHour = t.hour + t.min / 60

    if currentHour >= sunsetHour and currentHour < sunsetHour + 0.02 then
        runScene("wieczor")
        log.info("Zachod slonca - uruchamiam scene wieczorna")
    end
end)

Plugin Time Sync#

Funkcje os.time() / os.date() zwracają czas systemowy maszyny. Na Raspberry Pi bez RTC (zegar czasu rzeczywistego) czas po restarcie może być niepoprawny do momentu synchronizacji NTP. Plugin Time Sync rozwiązuje ten problem - synchronizuje czas z internetowego serwera (WorldTimeAPI) i udostępnia wygodne API do automatyzacji.

Instalacja#

Plugin @vclu/time-sync dostępny w repozytorium vCLU. Instalacja przez panel Plugins w interfejsie webowym.

Konfiguracja#

ParametrTypDomyślnieOpis
timezonestringEurope/WarsawStrefa czasowa IANA
intervalnumber3600Interwał synchronizacji w sekundach (0 = manualny)
autoSyncbooleantrueAutomatyczna synchronizacja przy starcie

Tryby pracy:

autoSyncintervalZachowanie
true3600Sync przy starcie + co godzinę (domyślne)
false3600Bez sync przy starcie, potem co godzinę
true0Tylko sync przy starcie
false0Tylko manualny sync, fallback na os.time()

API#

local time = Plugin.get("@vclu/time-sync")

-- Aktualny czas (synchronizowany z serwerem)
time:getTimestamp()        -- Unix timestamp (korygowany o drift)
time:getHour()             -- 0-23
time:getMinute()           -- 0-59
time:getSecond()           -- 0-59
time:getFormatted()        -- "15:30:45"
time:getFormattedDate()    -- "2025-12-30"
time:getFormatted("%H:%M") -- "15:30" (custom format)

-- Informacje o strefie czasowej
time:getTimezone()         -- "Europe/Warsaw"
time:getUtcOffset()        -- "+01:00"
time:isDst()               -- true/false (czas letni)
time:getDayOfWeek()        -- 0=niedziela, 6=sobota
time:getWeekNumber()       -- numer tygodnia w roku

-- Wygodne helpery do automatyzacji
time:isBetween(8, 0, 22, 0)   -- czy miedzy 8:00 a 22:00
time:isBetween(22, 0, 6, 0)   -- dziala tez przez polnoc
time:isWeekday()               -- poniedzialek-piatek
time:isWeekend()               -- sobota/niedziela

-- Kontrola synchronizacji
time:sync()                        -- wymus synchronizacje
time:setTimezone("Europe/London")  -- zmien strefe
time:getDrift()                    -- roznica serwer vs lokalny (sekundy)
time:isReady()                     -- czy czas jest zsynchronizowany
time:getLastSync()                 -- timestamp ostatniej sync
time:getData()                     -- wszystkie dane jako tabela

Zdarzenia#

-- Czas zsynchronizowany
plugin:on("time:synced", function(data)
    log.info("Czas: %s, drift: %+ds", data.datetime, data.drift)
end)

-- Zmiana godziny - idealne do automatyzacji
plugin:on("time:hourChanged", function(data)
    log.info("Nowa godzina: %d", data.hour)

    if data.hour == 18 then
        runScene("wieczor")
    end
end)

-- Blad synchronizacji
plugin:on("time:error", function(data)
    log.warn("Sync error: %s", data.error)
end)

Przykłady z pluginem#

Automatyzacja oparta na godzinie#

-- Uzyj zdarzenia hourChanged zamiast pollowania co minute
plugin:on("time:hourChanged", function(data)
    local time = Plugin.get("@vclu/time-sync")

    -- Wlacz swiatla o 18:00 w dni robocze
    if data.hour == 18 and time:isWeekday() then
        runScene("wieczor")
    end

    -- Wylacz wszystko o 23:00
    if data.hour == 23 then
        runScene("wylacz_wszystko")
    end
end)

Czujnik ruchu z trybem nocnym#

local time = Plugin.get("@vclu/time-sync")

_:get("CLU.DIN1"):add_event(DIN.EVENT_ON_CLICK, function()
    if time:isBetween(22, 0, 6, 0) then
        -- Noc: delikatne swiatlo
        _:get("CLU.DIMM1"):set(0, 20)
    elseif time:isBetween(6, 0, 8, 0) then
        -- Poranek: srednia jasnosc
        _:get("CLU.DIMM1"):set(0, 60)
    else
        -- Dzien: pelna jasnosc
        _:get("CLU.DIMM1"):set(0, 100)
    end
end)

Tryb pracy biurowej#

local time = Plugin.get("@vclu/time-sync")

-- Sprawdz czy godziny pracy
local function isOfficeHours()
    return time:isWeekday() and time:isBetween(8, 0, 17, 0)
end

_:get("CLU.DIN1"):add_event(DIN.EVENT_ON_CLICK, function()
    if isOfficeHours() then
        runScene("biuro_on")
    else
        runScene("po_godzinach")
    end
end)

os.time() vs Plugin Time Sync#

os.time()Plugin Time Sync
ŹródłoZegar systemowyWorldTimeAPI (internet)
Strefa czasowaStrefa systemuKonfigurowalna (IANA)
Po restarcie bez RTCMoże być błędnySynchronizuje się automatycznie
Czas letni (DST)os.date("*t").isdsttime:isDst()
HelperyBrak - trzeba pisać samemuisBetween(), isWeekday(), isWeekend()
ZdarzeniaBraktime:synced, time:hourChanged
Drift detectionBraktime:getDrift()

Strefę czasową i przedziały godzin załatwia wbudowany moduł datetime (setTimezone, between). Plugin Time Sync przydaje się, gdy urządzenie nie ma zegara RTC (np. Raspberry Pi bez sieci przy starcie) albo potrzebujesz zdarzeń hourChanged.

Podsumowanie#

FunkcjaOpis
datetime.setTimezone(name)Strefa czasowa dla całego datetime (np. "Europe/Warsaw")
datetime.between(od, do[, kiedy])Czy godzina jest w przedziale, także przez północ
datetime.now()Bieżąca data jako tabela, w ustawionej strefie
os.time()Aktualny Unix timestamp (sekundy)
os.time(table)Timestamp dla podanej daty
os.date("*t")Tabela z rozbitą datą
os.date(fmt)Sformatowany string daty
os.date(fmt, ts)Formatowanie konkretnego timestampu
os.date("!*t")Tabela daty w UTC
os.clock()Czas CPU procesu (sekundy, precyzja ms)
os.difftime(t2, t1)Różnica dwóch timestampów w sekundach
Plugin time-syncSynchronizowany czas z helperami i zdarzeniami