Add schedule examples and cron testing utility

- Add SCHEDULE_EXAMPLES.md with ready-to-use schedule templates
- Add test_cron.py utility for testing cron expressions
- Include examples for daily, weekly, interval-based schedules
- Add timezone conversion examples
- Add testing recommendations

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-05 12:19:50 +02:00
parent b7c1905671
commit 25380e7c1f
2 changed files with 364 additions and 0 deletions

181
SCHEDULE_EXAMPLES.md Normal file
View File

@@ -0,0 +1,181 @@
# Примеры расписаний для быстрого старта
Скопируйте эти примеры и отправьте их через `/manage` → `⏰ Расписания` → `➕ Добавить расписание`
## Ежедневные публикации
### Утренняя публикация (9:00 UTC)
```
Утренний вопрос | 0 9 * * * | 1
```
### Обеденная публикация (12:00 UTC)
```
Обеденный вопрос | 0 12 * * * | 1
```
### Вечерняя публикация (18:00 UTC)
```
Вечерний вопрос | 0 18 * * * | 1
```
### Два раза в день (9:00 и 21:00 UTC)
```
Утро и вечер | 0 9,21 * * * | 1
```
### Три раза в день (утро, обед, вечер)
```
Утро обед вечер | 0 8,13,19 * * * | 1
```
## Рабочая неделя (Пн-Пт)
### Будни утром (9:00 UTC)
```
Рабочие дни утром | 0 9 * * 1-5 | 1
```
### Будни в обед (12:30 UTC)
```
Рабочий обед | 30 12 * * 1-5 | 1
```
### Будни вечером (17:00 UTC)
```
После работы | 0 17 * * 1-5 | 1
```
## Выходные дни
### Выходные в полдень (12:00 UTC)
```
Выходные | 0 12 * * 6,0 | 1
```
### Только суббота утром (10:00 UTC)
```
Суббота | 0 10 * * 6 | 1
```
### Только воскресенье вечером (19:00 UTC)
```
Воскресенье вечер | 0 19 * * 0 | 1
```
## Регулярные интервалы
### Каждые 2 часа
```
Каждые 2 часа | 0 */2 * * * | 1
```
### Каждые 3 часа
```
Каждые 3 часа | 0 */3 * * * | 1
```
### Каждые 4 часа
```
Каждые 4 часа | 0 */4 * * * | 1
```
### Каждые 6 часов (4 раза в день)
```
4 раза в день | 0 */6 * * * | 1
```
### Каждые 30 минут
```
Каждые полчаса | */30 * * * * | 1
```
### Каждые 15 минут (для активных каналов)
```
Каждые 15 минут | */15 * * * * | 1
```
## Специальные расписания
### Рабочие часы (9:00-17:00 каждый час по будням)
```
Рабочие часы | 0 9-17 * * 1-5 | 1
```
### Первый день каждого месяца (10:00 UTC)
```
Первое число месяца | 0 10 1 * * | 1
```
### Каждый понедельник утром (8:00 UTC)
```
Понедельник утром | 0 8 * * 1 | 1
```
### Каждую пятницу вечером (20:00 UTC)
```
Пятница вечером | 0 20 * * 5 | 1
```
## Адаптация к местному времени
Если ваш часовой пояс отличается от UTC, вычтите разницу из желаемого времени:
### Москва (UTC+3)
Для публикации в 12:00 по Москве используйте 9:00 UTC:
```
Обед по Москве | 0 9 * * * | 1
```
### Киев/Минск (UTC+2)
Для публикации в 14:00 используйте 12:00 UTC:
```
14:00 Киев | 0 12 * * * | 1
```
### Лондон (UTC+0/+1 летом)
Для публикации в 10:00 используйте 10:00 UTC (зима) или 9:00 UTC (лето):
```
Утро Лондон | 0 10 * * * | 1
```
## Тестовые расписания
### Следующая минута (для теста)
Замените ХХ на текущий час+1 минута:
```
Тест | 18 10 * * * | 1
```
### Каждую минуту (только для краткосрочных тестов!)
```
Тест каждую минуту | * * * * * | 1
```
⚠️ **Не забудьте удалить или деактивировать после теста!**
## Деактивированные расписания
Чтобы временно отключить расписание, установите последний параметр в `0`:
```
Отключенное утро | 0 9 * * * | 0
```
Позже можно активировать, изменив `0` на `1`.
## Полезные советы
1. **UTC vs местное время**: Бот работает в UTC. Используйте конвертер времени для расчета.
2. **Тестирование**: Создайте расписание на ближайшую минуту для проверки.
3. **Имена**: Используйте понятные имена для расписаний.
4. **Активность**: Используйте `0` вместо удаления для временного отключения.
5. **Проверка**: После создания проверьте список расписаний в меню.
## Инструменты
Проверить cron выражение перед добавлением:
```bash
python test_cron.py "0 9 * * 1-5" 168
```
Это покажет, когда сработает ваше расписание в ближайшие 168 часов (7 дней).

183
test_cron.py Normal file
View File

@@ -0,0 +1,183 @@
#!/usr/bin/env python3
"""
Утилита для тестирования cron выражений.
Использование:
python test_cron.py "0 9 * * 1-5"
python test_cron.py "*/15 * * * *"
"""
import sys
from datetime import datetime, timedelta, timezone
# Прямой импорт только нужных классов, избегая полный импорт bot
sys.path.insert(0, ".")
# Копируем необходимые классы прямо сюда для независимости
from dataclasses import dataclass
@dataclass(frozen=True)
class _CronField:
allowed: set[int]
def matches(self, value: int) -> bool:
return value in self.allowed
class CronExpression:
def __init__(self, expression: str) -> None:
parts = expression.strip().split()
if len(parts) != 5:
raise ValueError("Cron must have 5 fields: minute hour day month weekday.")
self.expression = expression.strip()
self._minute = self._parse_field(parts[0], 0, 59)
self._hour = self._parse_field(parts[1], 0, 23)
self._day = self._parse_field(parts[2], 1, 31)
self._month = self._parse_field(parts[3], 1, 12)
self._weekday = self._parse_field(parts[4], 0, 7)
def matches(self, dt: datetime) -> bool:
weekday = (dt.weekday() + 1) % 7
return (
self._minute.matches(dt.minute)
and self._hour.matches(dt.hour)
and self._day.matches(dt.day)
and self._month.matches(dt.month)
and (weekday in self._weekday.allowed or (7 in self._weekday.allowed and weekday == 0))
)
def _parse_field(self, token: str, minimum: int, maximum: int) -> _CronField:
allowed: set[int] = set()
for part in token.split(","):
part = part.strip()
if not part:
raise ValueError("Invalid empty cron token.")
step = 1
base = part
if "/" in part:
base, step_part = part.split("/", 1)
if not step_part.isdigit() or int(step_part) < 1:
raise ValueError(f"Invalid step value in cron token: {part}")
step = int(step_part)
if base == "*":
start, end = minimum, maximum
elif "-" in base:
start_str, end_str = base.split("-", 1)
start = int(start_str)
end = int(end_str)
else:
start = int(base)
end = int(base)
if start < minimum or end > maximum or start > end:
raise ValueError(f"Cron value out of range: {part}")
for value in range(start, end + 1, step):
allowed.add(value)
if not allowed:
raise ValueError("Cron field has no values.")
return _CronField(allowed=allowed)
def test_cron(expression: str, hours: int = 24):
"""
Тестирует cron выражение на следующие N часов.
Args:
expression: Cron выражение для теста
hours: Количество часов для проверки (по умолчанию 24)
"""
print(f"Тестирование cron выражения: '{expression}'")
print(f"Проверка следующих {hours} часов...")
print("-" * 60)
try:
cron = CronExpression(expression)
except ValueError as e:
print(f"❌ Ошибка в cron выражении: {e}")
return
now = datetime.now(timezone.utc).replace(second=0, microsecond=0)
matches = []
# Проверяем каждую минуту на протяжении указанного времени
total_minutes = hours * 60
for i in range(total_minutes):
check_time = now + timedelta(minutes=i)
if cron.matches(check_time):
matches.append(check_time)
if not matches:
print(f"⚠️ Нет совпадений в ближайшие {hours} часов")
else:
print(f"✅ Найдено {len(matches)} совпадений:\n")
for i, match_time in enumerate(matches[:20], 1): # Показываем первые 20
# Форматируем в читаемый вид
day_name = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"][match_time.weekday()]
formatted = match_time.strftime("%Y-%m-%d %H:%M UTC")
print(f" {i:2d}. {formatted} ({day_name})")
if len(matches) > 20:
print(f"\n ... и ещё {len(matches) - 20} совпадений")
print("-" * 60)
# Статистика
if matches:
first = matches[0]
time_until = (first - now).total_seconds() / 60
print(f"\nСледующее срабатывание через {int(time_until)} минут:")
day_name = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"][first.weekday()]
print(f" {first.strftime('%Y-%m-%d %H:%M UTC')} ({day_name})")
if len(matches) > 1:
# Вычисляем среднюю частоту
total_time = (matches[-1] - matches[0]).total_seconds() / 60
avg_interval = total_time / (len(matches) - 1) if len(matches) > 1 else 0
print(f"\nСредний интервал: {avg_interval:.1f} минут")
def show_examples():
"""Показывает примеры использования."""
print("=" * 60)
print("Примеры cron выражений:")
print("=" * 60)
examples = [
("0 9 * * *", "Каждый день в 9:00 UTC"),
("0 9 * * 1-5", "Каждый будний день в 9:00 UTC"),
("*/15 * * * *", "Каждые 15 минут"),
("0 */4 * * *", "Каждые 4 часа"),
("0 8,12,18 * * *", "В 8:00, 12:00 и 18:00"),
("0 12 * * 6,0", "Суббота и воскресенье в 12:00"),
("30 14 1 * *", "1-го числа каждого месяца в 14:30"),
]
for cron, description in examples:
print(f"\n{cron:20s} - {description}")
print("\n" + "=" * 60)
def main():
if len(sys.argv) < 2:
print("Использование: python test_cron.py \"<cron выражение>\" [часов]")
print("Пример: python test_cron.py \"0 9 * * 1-5\" 48")
print()
show_examples()
sys.exit(1)
expression = sys.argv[1]
hours = int(sys.argv[2]) if len(sys.argv) > 2 else 24
print()
test_cron(expression, hours)
print()
if __name__ == "__main__":
main()