Skip to content

atlorium-api/cron-expression-parser-api-client

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cron API — разбор и валидация cron-выражений, ближайшие запуски

Русский · English

examples license API

Готовые примеры работы с 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() в каждом из шести примеров:

  1. Берёт у API факты — список ближайших запусков occurrences[].
  2. Сама вычисляет минимальный интервал между соседними запусками.
  3. Сверяет его с порогами (DANGER_MINUTES = 5, WARNING_MINUTES = 60) и выносит вердикт.

Интервал считается минимальный, а не средний: расписание 0 9 * * 1-5 даёт разрыв в трое суток на выходных, и среднее его размажет. Опасность всегда в самом плотном месте.

Как поставить в CI

Ненулевой код выхода означает, что шаг 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 собирает выражение из шаблона.
  • Перевод расписаний между таймзонами при переезде инфраструктуры.

Быстрый старт за 60 секунд

Проверить 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 Обратная операция: собрать выражение из шаблона расписания

POST /api/Cron/evaluate

Тело запроса (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, ни предупреждения вы не получите, просто тихо придёт не тот результат. Опечатка в имени поля здесь дороже, чем обычно.

POST /api/Cron/build

Тело запроса (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 }

Поля ответа evaluate

Поле Тип Что содержит
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: день месяца И день недели

Это надо знать до того, как вы перенесёте сюда выражение из рабочего 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. Если оба поля вам на самом деле не нужны (а в большинстве расписаний это так), оставьте одно из них *, и семантика совпадёт с любой реализацией. Кстати, именно ради таких находок и стоит смотреть на список реальных запусков, а не на само выражение.

Другие API Atlorium

Валидация расписания — обычно часть более общей задачи «проверить конфиг перед деплоем». Из того же аккаунта и тем же ключом доступны:

Полный каталог — atlorium.com

Ссылки

Лицензия

MIT — берите код и используйте как хотите, в том числе в коммерческих проектах.

About

API разбора cron-выражений: валидация, расшифровка, расчёт ближайших запусков с учётом таймзон. Примеры на Python, TypeScript, Go, Java, C#, PHP. Cron expression parser API client.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors