---
title: "Синтаксис cron-выражений: пять полей"
description: "Синтаксис cron-выражения: пять полей от минут до дня недели, диапазоны значений, ловушка день месяца против дня недели и проверка через ParseStandard."
course: "Фоновые задачи в Go: планировщик на robfig/cron"
url: "https://golangmaster.ru/courses/cron/lessons/03-sintaksis-cron/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 20
---

# Синтаксис cron-выражений: пять полей

*Курс «Фоновые задачи в Go: планировщик на robfig/cron», урок 3 из 25. ~20 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/cron/lessons/03-sintaksis-cron/*

Синтаксис cron — это язык из пяти полей, на котором вы договариваетесь с планировщиком: минута, час, день месяца, месяц, день недели. Именно в этом формате robfig/cron ждёт выражение в AddFunc, и именно здесь чаще всего ошибаются: строка 0 9 * * 1-5 выглядит загадочно ровно до первого разобранного примера. Разберём все поля, их диапазоны, чтение вслух и главную ловушку с объединением дней.

Знакомая сцена: в код-ревью появляется строка из цифр и звёздочек, и половина команды делает вид, что всё понятно. Автор уверяет, что задача будет ходить по будням в девять, ревьюер сомневается, и спор заканчивается запуском в стейджинге — потому что вслух выражение никто так и не прочитал. Между тем пять полей — это фиксированный порядок, который не менялся с семидесятых: он одинаков у системного crontab, у Kubernetes CronJob и у robfig/cron. Умение прочитать строку вслух и перевести её в человеческое расписание — это навык на два часа практики, который потом экономит часы дежурств: неправильное выражение не падает, не ругается и просто тихо делает не то, что вы имели в виду. В этом уроке идём по порядку: поля слева направо, диапазоны каждого, чтение вслух на реальных примерах, и одна ловушка, из-за которой задачи запускаются в два раза чаще, чем планировал автор. К концу урока у вас появится шпаргалка проверки, которой можно делиться с командой на ревью.

### Пять полей cron-выражения: слева направо

Cron-выражение в robfig/cron v3 по умолчанию состоит ровно из пяти полей, разделённых пробелами. Слева направо: минуты от 0 до 59, часы от 0 до 23, день месяца от 1 до 31, месяц от 1 до 12 и день недели от 0 до 6, где ноль — воскресенье. Читается строка от меньшего к большему: сначала минута внутри часа, потом час внутри дня, и так до недели. Порядок фиксированный, его не поменять и не пропустить: пять значений обязаны присутствовать все, даже если поле означает «каждый раз» — на это место ставится звёздочка. Вместо чисел в полях месяца и дня недели разрешены имена: JAN-DEC и MON-SUN, регистр не важен, и сокращения из трёх букв читаются лучше, чем коды. Секунд в стандартном формате нет — пять полей закончились; секундное расписание включается отдельной опцией планировщика и разбирается в уроке про [секунды и NewWithSeconds](/courses/cron/lessons/08-sekundy-raspisaniya/). Там же разобрана обидная ошибка миграции: шестипольное выражение со скопированными секундами не пройдёт парсер по умолчанию.

### Поля выражения и их диапазоны

| Поле | Диапазон | Пример значения | Что означает пример |
| --- | --- | --- | --- |
| Минуты | 0-59 | 30 | тридцатая минута часа |
| Часы | 0-23 | 9-17 | с девяти утра до пяти вечера включительно |
| День месяца | 1-31 | 1 | первое число месяца |
| Месяц | 1-12 | 3-5 | с марта по май включительно |
| День недели | 0-6 | MON-FRI | с понедельника по пятницу |

### Четыре выражения и что они значат

```go
// каждую субботу в полдень: дайджест недели
c.AddFunc("0 12 * * SAT", weeklyDigest)

// каждые полчаса в рабочие часы по будням
c.AddFunc("0,30 9-17 * * MON-FRI", officePing)

// первое число каждого месяца в 03:00: обслуживание
c.AddFunc("0 3 1 * *", monthlyMaintenance)

// каждый день ровно в полночь: чистка сессий
c.AddFunc("0 0 * * *", cleanupSessions)
```

Разберите каждую строку слева направо: минута, час, день месяца, месяц, день недели. Во второй строке список из двух минут работает вместе с диапазоном часов и дней недели — поля независимы, и задача пойдёт на пересечении условий. Четвёртая строка по смыслу совпадает с дескриптором @daily: полночь каждого дня — это и есть ноль минут, ноль часов, каждый день. Дескрипторы и звёздочки со слэшами — тема следующего урока.

### Читаем выражение вслух: разбор строки из конфига

Практический приём, который стоит закрепить в команде: любое новое выражение проговаривается словами. Возьмём строку из реального конфига: тридцать минут, восьмой час, каждое число, каждый месяц, будни. Складываем: в 08:30 с понедельника по пятницу. Обратите внимание, как единственное конкретное значение в поле минут и единственное в поле часов сузили время до одного момента в сутках, а звёздочка в днях месяца и месяцах позволила этому моменту повторяться ежедневно. Теперь наоборот: команда «каждое первое число квартала в полночь» собирается в строку из нулей и диапазона месяцев с шагом в три, и на этом шаге обычно и рождается ошибка — месяцы в cron нумеруются с единицы, кварталы задаются списком или диапазоном со слэшем, а не «номером квартала». Проговорённое вслух расписание ловит большинство опечаток ещё до запуска: человеческий язык не даёт перепутать день месяца с днём недели так легко, как строка из пяти полей.

### Ловушка: день месяца и день недели объединяются через ИЛИ

Самый дорогой сюрприз классического синтаксиса сидит в двух последних полях. Если и день месяца, и день недели ограничены — то есть в обоих стоят не звёздочки, — cron засчитывает совпадение любого из них, а не обоих сразу. Выражение с полднем, пятнадцатым числом и понедельником означает «15-го числа любого месяца ИЛИ каждый понедельник», и задача пойдёт в полдень по всем понедельникам, а не дважды в месяц, как хотел автор. Это поведение унаследовано robfig/cron v3 от классического парсера ради совместимости, и оно не меняется ни флагами, ни опциями. Выход простой и дисциплинарный: не ограничивайте оба поля одновременно. Одно из двух оставляйте звёздочкой, а второе условие переносите в код задачи — там день недели проверяется одной строкой. Либо используйте две отдельные задачи с двумя простыми выражениями: читаются они лучше, чем одно выражение с двойным смыслом, а заодно логируются и снимаются с расписания независимо друг от друга.

### Проверка: правое поле

**Вопрос:** Какое поле стоит в выражении правее всех и что означает его значение 0?

- Минуты; ноль — начало часа
- Часы; ноль — полночь
- ✅ День недели; ноль — воскресенье
- Месяц; ноль — январь

Правое поле — день недели, диапазон от 0 до 6, где ноль соответствует воскресенью, единица — понедельнику, шестёрка — субботе. Имена вроде MON и SUN разрешены вместо чисел и часто спасают именно от этой путаницы с нулевым началом недели.

### Проверка: ИЛИ вместо И

**Вопрос:** Выражение 0 12 15 * 1 — когда выполнится задача?

- 15-го числа каждого месяца, в полдень
- Каждый понедельник, в полдень
- ✅ 15-го числа ИЛИ в любой понедельник, в полдень
- Никогда: условия противоречат друг другу

Оба дневных поля ограничены, значит работает объединение через ИЛИ — поведение классического cron, которое robfig v3 сохраняет ради совместимости. Задача пойдёт 15-го числа каждого месяца и вдобавок по всем понедельникам. Если нужно «15-е число, если это понедельник», решайте кодом внутри задачи или оставляйте ограниченным только одно поле.

### Сколько раз за сутки?

```go
_, err := c.AddFunc("0 */6 * * *", statusPing)
if err != nil {
        log.Fatal(err)
}
c.Start()

// вопрос: сколько раз за полные сутки
// выполнится statusPing?
```

**Что выведет программа?**

- ✅ 4 раза: в полночь, 06:00, 12:00 и 18:00
- 6 раз: каждые шесть часов после старта
- 24 раза: каждый час
- Ни разу: выражение не пройдёт парсер

Суточная сетка часов — 0, 6, 12, 18 — даёт четыре запуска, включая полночь. Шаг в поле часов отсчитывается от нуля, а не от момента запуска планировщика: выражение привязано к сетке часов, поэтому результат стабилен и не зависит от того, когда процесс стартовал. Про отличия от дескриптора @every — в следующем уроке.

### Ошибка в выражении: парсер не молчит

Хорошая новость синтаксиса v3: испортить расписание незаметно трудно. Строка проверяется парсером в момент регистрации — при вызове AddFunc — и проваливается немедленно, если поле вышло за диапазон: семьдесят пятая минута, тридцать второй час, тринадцатый месяц. Не проходит и число полей: шесть значений при пяти полях тоже отвергнуты — парсер вернёт ошибку про число полей, и AddFunc вернёт её вместе с нулевым идентификатором. Пустая строка и лишние пробелы ловятся тем же способом. Практика простая: выражения из конфига или базы проверяются на старте сервиса, до постановки задач — об этом следующий код.

### Проверка выражений на старте через ParseStandard

```go
specs := map[string]string{
        "daily-report":    "0 3 * * *",
        "session-cleanup": "*/15 * * * *",
        "hourly-sync":     "0 * * * *",
}

for name, spec := range specs {
        if _, err := cron.ParseStandard(spec); err != nil {
                log.Fatalf("выражение %q из конфига неверно: %v", name, err)
        }
}
```

ParseStandard из пакета cron/v3 проверяет строку и возвращает расписание вместе с ошибкой — без регистрации задачи в планировщике. Здесь выражения пришли из конфига, и сервис отказывается стартовать, если хоть одно из них не разбирается: падение на деплое дешевле месяца тихо неисполняющейся задачи. Тот же вызов внутренне делает AddFunc, так что лишней проверки не будет — вопрос лишь в том, где вы хотите увидеть ошибку: на деплое со стеком окружения или в три часа ночи по логам.

> **Шестое поле секунд — не по умолчанию.** Статьи и конфиги с секундным полем гуляют по интернету, и скопированная строка из шести полей упадёт в стандартном парсере с ошибкой про число полей. Пять полей — это минуты и крупнее; если задача обязана тикать чаще минуты, включите секундный режим явно через cron.NewWithSeconds и осознайте цену: расписание в секундах легко устроит дожим зависимости или API. Отдельный урок про секунды поднимет тему с примерами.

> **Где запускать код из уроков.** Планировщик robfig/cron живёт внутри вашего процесса, поэтому примеры курса выполняются локально: Go, go mod init и go get из второго урока. Песочница сайта умеет только подмножество стандартной библиотеки — fmt, strings, strconv, math, sort, errors и time — сторонних пакетов в ней нет. Зато интерактивы урока повторяют те же выражения, и их можно прокрутить прямо на странице.

### Три шага проверки нового выражения

- Проговорите пять полей вслух и запишите человеческое расписание словами — расхождение с задуманным видно сразу.
- Проверьте строку через cron.ParseStandard на старте сервиса: невалидное выражение должно валить деплой, а не молчать в проде.
- После релиза посмотрите в лог на первые два запуска: время тика против ожидания — финальная проверка зоны и полей.

### Соберите выражение для будней

Впишите значения полей: задача openShop должна ходить в 09:00 с понедельника по пятницу.

```go
_, err := c.AddFunc("0 ___ * * ___", openShop)
if err != nil {
        log.Fatal(err)
}
```

Подсказка: Девятый час — это 9 в поле часов; будни задаются диапазоном 1-5, где единица — понедельник. Звёздочки в днях месяца и месяцах означают «каждый день» и «каждый месяц».

Правильные ответы в пропусках: `9`, `1-5`

### Соберите регистрацию задачи на будни

Расставьте строки в порядке выполнения: создание планировщика, регистрация с проверкой, запуск и подтверждение в логе.

Правильный порядок строк:

1. `c := cron.New()`
2. `spec := "0 9 * * 1-5"`
3. `_, err := c.AddFunc(spec, openShop)`
4. `if err != nil { log.Fatal(err) }`
5. `c.Start()`
6. `fmt.Println("задача на будни зарегистрирована")`

### Карточки: синтаксис пяти полей

- **Пять полей** — Минуты (0-59), часы (0-23), день месяца (1-31), месяц (1-12), день недели (0-6). Порядок фиксированный, разделитель — пробел.
- **День недели: 0** — Ноль — воскресенье, единица — понедельник, шестёрка — суббота. Вместо чисел поддержаны имена MON-SUN, регистр не важен.
- **ИЛИ для дня месяца и дня недели** — Когда оба дневных поля ограничены (не звёздочки), совпадение любого из них запускает задачу — поведение классического cron в robfig v3.
- **ParseStandard** — Функция cron/v3: разбирает строку в расписание и возвращает ошибку без регистрации задачи. Удобна для проверки выражений из конфига на старте.
- **Минутная сетка** — Стандартный парсер не знает секунд: пять полей, точность до минуты. Шестипольное выражение упадёт с ошибкой про число полей.
- **Имена полей** — JAN-DEC в месяцах и MON-SUN в днях недели читаются лучше числовых кодов и спасают от путаницы с нулевым воскресеньем.

Теперь строка из пяти полей читается у вас вслух и не вызывает споров на ревью: поля фиксированы, диапазоны известны, ИЛИ между дневными полями держится в голове, а парсер ловит кривые значения на старте. Следующий шаг — обогатить выражения: в [уроке про спецсимволы](/courses/cron/lessons/04-specsimvoly-cron/) разберём звёздочку со слэшем, диапазоны, списки и дескрипторы @daily и @every, которые закрывают большинство «человеческих» расписаний без единой звезды. Если вы перескакиваете с шестипольных конфигов из старых статей — сначала загляните в [урок про секунды](/courses/cron/lessons/08-sekundy-raspisaniya/), там объяснено, чем секундный режим отличается от стандартного. Жизненный цикл вокруг выражения — регистрация, идентификаторы, снятие задач — ждёт в [уроке про Start и Stop](/courses/cron/lessons/09-start-stop/). Полный маршрут курса — [на странице курса](/courses/cron/); в домашнем задании — чтение и сборка выражений вручную.

## Коротко: что запомнить

- Cron-выражение v3 — пять полей: минуты, часы, день месяца, месяц, день недели; секунды только в отдельном режиме.
- День недели считается с нуля: ноль — воскресенье; имена MON-SUN и JAN-DEC читаются надёжнее чисел.
- Если ограничены и день месяца, и день недели, работает ИЛИ — задача пойдёт чаще, чем планировалось.
- Парсер проверяет выражение при регистрации: диапазонные и структурные ошибки ловятся на старте.
- ParseStandard проверяет строку из конфига без постановки задачи — падение на деплое дешевле молчания в проде.
- Привычка проговаривать выражение вслух ловит большинство опечаток до ревью и до дежурства.

## Домашнее задание

Выпишите из любого своего проекта три задачи, которые сегодня делаются вручную, и переведите их в cron-выражения: будничное утро, первое число месяца и шаг каждые N минут. Проговорите каждое вслух, соберите в map как в примере с ParseStandard и прогоните проверку в отдельной программе. Затем нарочно сломайте каждое выражение — выйдите за диапазон, уберите поле, добавьте шестое — и запишите, какую ошибку вернул парсер в каждом случае.

## Вопросы и ответы по уроку

### Почему день недели начинается с воскресенья?

Так сложилось исторически в классическом cron, и robfig/cron v3 сохраняет соглашение ради совместимости: 0 — воскресенье, 1 — понедельник, 6 — суббота. Если не хочется держать это в голове, используйте имена MON-SUN — они однозначны и читаются в коде и конфигах без шпаргалки.

### Как задать «только рабочие дни»?

Ограничьте день недели диапазоном с понедельника по пятницу и не трогайте день месяца. Праздники классический синтаксис не знает: для них либо держите список исключений в конфиге и проверяйте его кодом задачи, либо ведите отдельное расписание. Совмещать ограничения в обоих дневных полях не стоит — заработает ИЛИ.

### Обязательно ли проверять выражения из конфига?

Да, если конфиг правится людьми без деплоя кода. ParseStandard стоит ноль ресурсов и позволяет отказать в старте с понятной ошибкой вместо тишины в проде. AddFunc проверит то же самое, но проверка до регистрации даёт возможность отвергнуть весь конфиг целиком и показать оператору все ошибки сразу.
