Справочник · формат данных
Синтаксис и форматы
Что принимает поле выражения, какие ошибки оно показывает, как устроены файлы JSON и .ics и как проверить отпечаток. Сетевого API нет: всё, что описано ниже, происходит в вашем браузере.
Принимаемый синтаксис
| элемент | пример | значение |
|---|---|---|
| Поля | 30 2 * * * | минута 0–59, час 0–23, день месяца 1–31, месяц 1–12, день недели 0–7 |
| Звёздочка | * | все значения поля; поле со звёздочкой в начале считается неограниченным |
| Число | 15 | одно значение |
| Диапазон | 1-5 | включительно, начало не больше конца |
| Список | 0,15,30 | значения через запятую; элементом может быть диапазон или шаг |
| Шаг | */15, 10-50/20 | от начала диапазона: 0, 15, 30, 45 и 10, 30, 50 |
| Имена | jan-mar, mon,wed,fri | jan–dec и sun–sat в любом регистре, в том числе в диапазонах и списках |
| Воскресенье | 0, 7 | оба значения — воскресенье |
| Макросы | @daily | @yearly и @annually = 0 0 1 1 *, @monthly = 0 0 1 * *, @weekly = 0 0 * * 0, @daily и @midnight = 0 0 * * *, @hourly = 0 * * * * |
Отклоняемый синтаксис
Ошибка в поле показывается на его картуше: картуш краснеет, а под лентой появляется текст с названием поля. Ниже — точные тексты.
| ввод | текст ошибки |
|---|---|
@reboot | @reboot привязан к старту системы, а не ко времени — в ведомость не попадает. |
0 0 * * * * | Полей 6 — похоже на формат с секундами или годом; здесь пятипольный cron. |
0 0 L * * | день месяца: «L» — это расширение других планировщиков; CronLens проверяет классический пятипольный cron |
0 0 * * 5#3 | день недели: «5#3» — это расширение других планировщиков; CronLens проверяет классический пятипольный cron |
5/10 * * * * | минута: шаг пишется после * или диапазона: */10 или 5-59/10 |
0 24 * * * | час: час 24 вне 0–23 |
*/0 * * * * | минута: шаг 0 — шаг должен быть не меньше 1 |
0 5-2 * * * | час: диапазон 5-2 обратный: начало больше конца |
0 1,,2 * * * | час: пустой элемент списка — лишняя запятая |
0 0 * foo * | месяц: «foo» — такого имени нет: месяцы пишутся jan–dec |
0 0 1 | Полей 3, а нужно пять: минута, час, день месяца, месяц, день недели. |
| пустое поле | Введите выражение: пять полей через пробел или макрос вроде @daily. |
| больше 120 символов | Выражение длиннее 120 символов. |
пояс Europe/Moskva | Пояс «Europe/Moskva» не найден в базе браузера. Похожие: Europe/Moscow, … (до трёх подсказок) |
Прочие проверки: имя задания — от 1 до 40 символов, без управляющих символов и без повторов; длительность — целое число от 1 до 1440; дата начала окна обязательна. Для имён вроде Etc/GMT-3 рядом с полем появляется напоминание, что знак в них обратный.
Три правила на примере Берлина
Задание 30 2 * * * в поясе Europe/Berlin. 25.10.2026 в 03:00 часы переводят на 02:00 (+02:00 → +01:00), 28.03.2027 в 02:00 — на 03:00 (+01:00 → +02:00).
| правило | 25.10.2026, повтор | 28.03.2027, разрыв |
|---|---|---|
| С догоном | один раз: 02:30 +02:00 = 00:30Z | ↷ 03:00 +02:00 = 01:00Z |
| Пропуск и один раз | один раз: 02:30 +02:00 = 00:30Z | ⊘ потерян |
| Буквально | дважды: 00:30Z (↺¹) и 01:30Z (↺²) | ⊘ потерян |
Для задания по маске, например */15 2 * * *, «С догоном» ведёт себя иначе: весной четыре запуска 02:00–02:45 потеряны, осенью они выполняются дважды — задание идёт по новым часам.
Файл JSON
Имя файла: cronlens-<образец или check>-<первые 8 hex отпечатка>.json, тип application/json, отступ два пробела. Все числа — целые.
| поле | что содержит |
|---|---|
format, tool | "cronlens.vedomost/1", "CronLens" |
generated_locally | всегда true: файл собран в браузере |
exported_at | момент скачивания в UTC; в отпечаток не входит |
sample | слаг образца или null для своего расчёта |
rule | catchup, skip-once или literal |
tzdb | "Intl браузера" — откуда правила поясов |
window | start_local, zone, start_utc, end_utc, span_days |
dial_zone, extra_zones | пояс циферблата и до двух дополнительных поясов |
jobs[] | id (А–Г), name, expression, class (fixed-time или mask), zone, duration_min, normalized, runs, runs_total, truncated, lost, next_after_window |
normalized | списки minute, hour, dom, month, dow и флаги dom_restricted, dow_restricted |
runs[] | n, utc, local, offset_min, extra[] (zone, local, offset_min), marks[] |
marks[] | catchup, repeat-first, repeat-second, repeat-second-skipped, overlap, longer-than-interval |
lost[] | local и reason: gap или gap-large-shift; такие совпадения не нумеруются и не попадают в .ics |
days[] | date, zone, length_min — длина суток пояса циферблата |
transitions[] | zone, utc, from_offset_min, to_offset_min, kind (gap или repeat), local_from, local_to |
overlaps[] | a, b, start_utc, end_utc, minutes |
self_overlaps[] | job, count, first_utc |
warnings[] | kind, job, text — те же замечания, что на странице |
fingerprint | SHA-256, 64 hex |
Фрагмент файла для образца «Ночной архив · осень», правило «С догоном» (многоточием сокращены длинные списки):
{
"format": "cronlens.vedomost/1",
"tool": "CronLens",
"generated_locally": true,
"sample": "archive-autumn",
"rule": "catchup",
"tzdb": "Intl браузера",
"window": { "start_local": "2026-10-20T00:00", "zone": "Europe/Berlin",
"start_utc": "2026-10-19T22:00:00Z", "end_utc": "2026-10-26T23:00:00Z", "span_days": 7 },
"dial_zone": "Europe/Berlin", "extra_zones": [],
"jobs": [ { "id": "А", "name": "Ночной архив", "expression": "30 2 * * *",
"class": "fixed-time", "zone": "Europe/Berlin", "duration_min": 45,
"normalized": { "minute": [30], "hour": [2], "dom": [1, 2, …, 31],
"month": [1, 2, …, 12], "dow": [0, 1, …, 6],
"dom_restricted": false, "dow_restricted": false },
"runs": [ …,
{ "n": 6, "utc": "2026-10-25T00:30:00Z", "local": "2026-10-25T02:30",
"offset_min": 120, "extra": [],
"marks": ["repeat-first", "repeat-second-skipped"] }, … ],
"runs_total": 7, "truncated": false, "lost": [],
"next_after_window": "2026-10-27T01:30:00Z" } ],
"days": [ …, { "date": "2026-10-25", "zone": "Europe/Berlin", "length_min": 1500 }, … ],
"transitions": [ { "zone": "Europe/Berlin", "utc": "2026-10-25T01:00:00Z",
"from_offset_min": 120, "to_offset_min": 60, "kind": "repeat",
"local_from": "2026-10-25T03:00", "local_to": "2026-10-25T02:00" } ],
"overlaps": [], "self_overlaps": [],
"warnings": [ { "kind": "repeat", "job": "А", "text": "А · Ночной архив: 25.10.2026 в Берлине час 02:00–02:59 проходит дважды …" } ],
"exported_at": "…",
"fingerprint": "…"
}
Отпечаток ведомости
Отпечаток — SHA-256 канонической строки: объект JSON без полей fingerprint и exported_at, ключи рекурсивно отсортированы, JSON без пробелов, кодировка UTF-8. Одинаковые входные данные дают одинаковый отпечаток; смена правила, пояса или окна — другой. Момент скачивания и DTSTAMP в .ics в отпечаток не входят.
Проверка скачанного файла на Python (стандартная библиотека; p — путь к файлу):
import json, hashlib
d = json.load(open(p, encoding='utf-8'))
fp = d.pop('fingerprint')
d.pop('exported_at')
s = json.dumps(d, ensure_ascii=False, sort_keys=True, separators=(',', ':'))
print(hashlib.sha256(s.encode()).hexdigest() == fp)
Файл .ics
Имя файла: cronlens-<образец или check>-<ГГГГММДД начала окна>-<N>d.ics, тип text/calendar; charset=utf-8. Строки разделены CRLF и свёрнуты по 75 октетов UTF-8 (продолжение начинается с пробела, кириллица не разрывается). В TEXT экранируются \\, \;, \, и перевод строки \n.
Календарь: VERSION:2.0, PRODID:-//CronLens//Vedomost 1//RU, CALSCALE:GREGORIAN, X-WR-CALNAME:CronLens — <образец или «свой расчёт»>. Одно событие на каждый выполненный запуск, до 1000 событий. Напоминаний (VALARM) нет.
BEGIN:VEVENT
UID:<первые 16 hex отпечатка>-A-6@cronlens.onehots-api.com
DTSTAMP:20260911T120000Z ← момент экспорта, в отпечаток не входит
DTSTART:20261025T003000Z ← начало в UTC
DTEND:20261025T011500Z ← начало + длительность, не меньше 1 мин
SUMMARY:Ночной архив · CronLens
DESCRIPTION:Выражение: 30 2 * * *\nПояс: Europe/Berlin\nМестное время: 2026-10-25 02:30 +02:00\nПравило перевода часов: С догоном\nОтметки: ↺¹ первое прохождение повтора; второе прохождение не выполняется\nОбразец\nРассчитано в браузере
TRANSP:TRANSPARENT ← событие не занимает время в календаре
END:VEVENT
Буква задания в UID записана латиницей: А → A, Б → B, В → V, Г → G. Номер — порядковый номер запуска задания в окне. В самом файле длинная строка DESCRIPTION свёрнута.
Почему не RRULE и TZID
По RFC 5545 (раздел 3.3.5) время с TZID, попавшее в разрыв, календарь сам сдвигает по смещению до перевода, а в повторе берёт первое прохождение. Правило повтора RRULE разворачивалось бы по этим правилам календаря, а не по правилу вашего планировщика. Поэтому каждый запуск — отдельное событие с точным моментом в UTC, как в ведомости.
Формат ссылки
/product.html?sample=archive-autumn#workspace — открыть образец. Передаётся только имя образца; неизвестное имя открывает «Ночной архив · осень» с видимым уведомлением.
/product.html#s=<base64url(JSON)> — свой расчёт. JSON: v, jobs[] (name, expr, zone, duration), window_start, window_zone, span_days, extra_zones, rule, dial_zone и, если расчёт отличается от образца только правилом или поясом циферблата, sample — слаг образца. Фрагмент после # браузер не отправляет сайту. Повреждённый фрагмент открывает образец с уведомлением.
Кнопка «Копировать ссылку на расчёт» собирает ссылку ?sample=, если образец не менялся, и #s= — во всех остальных случаях. По любой из них расчёт открывается с тем же отпечатком.
Сетевого API нет. CronLens не принимает запросов с расписаниями и никуда не отправляет результаты: страница, файлы и отпечаток получаются в вашем браузере.