---
title: "Спецсимволы: звёздочка, слэш, диапазоны и @daily"
description: "Спецсимволы cron-выражений: звёздочка, слэш-шаг, диапазоны, списки, дескрипторы @daily и @hourly, чем @every 1h30m отличается от выражения с полями."
course: "Фоновые задачи в Go: планировщик на robfig/cron"
url: "https://golangmaster.ru/courses/cron/lessons/04-specsimvoly-cron/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

# Спецсимволы: звёздочка, слэш, диапазоны и @daily

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

Спецсимволы cron превращают пять полей из загадки в точный инструмент: звёздочка означает «каждое значение», слэш задаёт шаг, дефис — диапазон, запятая — список, а дескрипторы вроде @daily и @every 1h30m заменяют собой целые выражения. В этом уроке пройдём по каждому символу на примерах AddFunc, поймём, к чему привязан шаг, и увидим, чем интервальные расписания отличаются от сетки часов.

Выражение из одних звёздочек — самая неточная команда, какую можно дать планировщику: «каждую минуту, всегда». Реальные задачи почти всегда требуют сужения: не каждую минуту, а каждые десять; не каждый день, а по будням; не в произвольный момент, а в полдень. Для этого в синтаксисе есть четыре спецсимвола и один особый случай — дескрипторы с собакой, которые robfig/cron поддерживает поверх классики. Символы не заучиваются: их ровно четыре, каждый читается одним словом, а путаница возникает в двух местах — к чему привязан шаг и чем @every отличается от выражения с полем часов. Эти два вопроса мы разберём отдельно и с примерами, потому что именно на них строятся неверные ожидания: задача запускается в правильные, но не те моменты, и код при этом безупречен. Хорошая новость в том, что словарь конечен: освоив восемь конструкций этого урока, вы сможете записать практически любое расписание, которое встретите в требованиях.

### Звёздочка и слэш: шаг по расписанию

Звёздочка в поле означает весь диапазон значений этого поля: каждая минута, каждый час, каждое число. Сама по себе она редко встречается во всех пяти полях — чаще её комбинируют, оставляя звёздочкой то, что не ограничиваем. Слэш задаёт шаг: выражение */5 в поле минут — это каждая пятая минута по сетке часа, то есть 00, 05, 10 и так до 55. Ключевая деталь, которую многие пропускают: шаг отсчитывается от начала поля, а не от момента запуска планировщика. Процесс мог подняться в 13:42, но задача с шагом в десять минут всё равно пойдёт в 13:50, а не через десять минут после старта. Сетка стабильна и одинакова у всех экземпляров сервиса — это удобно для задач, где важна синхронность: отчёты собираются по одним и тем же границам, какие бы рестарты ни случались. Минимальный шаг ограничен полем: в минутах меньше единицы не бывает, для более частой работы нужен секундный режим из [отдельного урока](/courses/cron/lessons/08-sekundy-raspisaniya/).

### Шпаргалка спецсимволов

| Символ | Читается как | Пример в поле минут | Что означает пример |
| --- | --- | --- | --- |
| звёздочка | каждое значение | звёздочка | каждая минута часа |
| слэш | шаг | */5 | 00, 05, 10 — каждую пятую минуту |
| дефис | диапазон | 10-20 | с 10-й по 20-ю минуту включительно |
| запятая | список | 0,15,30,45 | четыре момента каждый час |
| собака | дескриптор | @hourly | в начале каждого часа |

### Диапазоны, списки и имена полей

Дефис превращает два числа в диапазон: 9-17 в поле часов — это с девяти утра до пяти вечера включительно. Запятая перечисляет значения: 0,15,30,45 — четыре момента каждый час. Оба приёма работают в любом поле и свободно комбинируются с шагом и звёздочкой, а в полях месяца и дня недели вместо чисел разрешены имена: JAN-DEC и MON-SUN — регистр не важен. Имена — не украшение: строка с MON-FRI читается без шпаргалки, а выражение с числом 1 в последнем поле постоянно провоцирует вопрос «понедельник или воскресенье?». Внутри диапазона и списка можно мешать имена с числами — парсер приведёт их к одному виду. Единственное правило вкуса: не собирайте в одном выражении всё сразу. Когда в строке одновременно живут шаг, диапазон, список и имена, она перестаёт читаться на ревью, и проще разбить задачу на две с двумя простыми выражениями — планировать их можно по-прежнему в одном планировщике.

### Комбинируем спецсимволы в AddFunc

```go
// каждые 10 минут в рабочие часы по будням
c.AddFunc("*/10 9-17 * * MON-FRI", healthProbe)

// полдень и шесть вечера по выходным
c.AddFunc("0 12,18 * * SAT,SUN", weekendDigest)

// с 10-й по 45-ю минуту шагом в пять
c.AddFunc("10-45/5 * * * *", reportChunk)

// интервал без привязки к часам
c.AddFunc("@every 90m", syncRates)
```

Первая строка — типовой продакшен-профилинг: шаг поверх диапазона часов, будни в поле дней. Вторая показывает список в поле часов и список дней: поля независимы, задача идёт на пересечении. Третья — шаг, применённый к диапазону минут: 10, 15, 20 и так до 45, после чего тишина до следующего часа. Четвёртая — интервальный дескриптор, о нём подробнее ниже.

### Шаг поверх диапазона и особый случай N-со-слэшем

Шаг можно вешать не только на звёздочку, но и на диапазон: 10-45/5 в поле минут означает 10, 15, 20 и так до 45 — включительно, потому что цикл парсера идёт до конца диапазона. Это удобно для окон: полчаса интенсивной проверки внутри часа, потом тишина. Отдельного упоминания заслуживает форма из одного числа и слэша. В robfig/cron v3 она документирована прямо в исходнике парсера: N/шаг трактуется как N-максимум/шаг, то есть 3/10 в минутах — это 3, 13, 23 и далее до 53. Не все планировщики трактуют эту форму одинаково, поэтому в переносимых конфигах надёжнее писать диапазон целиком: 3-59/10 говорит без двусмысленностей и читается одинаково у всех. Правило то же, что с ИЛИ в дневных полях: неоднозначные формы лучше не использовать не потому, что они сломаны, а потому что их смысл варьируется от системы к системе, а расписания живут дольше, чем команда, которая их писала.

### Дескрипторы: @daily, @hourly и @every

Поверх пяти полей robfig/cron v3 поддерживает дескрипторы — читаемые сокращения для типовых расписаний. @yearly и его синоним @annually — раз в год, первого января в полночь; @monthly — первое число каждого месяца; @weekly — воскресенье в полночь; @daily и @midnight — каждый день в 00:00; @hourly — в начале каждого часа. Дескриптор разворачивается в обычное выражение в зоне планировщика, поэтому @daily в сервисе с московской зоной идёт в полночь по Москве — про пояса подробно в [уроке про часовые пояса](/courses/cron/lessons/07-chasovye-poyasa/). Отдельно стоит @every — единственный дескриптор, который не привязан к часам вообще: после него пишется длительность в формате time.Duration, например @every 1h30m или @every 90m, и задача ходит с этим интервалом. Минимальный шаг — одна секунда, меньшие значения парсер округлит вверх. Дескрипторы не добавляют новых возможностей — то же самое пишется пятью полями, — но выражение «@daily» читается быстрее, чем пять нулей и звёздочек, а в конфигах читаемость напрямую снижает число ошибок. Внутри библиотеки каждый дескриптор собирает ту же битовую маску значений поля, что и разобранная строка: разницы в поведении и производительности нет.

### Шесть дескрипторов одним списком

```go
c.AddFunc("@yearly", archiveYear)      // 1 января, 00:00
c.AddFunc("@annually", archiveYear)    // синоним @yearly
c.AddFunc("@monthly", billCustomers)   // 1-го числа, 00:00
c.AddFunc("@weekly", weeklyDigest)     // воскресенье, 00:00
c.AddFunc("@daily", cleanupSessions)   // каждый день, 00:00
c.AddFunc("@midnight", cleanupSessions) // синоним @daily
c.AddFunc("@hourly", refreshCache)     // в начале каждого часа
c.AddFunc("@every 1h30m", syncRates)   // интервал, без привязки к часам
```

Все дескрипторы принимаются тем же AddFunc и тем же парсером — ParseStandard тоже их понимает, так что проверка выражений из конфига покрывает и этот случай. Синонимы @annually и @midnight даны ради читаемости: выбирайте одну форму и держите её по всему проекту, иначе конфиг превратится в коллекцию стилей.

> **@every: интервал живёт своей жизнью.** @every отсчитывает интервал не по сетке часов, а от предыдущего срабатывания: первый запуск случится через полтора часа после старта планировщика, следующий — через полтора часа после него. Долгая задача сдвигает цепочку, а рестарт процесса переносит все моменты заново. Если расписание обязано совпадать с круглыми часами — например, отчёт «в начале часа» для сравнения с другими данными, — берите выражение с полем минут, а не @every.

### Когда дескриптор, а когда выражение

- Суточные и более редкие запуски в полночь — @daily и @weekly: читается мгновенно, ошибиться негде.
- Запуск «в начале часа» — @hourly, если вам не нужен контроль над минутой.
- Неизвестный заранее интервал из конфига — @every с Duration: парсинг расписания не нужен.
- Конкретное время, шаги, окна и дни недели — только пять полей: дескриптор «в 09:30» не умеет.
- Смешивать стили в одном конфиге можно, но лучше выбрать один стиль расписаний на проект.

### Проверка: привязка шага

**Вопрос:** Процесс стартовал в 13:42. Когда выполнится задача с выражением */10 в поле минут?

- Через 10 минут после старта, в 13:52
- ✅ В 13:50 — по сетке шага от начала часа
- В 14:00 ровно
- Ни разу: старт не попал в сетку шага

Шаг со звёздочкой отсчитывается от начала поля, а не от запуска процесса: минуты 13:50, 14:00, 14:10 и так далее. Именно поэтому расписания с шагом синхронны у всех экземпляров сервиса и переживают рестарты без сдвига сетки. Для данных, которые сравниваются между инстансами — агрегаты, проверки, кэш — это свойство важнее удобства @every.

### Проверка: @every против поля часов

**Вопрос:** Чем @every 1h отличается от выражения с нулевой минутой каждый час?

- Ничем: это два синонима одного расписания
- ✅ @every идёт интервалом от предыдущего запуска и плывёт после рестартов; выражение ходит по круглым часам
- @every учитывает часовой пояс, а выражение нет
- Выражение срабатывает чаще: каждую секунду в течение часа

Дескриптор @every — это Duration-интервал, привязанный к моменту предыдущего срабатывания, а выражение с полем минут — сетка настенных часов. Для сравнимых по времени данных, алертов и отчётов сетка предсказуемее; @every хорош для фоновой подметки без привязки к часам.

### Во сколько выполнится задача?

```go
c := cron.New()
_, err := c.AddFunc("@daily", nightlyReport)
if err != nil {
        log.Fatal(err)
}
c.Start()

// процесс стартовал в 14:00
// во сколько выполнится nightlyReport?
```

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

- Сразу при старте: дескриптор срабатывает немедленно
- В 14:00 следующего дня — ровно через сутки
- ✅ В 00:00 ближайшей ночи по зоне планировщика
- Ни разу: @daily требует пяти полей

@daily разворачивается в полночь каждого дня — и первый запуск случится в ближайшую полночь, независимо от того, когда процесс поднялся. Интервальная логика «через сутки после старта» — это территория @every 24h, и это принципиально другое расписание.

### Соберите три расписания

Впишите звёздочку, диапазон будней и ключевое слово интервального дескриптора.

```go
c.AddFunc("___/15 9-17 * * ___", officePing)
c.AddFunc("@___ 1h30m", syncRates)
```

Подсказка: Шаг всегда пишется после звёздочки; будни — MON-FRI; интервальный дескриптор начинается со слова every.

Правильные ответы в пропусках: `*`, `MON-FRI`, `every`

### Соберите регистрацию полечасовой проверки

Расставьте строки в порядке выполнения: от создания планировщика до вечного ожидания в main.

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

1. `c := cron.New()`
2. `_, err := c.AddFunc("*/30 * * * *", probe)`
3. `if err != nil { log.Fatal(err) }`
4. `c.Start()`
5. `fmt.Println("проверка раз в полчаса запланирована")`
6. `select {}`

### Карточки: спецсимволы и дескрипторы

- **Звёздочка** — Весь диапазон значений поля: каждая минута, каждый час. Комбинируется со шагом и остальными символами.
- **Слэш (шаг)** — Повтор через N значений по сетке поля: */5 в минутах — 00, 05, 10. Шаг отсчитывается от начала поля, а не от старта процесса.
- **N/шаг в robfig v3** — Особая форма: трактуется как N-максимум/шаг — 3/10 в минутах это 3, 13, 23 и так далее. В переносимых конфигах лучше писать диапазон целиком.
- **Дефис и запятая** — Диапазон 9-17 включает оба конца; список 0,15,30,45 перечисляет значения. Работают в любом поле, комбинируются с шагом.
- **@daily** — Дескриптор: каждый день в 00:00 по зоне планировщика. Синоним @midnight; полный набор — @yearly, @monthly, @weekly, @hourly.
- **@every 1h30m** — Интервальное расписание в стиле time.Duration: от предыдущего срабатывания, без привязки к часам. Рестарт переносит точку отсчёта.

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

Четыре спецсимвола и шесть дескрипторов закрывают почти весь словарь расписаний, который нужен сервису: шаги, окна, списки и читаемые сокращения. Дальше выражения станут параметром задач: в [уроке про AddFunc](/courses/cron/lessons/05-addfunc/) разберём, какие функции годятся в задачи, как передавать им параметры через замыкания и почему тело задачи должно быть маленьким. Если хочется сразу увидеть дескрипторы в деле против сетки часов — загляните в [урок про сравнение с Ticker](/courses/cron/lessons/24-ticker-vs-cron/), там эта разница решает выбор инструмента. Про секунды и шестипольные конфиги из старых статей — [урок про NewWithSeconds](/courses/cron/lessons/08-sekundy-raspisaniya/). В домашнем задании — перевод человеческих формулировок в выражения со спецсимволами и проверка себя через ParseStandard.

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

- Звёздочка — весь диапазон поля; слэш — шаг от начала поля, а не от старта процесса.
- Дефис даёт диапазон включительно, запятая — список; в месяцах и днях недели работают имена JAN-DEC и MON-SUN.
- Шаг вешается и на диапазон: 10-45/5 — это 10, 15, 20 и так до 45; форма N/шаг в robfig v3 означает N-максимум/шаг.
- Дескрипторы @yearly, @monthly, @weekly, @daily, @hourly — читаемые замены типовых выражений в зоне планировщика.
- @every — интервал Duration от предыдущего срабатывания: плывёт после рестартов и не привязан к круглым часам.
- Для отчётов и сравнимых данных выбирайте сетку часов; @every — для фоновой подметки без привязки ко времени суток.

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

Переведите пять человеческих формулировок в выражения: «каждые двадцать минут по будням с 8 до 20», «полночь воскресенья», «полдень и полночь по выходным», «окно с 10 по 40 минуту шагом в десять», «раз в сорок пять минут без привязки к часам». Проверьте все пять через cron.ParseStandard в отдельной программе, затем зарегистрируйте их в одном планировщике из урока и убедитесь по логу, что первые тики совпали с ожиданиями.

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

### Чем @daily лучше выражения 0 0 * * *?

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

### Почему шаг не совпадает с моментом запуска процесса?

Шаг отсчитывается по сетке значений поля от его начала: минуты с шагом десять всегда кратны десяти, независимо от того, когда поднялся планировщик. Это делает расписания синхронными между инстансами и рестартами. Интервальное поведение «каждые N минут от старта» реализует @every — но там сетка плывёт.

### Можно ли задать шаг меньше минуты?

Не в стандартном парсере: пять полей заканчиваются минутами. Для секундного расписания планировщик создаётся с опцией секунд — cron.NewWithSeconds, и тогда первым полем становятся секунды. Помните, что частые тики — это постоянный дожим на зависимости: перед включением секунд оцените, что задача делает.
