Przejdź do treści
HNarzędzia
pl
Kategorie

Dla programistów

Wyrażenie cron: jak zapisać harmonogram i nie pomylić godzin

Wyrażenie cron wygląda prosto, a pomyłka wychodzi dopiero po tygodniu: zadanie uruchamia się za często, o złej godzinie albo wcale. Sprawdź zapis w narzędziu, zanim wkleisz go do konfiguracji.

Wyrażenie cron to pięć pól oddzielonych spacjami: minuta, godzina, dzień miesiąca, miesiąc i dzień tygodnia. Najwięcej problemów robią trzy rzeczy: dzień miesiąca łączony z dniem tygodnia, strefa czasowa serwera i zmiana czasu. Dalej każdy z nich jest sprawdzony w generatorze cron, który wypisuje opis słowny i listę 10 najbliższych uruchomień.

Wszystkie pomiary w tym poradniku zrobiliśmy w Chromium sterowanym Playwrightem, ze strefą Europe/Warsaw i ustawioną na sztywno datą: niedziela 11 października 2026, 10:00 czasu warszawskiego. Skrypty leżą w repozytorium (scripts/guide-fixtures/cron-expression-examples*.mjs). Ty zobaczysz listę liczoną od swojej bieżącej daty.

Pięć pól i ich zakresy

Pole Zakres Uwagi
Minuta 0-59
Godzina 0-23 doba w formacie 24-godzinnym
Dzień miesiąca 1-31
Miesiąc 1-12 lub JAN-DEC
Dzień tygodnia 0-7 lub SUN-SAT 0 i 7 to niedziela

Zakresy minut, godzin, dni i miesięcy są takie same w specyfikacji POSIX. POSIX przyjmuje dla dnia tygodnia tylko 0-6 (0 to niedziela), a 0-7 i nazwy dni pochodzą z crontaba Vixie, zapisanego w man crontab(5) dla pakietu cron w Debianie (sprawdzone w październiku 2026).

W każdym polu możesz użyć czterech znaków:

  • * - każda wartość,
  • , - lista (1,15),
  • - - zakres (1-5),
  • / - krok (*/15 to co 15 jednostek od początku zakresu).

Jeśli używasz nazw (MON-FRI), zapis jest czytelniejszy niż cyfry i nie zależy od tego, czy system liczy niedzielę jako 0, czy 7.

Typowe zadania sprawdzone w narzędziu

Każde wyrażenie wpisaliśmy w generator i zapisaliśmy to, co pokazał interfejs. Daty w ostatniej kolumnie przepisaliśmy skrótowo, narzędzie wypisuje je pełnym tekstem z dniem tygodnia.

Zadanie Wyrażenie Opis w narzędziu Pierwsze uruchomienia
codziennie o 3:00 0 3 * * * O 03:00 12.10, 13.10, 14.10, … każdego dnia o 03:00
dni robocze o 9:00 0 9 * * 1-5 O 09:00, od poniedziałku do piątku pn 12.10, wt 13.10, śr 14.10, czw 15.10, pt 16.10, potem pn 19.10
co 15 minut */15 * * * * Co 15 minut 11.10 o 10:15, 10:30, 10:45, 11:00
pierwszego dnia miesiąca 0 0 1 * * O 00:00, 1. dnia miesiąca 1.11.2026, 1.12.2026, 1.01.2027, 1.02.2027

Kilka uwag do tej tabeli:

  • */15 zaczyna liczyć od początku zakresu, więc uruchomienia wypadają w minutach 0, 15, 30 i 45. Pierwsze po 10:00 jest o 10:15, bo narzędzie nie liczy uruchomienia, które właśnie minęło.
  • 1-5 w polu dnia tygodnia to poniedziałek-piątek. Cron nie zna świąt, więc w dzień wolny od pracy zadanie też ruszy.
  • Dzień miesiąca 31 pomija miesiące, które go nie mają. Dla 0 0 31 * * narzędzie pokazało 31.10.2026, 31.12.2026, 31.01.2027, 31.03.2027 i 31.05.2027. Listopad, luty i kwiecień wypadły z listy.
  • 0 0 29 2 * uruchomi się tylko w lata przestępne: pierwsze dwa terminy to 29.02.2028 i 29.02.2032. 0 0 30 2 * nie ma żadnego terminu, a narzędzie pisze, że wyrażenie nie uruchomi się w ciągu najbliższych 8 lat.

Zapis, który się nie zgadza z zakresem, narzędzie odrzuca z komunikatem:

Wpisane Komunikat narzędzia
0 0 * * Wyrażenie ma 4 pól, a powinno mieć 5 (…) lub 6 (z sekundami na początku)
61 * * * * Pole „Minuta”: wartość „61” jest poza zakresem 0-59
0 24 * * * Pole „Godzina”: wartość „24” jest poza zakresem 0-23
*/0 * * * * Pole „Minuta”: niepoprawny krok w „*/0”

Dzień miesiąca i dzień tygodnia: warunek „lub”

Najczęstsza niespodzianka: gdy wpiszesz konkretną wartość i w polu dnia miesiąca, i w polu dnia tygodnia, cron nie szuka dnia, który spełnia oba warunki. Uruchamia zadanie, gdy pasuje którykolwiek z nich. Tak opisuje to man crontab(5) Vixie: jeśli oba pola są ograniczone (nie zaczynają się od gwiazdki), polecenie ruszy, gdy dopasuje się któreś z nich. W POSIX jest to samo (sekcja o crontab).

Przykład: ktoś chce uruchamiać zadanie w piątek trzynastego, więc zapisuje 0 0 13 * 5. Porównaj trzy wyrażenia w narzędziu:

Wyrażenie Pierwsze uruchomienia od 11.10.2026
0 0 13 * * wt 13.10, pt 13.11, nd 13.12, śr 13.01.2027
0 0 * * 5 pt 16.10, 23.10, 30.10, 6.11, 13.11, 20.11
0 0 13 * 5 wt 13.10, potem piątki: 16.10, 23.10, 30.10, 6.11, 13.11

Trzecie wyrażenie to suma dwóch pierwszych: dostajesz każde 13. i każdy piątek. W opisie narzędzie pisze „O 00:00, 13. dnia miesiąca, lub w piątek” i pokazuje ostrzeżenie o warunku „lub”.

Generator cron z wyrażeniem 0 0 13 * 5: opis „13. dnia miesiąca, lub w piątek”, ostrzeżenie o warunku „lub” i lista uruchomień zaczynająca się od wtorku 13 października, a dalej same piątki

Jak zostawić tylko jeden warunek:

  • Wpisz gwiazdkę w polu, którego nie potrzebujesz: 0 0 * * 5 to wszystkie piątki, 0 0 13 * * to wszystkie 13. dni.
  • Gdy naprawdę potrzebujesz „13., ale tylko jeśli to piątek”, ustaw harmonogram na piątki i sprawdź datę w samym zadaniu (skrypt kończy działanie, gdy dzień miesiąca jest inny niż 13).

Pole zaczynające się od gwiazdki nie jest ograniczone, także gdy ma krok, np. */2. Wtedy oba warunki muszą być spełnione naraz: 0 0 */2 * 5 uruchomi zadanie tylko w piątki, które wypadają w nieparzysty dzień miesiąca. Narzędzie pokazało 23.10, 13.11 i 27.11.2026, opis „O 00:00, co 2 dni, tylko w piątek” i nie wyświetliło ostrzeżenia o warunku „lub”.

Strefy czasowe i zmiana czasu

Wyrażenie cron nie zawiera strefy. O tym, co znaczy „9:00”, decyduje program, który je wykonuje:

  • Zwykły crontab używa strefy serwera. Cronie, jedna z implementacji crona w dystrybucjach Linuksa, ma też zmienną CRON_TZ ustawianą dla jednej tabeli (man crontab(5) cronie).
  • GitHub Actions liczy w UTC. Dokumentacja podaje: „By default, scheduled workflows run in UTC” (GitHub Docs, zdarzenie schedule).
  • Kubernetes CronJob bez pola .spec.timeZone liczy w strefie kontrolera (kube-controller-manager). Pole timeZone ma status stabilnego od wersji 1.27, a wpisywanie CRON_TZ lub TZ w schedule jest odrzucane z błędem walidacji (dokumentacja Kubernetes).

Narzędzie liczy terminy w strefie przeglądarki i pod listą wypisuje jej nazwę (u nas „Europe/Warsaw”). Jeśli serwer działa w innej strefie, lista nie będzie się zgadzać z tym, co zobaczysz w logach.

GitHub Actions: 8:00 w Polsce to dwa różne wyrażenia

Zadanie, które ma ruszać o 8:00 czasu polskiego, w Actions trzeba zapisać w UTC, więc dwa razy w roku trzeba je zmienić. Zmierzyliśmy to w konwerterze Unix timestamp, wpisując 8:00 czasu lokalnego (Europe/Warsaw):

Data lokalna, 8:00 Czas w UTC (ISO 8601 w narzędziu) Wyrażenie dla Actions
15 lipca 2026 2026-07-15T06:00:00.000Z 0 6 * * *
15 stycznia 2027 2027-01-15T07:00:00.000Z 0 7 * * *

Jedno wyrażenie nie obsłuży obu pór roku. Rozwiązania są dwa: zaakceptować przesunięcie o godzinę albo zapisać dwa harmonogramy i w pierwszym kroku zadania sprawdzać lokalną godzinę. Przy okazji dokumentacja GitHuba zastrzega, że najkrótszy odstęp to 5 minut, uruchomienie może się opóźnić przy dużym obciążeniu, a harmonogram działa tylko na domyślnej gałęzi.

Zmiana czasu o 2:30 w nocy

W Polsce czas przestawia się dwa razy w roku. W 2027 roku wiosną zegar skacze z 2:00 na 3:00 w niedzielę 28 marca, a w 2026 roku jesienią cofa się z 3:00 na 2:00 w niedzielę 25 października. Wyrażenie 30 2 * * * trafia wtedy w godzinę, której nie ma (wiosna) albo w taką, która wystąpi dwa razy (jesień).

Co o tym mówi dokumentacja (sprawdzone w październiku 2026): man crontab(5) cronie opisuje samą regułę dopasowania: nieistniejące godziny, takie jak „brakujące” przy zmianie czasu, nigdy się nie dopasują, a godziny powtórzone dopasują się dwa razy. Ten sam projekt w man cron(8) opisuje jednak, że demon obsługuje zmiany czasu mniejsze niż 3 godziny w szczególny sposób, ale tylko dla zadań o stałej godzinie i zadań uruchamianych rzadziej niż co godzinę. Po przestawieniu zegara do przodu uruchamia od razu zadania z pominiętej godziny, a po cofnięciu nie uruchamia ich drugi raz. Zadania częstsze, np. */15, są planowane zwyczajnie. Strona Vixie w Debianie nie opisuje tego przypadku w ogóle, więc zachowanie zależy od implementacji i wersji crona. Najpewniej sprawdzić je w dokumentacji własnego systemu.

Co pokazuje narzędzie. Wiosna, data bieżąca ustawiona na sobotę 20 marca 2027, 12:00, wyrażenie 30 2 * * *:

Nr Uruchomienie
1-7 niedziela 21.03 do sobota 27.03, każdego dnia 02:30
8 poniedziałek 29.03, 02:30
9 wtorek 30.03, 02:30
10 środa 31.03, 02:30

Niedzieli 28 marca nie ma na liście: narzędzie pomija godzinę, która nie istnieje. 0 2 * * * zachowuje się tak samo. Wyrażenie 0 3 * * * w tym samym teście uruchamia się 28 marca o 03:00, jak w każdy inny dzień.

Generator cron z wyrażeniem 30 2 * * * i listą uruchomień od niedzieli 21 marca 2027: po 27 marca następny wpis to 29 marca, bo 28 marca godzina 2:30 nie istnieje

Jesień, data bieżąca wtorek 20 października 2026, 12:00, wyrażenie 30 2 * * *: lista ma jedno uruchomienie 25 października o 02:30 (między 24 a 26 października). Pod tą datą narzędzie dopisuje: „Tego dnia ta godzina występuje dwa razy, bo zegar cofa się o godzinę. Część implementacji cron uruchomi zadanie dwukrotnie.” Wpis zostaje jeden, bo to, czy zadanie ruszy raz czy dwa razy, zależy od programu, który je wykonuje. Wyrażenie 30 3 * * * w tym samym teście nie ma adnotacji: 3:30 występuje tego dnia raz.

Wniosek praktyczny: w strefie Europe/Warsaw nie planuj w oknie 2:00-2:59 zadań, które muszą ruszyć dokładnie raz. Godzina 3:00 istnieje w obu dniach zmiany czasu. Ten wybór nie dotyczy innych stref: w USA zmiana czasu następuje w innych dniach i o innej godzinie. Alternatywa to serwer ustawiony na UTC, bo w UTC zmiany czasu nie ma.

Różnice między crontabem, Quartz, Actions i Kubernetes

System Pola Dzień tygodnia Strefa Źródło
crontab (Vixie, cronie) 5 0-7 lub SUN-SAT, 0 i 7 to niedziela strefa serwera, w cronie CRON_TZ man crontab(5)
GitHub Actions schedule 5 (składnia POSIX) jak w POSIX zawsze UTC GitHub Docs
Kubernetes CronJob 5 0-6 lub sun-sat strefa kontrolera albo .spec.timeZone Kubernetes
Quartz 6 lub 7 (sekundy na początku, rok opcjonalnie na końcu) 1-7 lub SUN-SAT ustawiana w harmonogramie CronTrigger Quartz

Źródła z tej tabeli sprawdziliśmy w październiku 2026. Z dokumentacji Quartza warto zapamiętać dwie rzeczy. Po pierwsze, w jednym z pól dni trzeba wpisać ? („bez określonej wartości”), bo jednoczesne podanie dnia miesiąca i dnia tygodnia nie jest w pełni wspierane. Po drugie, w Quartz dni tygodnia numeruje się od 1 do 7, a przykład z dokumentacji pokazuje, że 6#3 to trzeci piątek miesiąca. W Kubernetes ? znaczy to samo co *.

Narzędzie rozumie 6 pól z sekundami na początku i znaki L, W i #, ale wypisuje przy nich uwagę, że crontab, Actions i Kubernetes ich nie obsługują. Wyrażenie z 6 polami i ? w polu dnia miesiąca albo dnia tygodnia narzędzie traktuje jako zapis Quartz i liczy w nim dni tygodnia od 1 (niedziela) do 7 (sobota). Gdy w polu dnia tygodnia są cyfry, wyświetla o tym osobną uwagę, a w tabeli pól podaje zakres 1-7. Sprawdziliśmy pięć zapisów w stylu Quartz:

Wyrażenie Opis w narzędziu Pierwsze uruchomienia
0 30 9 * * 1-5 O 09:30, od poniedziałku do piątku pn 12.10.2026 o 09:30:00, potem kolejne dni robocze
0 0 9 ? * MON-FRI O 09:00, od poniedziałku do piątku pn 12.10.2026 o 09:00:00
0 0 9 L * ? O 09:00, ostatni dzień miesiąca 31.10, 30.11, 31.12.2026, 31.01.2027 i 28.02.2027, o 09:00:00
0 0 9 ? * 6#3 O 09:00, trzeci piątek miesiąca pt 16.10, 20.11, 18.12.2026, o 09:00:00
0 0 9 ? * 5 O 09:00, tylko w czwartek czw 15.10, 22.10, 29.10.2026, o 09:00:00

Uwaga na pierwszy wiersz. Wyrażenie 0 30 9 * * 1-5 nie ma ?, więc narzędzie liczy w nim dni jak crontab: 1-5 to poniedziałek-piątek. Quartz nie przyjąłby tego zapisu, bo wymaga ? w jednym z pól dni, a w jego numeracji 1-5 to niedziela-czwartek. Jeśli nie wiesz, jak Twój harmonogram numeruje dni, wpisz nazwy (MON-FRI). Znaczą to samo w crontabie i w Quartz.

Czego narzędzie nie robi

  • Nie zna Twojego serwera: liczy w strefie przeglądarki i nie sprawdza, czy harmonogram w Actions, Kubernetes lub crontabie będzie w tej samej strefie.
  • Nie pokazuje przesunięcia UTC przy terminach. Godzinę, która jesienią wystąpi dwa razy, pokazuje jako jeden wpis z adnotacją, a nieistniejącą godzinę wiosną pomija bez komentarza.
  • Nie obsługuje pola roku (7 pól Quartz) ani @reboot; w obu przypadkach zwraca komunikat.
  • Szuka terminów 8 lat do przodu.
  • Opis słowny jest tłumaczeniem biblioteki cronstrue, poprawianym w polskiej odmianie, więc przy nietypowych zapisach zdarzają się niezgrabne sformułowania. Rozstrzygaj według listy uruchomień, nie samego opisu.

Do szybkiego sprawdzenia, jak 8:00 czy 3:00 wygląda w UTC, użyj konwertera Unix timestamp. Gotowy zapis wpisz do generatora cron i porównaj z listą uruchomień, zanim wkleisz go do konfiguracji.