Русский

Почему cron запускается и по дню месяца, и по дню недели

Разбираем ловушку пяти полей cron: почему 30 4 1 * 1 срабатывает каждое первое число и каждый понедельник, а не только в первый понедельник.

Задача 30 4 1 * 1 должна была запускать отчёт в первый понедельник месяца. Вместо этого отчёт пришёл 1 августа, потом 3 августа, затем снова через неделю. Cron не опоздал и не задвоил очередь. Расписание изначально означало другое.

В обычном пятичастном crontab поля идут так:

минута час день-месяца месяц день-недели
30     4   1           *     1

Последние два ограничения читаются не как «первое число, которое оказалось понедельником», а как «первое число или понедельник». Именно такое правило описано в crontab(5): когда ограничены оба поля дня, достаточно совпадения одного из них.

Откройте генератор cron, вставьте 30 4 1 * 1 и посмотрите пять ближайших запусков. Превью здесь полезнее словесной расшифровки. Календарь сразу выдаёт лишние понедельники, которые легко пропустить при ревью одной строки.

Звёздочка меняет правило

Нужен запуск только первого числа месяца в 04:30? Выражение будет таким:

30 4 1 * *

Нужен каждый понедельник в 04:30?

30 4 * * 1

В обоих случаях одно поле дня оставлено без ограничения через *, поэтому расписанием управляет второе. Ловушка появляется, когда конкретное значение задано сразу и в дне месяца, и в дне недели.

Если первое число само выпало на понедельник, одна строка crontab всё равно даёт один запуск в эту минуту. OR расширяет набор подходящих дат, а не создаёт две независимые задачи.

А как записать первый понедельник месяца

Чистое пятичастное выражение не умеет потребовать одновременное совпадение этих двух полей. Практичный вариант для Unix cron: запускать задачу каждый понедельник, а проверку число месяца <= 7 оставить небольшому скрипту-обёртке. Так условие можно покрыть тестом и не прятать shell-арифметику в одной строке crontab.

Другой вариант - планировщик с отдельным правилом календаря. Но сначала проверьте его диалект. Quartz добавляет секунды и ?, GitHub Actions принимает пять полей, AWS EventBridge использует свой формат. Строка, валидная в одном месте, не обязана означать то же самое в другом.

Devdeck намеренно разбирает традиционный формат из пяти полей. Поддерживаются списки, диапазоны, шаги и короткие английские имена месяцев и дней недели. Спецсимволы Quartz вроде ?, L, W и # сюда не относятся.

Проверка перед деплоем

Для расписания из pull request я сначала смотрю не на перевод «человеческим языком», а на ближайшие реальные даты. У 30 4 1 * 1 в списке быстро появятся и понедельники, и первые числа. После исправления на 30 4 1 * * останутся только первые числа месяца.

Генератор считает превью локально в текущей вкладке и использует часовой пояс браузера. Production cron может жить в UTC, внутри контейнера или с CRON_TZ, поэтому финальная проверка всё равно делается рядом с конфигурацией сервиса. Граница локальных браузерных утилит подробнее разобрана в статье про инструменты разработчика и Network tab.

Сохраните при ревью не только выражение, но и две-три ожидаемые даты запуска. Для cron это короткий тест, который ловит ошибку лучше любого уверенного комментария рядом со строкой.

Вопросы

Как cron объединяет день месяца и день недели?

В традиционном пятичастном crontab два ограниченных поля соединяются через OR: запуск происходит при совпадении дня месяца или дня недели.

Что означает 30 4 1 * 1?

Запуск в 04:30 каждое первое число месяца, а также каждый понедельник. Это не расписание только для первого понедельника месяца.

Можно ли проверить такое выражение в devdeck?

Да. Генератор cron разбирает пять полей и показывает пять ближайших запусков в часовом поясе браузера. Он не проверяет диалекты Quartz, AWS EventBridge или конкретной CI-системы.

Связанные инструменты