Русский · English
Готовые примеры работы с API разбора cron-выражений на шести языках: Python, TypeScript (Node.js), Go, Java, C#, PHP. Проверить cron-выражение на синтаксис, узнать следующий запуск cron и посмотреть cron-расписание в нужной таймзоне — с учётом перехода на летнее время. Плюс обратная операция: собрать cron-синтаксис из шаблона («каждые 15 минут», «по будням в 9:00»), не вспоминая, что в каком поле стоит.
Каждый пример запускается сразу — без регистрации, без ключа, без карты. В коде зашит публичный демо-ключ.
git clone https://github.com/atlorium-api/cron-expression-parser-api-client
cd cron-expression-parser-api-client/python && pip install -r requirements.txt && python main.pyДемо-ключ: на этом сервисе он возвращает НАСТОЯЩИЙ разбор, а не мок —
cron считается локально, мокать нечего.
Выражение: 0 9 * * 1-5
Нормализовано: 0 9 * * 1-5
Сегменты: 0 | 9 | * | * | 1-5
Таймзона: Europe/Moscow — (UTC+03:00) Moscow Time
переход на летнее время учитывается (Moscow Summer Time)
Ближайшие 5 запусков:
1. вт 2026-07-14 09:00 (+03:00)
2. ср 2026-07-15 09:00 (+03:00)
3. чт 2026-07-16 09:00 (+03:00)
4. пт 2026-07-17 09:00 (+03:00)
5. пн 2026-07-20 09:00 (+03:00)
[i] Минимальный интервал между запусками: 1440 мин (1 д 0 ч).
Вердикт: OK — расписание не чаще, чем раз в 60 мин.
Код выхода: 0
Демо-ключ здесь отдаёт настоящий результат, а не мок. Это редкий случай среди сервисов Atlorium, и сказать об этом стоит прямо: разбор cron — чистая локальная логика, внешних источников нет, и подделывать нечего. Ответ помечен заголовком
X-Atlorium-Sandbox: true, но расписание в нём подлинное — таймзона учтена, переходы времени учтены. Всему выводу в этом README можно верить как есть, он снят с реальных прогонов. У сервисов с внешними источниками (ЕГРЮЛ, BIN, погода) песочница отдаёт правдоподобные моки — здесь нет.
Это не «печаталка JSON», а готовая проверка cron-расписания для CI. Программа сама решает, всё ли в порядке, и сообщает об этом кодом выхода — парсить её вывод не нужно:
| Код выхода | Когда |
|---|---|
0 |
OK — расписание реже, чем раз в 60 минут |
1 |
ВНИМАНИЕ — задача запускается чаще раза в час: убедитесь, что так и задумано |
2 |
ОПАСНО — чаще раза в 5 минут: почти наверняка опечатка |
3 |
ОШИБКА — выражение невалидно, либо не удалось выполнить проверку (ошибка API или сети) |
Классическая авария планировщика — * 9 * * * вместо 0 9 * * *.
Задумано: «каждый день в 9:00». Получилось: «каждую минуту с 9:00 до 9:59» — 60 запусков в час вместо одного. Звёздочка в поле минут означает не «в начале часа», а «в каждую минуту». Такое выражение абсолютно валидно, cron его примет молча, а обнаружится это по счёту за облако или по упавшей базе.
Вот что печатает тот же самый пример на этом выражении — настоящий прогон, python main.py "* 9 * * *":
Демо-ключ: на этом сервисе он возвращает НАСТОЯЩИЙ разбор, а не мок —
cron считается локально, мокать нечего.
Выражение: * 9 * * *
Нормализовано: * 9 * * *
Сегменты: * | 9 | * | * | *
Таймзона: Europe/Moscow — (UTC+03:00) Moscow Time
переход на летнее время учитывается (Moscow Summer Time)
Ближайшие 5 запусков:
1. вт 2026-07-14 09:00 (+03:00)
2. вт 2026-07-14 09:01 (+03:00)
3. вт 2026-07-14 09:02 (+03:00)
4. вт 2026-07-14 09:03 (+03:00)
5. вт 2026-07-14 09:04 (+03:00)
[i] Минимальный интервал между запусками: 1 мин (1 мин).
[!] Задача запускается чаще, чем раз в 5 мин — это почти наверняка опечатка (например, «*» вместо «0» в поле минут)
Вердикт: ОПАСНО — расписание почти непрерывное, деплой стоит остановить.
Код выхода: 2
Разница между двумя прогонами — один символ. Список ближайших запусков делает её очевидной за секунду.
API не предупреждает о слишком частом расписании. На * 9 * * * он честно возвращает isValid: true и пустой warnings — с точки зрения синтаксиса всё безупречно, и это правильно: планировщик не может знать, что вы имели в виду.
Поэтому функция validateSchedule() в каждом из шести примеров:
- Берёт у API факты — список ближайших запусков
occurrences[]. - Сама вычисляет минимальный интервал между соседними запусками.
- Сверяет его с порогами (
DANGER_MINUTES = 5,WARNING_MINUTES = 60) и выносит вердикт.
Интервал считается минимальный, а не средний: расписание 0 9 * * 1-5 даёт разрыв в трое суток на выходных, и среднее его размажет. Опасность всегда в самом плотном месте.
Ненулевой код выхода означает, что шаг GitHub Actions покраснеет сам — без grep по выводу:
name: cron-lint
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- run: pip install -r requirements.txt
working-directory: python
# Валидируем расписание перед деплоем. Опечатка «* 9 * * *» — красный джоб.
- run: python main.py "$(yq '.schedule' deploy/job.yaml)"
working-directory: python
env:
CRON_TZ: Europe/Moscow
ATLORIUM_API_KEY: ${{ secrets.ATLORIUM_API_KEY }}Cron-выражение — один из немногих форматов, который невозможно проверить глазами. */7 * * * * — это не «каждые 7 минут», а «на 0, 7, 14, 21, 28, 35, 42, 49 и 56-й минуте»: между 56-й и следующей нулевой пройдёт 4 минуты, а не 7. 0 0 1 * 1 в системном crontab означает «первого числа или по понедельникам», а здесь — «первого числа, если оно понедельник» (см. раздел ниже — это важное расхождение). Такие вещи видно только по списку реальных запусков.
Типовые применения:
- Валидация в CI перед выкаткой расписания — то, что делает
validateSchedule(). - Показ пользователю в UI: «следующий запуск — завтра в 09:00» рядом с полем ввода cron.
- Конструктор расписаний без ручного ввода:
/api/Cron/buildсобирает выражение из шаблона. - Перевод расписаний между таймзонами при переезде инфраструктуры.
Проверить API вообще без клонирования:
curl -X POST "https://atlorium.com/api/Cron/evaluate" \
-H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
-H "Content-Type: application/json" \
-d '{"expression":"0 9 * * 1-5","timeZoneId":"Europe/Moscow","take":3}'| Язык | Запуск | Требуется |
|---|---|---|
| Python | pip install -r requirements.txt && python main.py |
Python 3.10+ |
| TypeScript / Node.js | npm install && npm start |
Node.js 20+ |
| Go | go run . |
Go 1.22+ |
| Java | java Main.java |
JDK 11+ (без зависимостей) |
| C# | dotnet run |
.NET 8+ |
| PHP | php main.php |
PHP 8.1+ |
Передать своё выражение аргументом: python main.py "*/15 * * * *"
Сменить таймзону: CRON_TZ=America/New_York python main.py
Ключ передаётся в заголовке Authorization:
Authorization: Bearer ВАШ_КЛЮЧ
| Ключ | Что делает |
|---|---|
ak_sandbox_demo_mockdata_v1 |
Демо-ключ. Публичный, один на всех. Регистрации не требует, денег не списывает. На этом сервисе возвращает настоящий разбор, а не мок — считать нечего подделывать |
| Боевой ключ | То же самое, но на вашем аккаунте: лимит считается по ключу, а не по IP, общему на всех. Получить в личном кабинете: atlorium.com |
Переход на боевой ключ не требует правок в коде — все примеры читают переменную окружения:
export ATLORIUM_API_KEY="ak_ваш_боевой_ключ"Каждый ответ песочницы помечен заголовком X-Atlorium-Sandbox: true.
Базовый адрес: https://atlorium.com
Оба эндпоинта — POST с JSON-телом. GET-варианта и «корневого» маршрута /api/cron не существует (вернёт 404).
| Метод | Путь | Назначение |
|---|---|---|
POST |
/api/Cron/evaluate |
Разбор и валидация выражения + ближайшие запуски в заданной таймзоне |
POST |
/api/Cron/build |
Обратная операция: собрать выражение из шаблона расписания |
Тело запроса (CronEvaluateRequest):
| Поле | Тип | Описание |
|---|---|---|
expression |
string | Обязательное. Cron-выражение: 5 полей (мин час день месяц д.н.) или 6 (первое поле — секунды, формат Quartz) |
timeZoneId |
string | Часовой пояс, в котором считать запуски. IANA: Europe/Moscow, America/New_York, UTC. По умолчанию UTC |
fromUtc |
datetime|null | Точка отсчёта. По умолчанию — «сейчас» |
take |
int | Сколько ближайших запусков вернуть. От 1 до 100, по умолчанию 10 |
Осторожно с именами полей. Поле называется
timeZoneId, а неtimeZone, иtake, а неcount. Неизвестные поля сервер молча игнорирует и подставляет умолчания (UTC, 10 запусков) — ни400, ни предупреждения вы не получите, просто тихо придёт не тот результат. Опечатка в имени поля здесь дороже, чем обычно.
Тело запроса (CronBuildRequest). В OpenAPI-спеке template — целое число без расшифровки; значения ниже подтверждены живыми запросами:
| Поле | Тип | Описание |
|---|---|---|
template |
int | Тип шаблона, см. таблицу ниже |
interval |
int | Шаг для шаблонов 1 и 3 (каждые N минут / часов) |
timeOfDay |
string | Время запуска, "09:30:00" — для шаблонов 4, 5, 6 |
dayOfMonth |
int | День месяца 1–31 — для шаблона 6 |
weekDays |
int[] | Дни недели — для шаблона 5. 0 = воскресенье … 6 = суббота |
includeSeconds |
bool | Добавить шестое поле секунд (формат Quartz) |
template |
Смысл | Пример ответа (includeSeconds: false) |
|---|---|---|
0 |
Каждую минуту | * * * * * |
1 |
Каждые N минут (interval) |
*/15 * * * * |
2 |
Каждый час | 0 * * * * |
3 |
Каждые N часов (interval) |
0 */15 * * * |
4 |
Ежедневно в timeOfDay |
30 9 * * * |
5 |
По дням недели weekDays в timeOfDay |
0 9 * * MON,WED,FRI |
6 |
Ежемесячно dayOfMonth в timeOfDay |
30 9 15 * * |
Ответ (CronBuildResult): { "expression": "...", "warnings": [], "error": "", "success": true }
| Поле | Тип | Что содержит |
|---|---|---|
isValid |
bool | Ключевое поле. Выражение разобрано и хотя бы один запуск найден |
error |
string | Текст ошибки при isValid: false. Например: Ожидается 5 или 6 полей cron, получено: 4. Пустая строка, если всё в порядке |
rawExpression |
string | Выражение ровно в том виде, в каком его прислали |
normalizedExpression |
string | После нормализации: схлопнуты лишние пробелы, Quartz-символ ? заменён на *, отброшено поле года |
segments |
string[] | Нормализованное выражение, разложенное по полям: ["0","9","*","*","1-5"] |
occurrences |
datetime[] | Ближайшие запуски в запрошенной таймзоне, ISO-8601 со смещением: 2026-07-14T09:00:00+03:00. Ровно то, ради чего обычно и зовут этот API |
warnings |
string[] | Предупреждения нормализации (замена ?, отброшенное поле года). Про слишком частое расписание сервер НЕ предупреждает — это работа клиента |
timeZone |
object | Разобранный часовой пояс, см. ниже |
Объект timeZone:
| Поле | Тип | Что содержит |
|---|---|---|
id |
string | Идентификатор: Europe/Moscow |
hasIanaId |
bool | Распознан ли ID как IANA (а не как Windows-имя вида Russian Standard Time) |
displayName |
string | Человекочитаемое имя: (UTC+03:00) Moscow Time |
standardName |
string | Имя зимнего времени: Moscow Standard Time |
daylightName |
string | Имя летнего времени: Moscow Summer Time |
baseUtcOffset |
string | Базовое смещение от UTC: 03:00:00 |
supportsDaylightSavingTime |
bool | Предусмотрен ли в этой зоне переход на летнее время |
| Код | Причина | Что делать |
|---|---|---|
400 |
Тело запроса пустое или не передано expression |
Проверьте, что тело — JSON и в нём есть непустое поле expression |
401 |
Ключ отсутствует, просрочен или недействителен | Проверьте заголовок Authorization |
402 |
Недостаточно кредитов на балансе | Пополнить на atlorium.com |
404 |
Такого маршрута нет | Помните: корневого /api/cron не существует, только /evaluate и /build |
429 |
Превышен rate-limit | Повторить с задержкой — но с потолком ожидания (см. ниже) |
500 |
Внутренняя ошибка при разборе | Сообщите нам: support@atlorium.com |
Кривое выражение — это НЕ ошибка HTTP. Сервер отвечает 200 OK с isValid: false и текстом в error. Так и задумано: разбор выполнен, просто результат отрицательный. Клиент, который проверяет только HTTP-код, пропустит невалидное расписание — примеры проверяют именно isValid.
Во всех шести примерах HTTP-коды разложены в человекочитаемые причины — смотрите класс AtloriumError.
Про 429 и потолок ожидания. Исчерпав часовую квоту, сервер честно просит подождать десятки минут. Клиент, который слепо спит столько, сколько попросили, зависает на всё это время (а в CI съедает бюджет джоба). Поэтому в примерах есть MAX_RETRY_DELAY = 120 секунд: дольше потолка не ждём, а честно сообщаем, что квота исчерпана, и выходим с кодом 3.
Оплата pay-as-you-go, без подписки: платите только за выполненные запросы.
Лимиты этого сервиса щедрые: он считает всё локально, поэтому вызывать /evaluate из UI при каждом изменении поля ввода вполне реально. Актуальные значения — на atlorium.com/pricing.
Актуальные цены и лимиты: atlorium.com/pricing
Как узнать следующий запуск cron-выражения? Один POST на /api/Cron/evaluate — и occurrences[0] даст ближайший запуск, уже приведённый к нужной таймзоне. take управляет тем, сколько запусков вернуть (до 100).
Учитывается ли переход на летнее время? Да. Расчёт идёт по базе часовых поясов операционной системы, и сдвиг стрелок применяется сам. Проверить легко: 0 9 * * * в Europe/Berlin зимой даёт 09:00+01:00, а летом — 09:00+02:00: местное время запуска остаётся девятью утра, меняется смещение относительно UTC. Именно это и нужно, и именно это ломается, когда расписание считают в UTC вручную. Отдельно: у Europe/Moscow флаг supportsDaylightSavingTime приходит true (так помечена зона в базе), но фактическое смещение +03:00 круглый год — Россия отменила переход в 2014-м, и запуски это корректно отражают.
Чем это лучше библиотеки croniter / cron-parser в моём коде? Ничем, если у вас уже есть готовая библиотека для вашего языка и вы ей доверяете. Смысл API — в тех местах, где библиотеки нет или её тянуть не хочется: единая проверка на шести языках сразу, шаг в CI без установки зависимостей, валидация пользовательского ввода на фронтенде без отправки кода в браузер. И одна семантика разбора на весь зоопарк сервисов вместо трёх разных библиотек с расходящимся поведением на */7.
Почему API не предупреждает, что расписание слишком частое? Потому что это не свойство выражения, а свойство вашей задачи: */1 * * * * для сборщика метрик — норма, для рассылки писем — катастрофа. Сервер отдаёт факты (когда именно будет запуск), решение принимает клиент. Готовая логика решения — функция validateSchedule() во всех шести примерах, пороги вынесены в константы.
Поддерживается ли синтаксис Quartz с секундами? Да. Шесть полей — первое поле секунды. Символ ? (Quartz) принимается и нормализуется в *, о чём приходит запись в warnings. Поле года (7-е) отбрасывается, тоже с предупреждением.
Есть ли SDK-пакет в PyPI/npm? Пока нет — это самодостаточные примеры без зависимостей, чтобы их можно было скопировать в свой проект целиком.
Это надо знать до того, как вы перенесёте сюда выражение из рабочего crontab.
Если в выражении заданы и день месяца, и день недели (то есть ни то, ни другое не *), классический системный cron (Vixie/POSIX, тот, что в Linux) соединяет их через ИЛИ: 0 0 1 * 1 сработает и первого числа каждого месяца, и каждый понедельник.
Этот API работает иначе. Он построен на собственной реализации, а она применяет И: запуск происходит, только когда первое число месяца выпадает на понедельник. Проверено живым запросом — 0 0 1 * 1, начиная с 1 января 2027:
2027-02-01 понедельник
2027-03-01 понедельник
2027-11-01 понедельник
2028-05-01 понедельник
2029-01-01 понедельник
2029-10-01 понедельник
Шесть запусков за три года вместо примерно двухсот. Разница огромная, и молчать о ней было бы нечестно.
Практический вывод: если оба поля заданы явно, не переносите выражение между системным cron и этим API вслепую — сначала сравните списки occurrences. Если оба поля вам на самом деле не нужны (а в большинстве расписаний это так), оставьте одно из них *, и семантика совпадёт с любой реализацией. Кстати, именно ради таких находок и стоит смотреть на список реальных запусков, а не на само выражение.
Валидация расписания — обычно часть более общей задачи «проверить конфиг перед деплоем». Из того же аккаунта и тем же ключом доступны:
- Погодные данные — текущие условия по координатам
- Проверка SSL-сертификата — срок действия, SAN, цепочка доверия
- Адреса ГАР/ФИАС — поиск и подсказки по официальному справочнику
- DNS Lookup — записи домена, MX, SPF, DMARC
- Стандартизация адреса — разбор строки на компоненты и оценка качества
- CIDR-калькулятор — разбиение сети на подсети и проверка вхождения IP
Полный каталог — atlorium.com
- Документация API (Swagger): atlorium.com/cronAPI
- Описание сервиса: atlorium.com/cronDescription
- Веб-интерфейс: atlorium.com/cronGUI
- OpenAPI-спецификация: cron_ru.json
- Поддержка: support@atlorium.com
MIT — берите код и используйте как хотите, в том числе в коммерческих проектах.