timesense
Детерминированный парсер естественного языка для дат, событий и повторов — без LLM и без ML. «каждый второй вторник в 18:30 ретро» → структурированное событие, RFC 5545 rrule или готовый .ics. Русский и английский, zero-dependency, меньше 1 мс на фразу.
Что это
Библиотека, которая превращает фразу на естественном языке в структурированное событие. Не только дату и время: тип события, заголовок, место, продолжительность, правило повтора и дедлайн.
«каждый второй вторник в 18:30 ретро в переговорке»
→ событие · заголовок «ретро» · место «в переговорке»
→ 18:30 · FREQ=MONTHLY;BYDAY=2TU
Результат выгружается в JSON, в rrule по RFC 5545 или в готовый .ics, который открывается в Google Calendar, Apple Calendar и Outlook.
Русский и английский, определение языка автоматическое. Чистый Python 3.9–3.13, ноль обязательных зависимостей.
Почему появился
Задача возникает каждый раз, когда есть голосовой ввод или бот: человек говорит «перенеси на следующий вторник к шести», а системе нужна дата, время и правило повтора.
Готовые парсеры дат — dateparser, parsedatetime — извлекают datetime и на этом останавливаются. Они не скажут, что это повторяющееся событие, не отдадут rrule и не соберут .ics. Всё остальное приходится писать самому.
Второй путь — отправить фразу в LLM. И вот здесь начинается настоящая проблема.
Почему не LLM
Календарной автоматизации нужна детерминированность: одна и та же фраза обязана давать одно и то же событие каждый раз. Не «почти всегда», а всегда.
Модель этого не гарантирует. На той же фразе она может вернуть другую дату после обновления версии, при другой температуре или просто в другой раз. Отладить такое поведение нельзя: нет места, куда можно посмотреть и увидеть, почему получился именно этот ответ.
Внутри timesense нет машинного обучения — только словари и регулярные выражения. Отсюда всё остальное:
| Показатель | timesense |
|---|---|
| Обязательные зависимости | 0 — только стандартная библиотека |
| Модели и GPU | не нужны |
| Холодный старт | ~0,2 с |
| Память | несколько МБ |
| Задержка | меньше 1 мс на фразу, 1 000+ фраз в секунду на одном скромном ядре |
| Одинаковый вход → одинаковый выход | да, всегда |
Практическое следствие: парсер можно вызывать на каждой реплике живой транскрипции, прямо внутри конвейера, и он работает на самой дешёвой VPS, на плате уровня Raspberry Pi или внутри serverless-функции.
Что внутри
Три типа результата, выбираются автоматически:
- точка во времени →
ReminderResult - интервал с началом и концом →
CalendarResult - период, дедлайн, открытое начало или размытый срок →
TaskResult
Что разбирается. Время (в 17:00, без пяти десять, half past ten), относительные сроки (через полтора часа, через 2,5 часа с десятичной запятой, in forty five minutes), даты (первого сентября, 15 числа, ISO), диапазоны (с девяти до пяти, через полночь), повторы (по будням, каждые 2 недели, каждое 15-е число), дедлайны (до конца марта, не позднее 20-го), продолжительность и место.
Рабочий календарь. Можно загрузить праздники и перенесённые рабочие субботы — тогда «последний рабочий день месяца» считается правильно, а не как «последний Пн–Пт».
Арифметика рабочих дней. за два рабочих дня до конца месяца разрешается в конкретную дату с учётом загруженного календаря.
Осознанные ограничения
Часовые пояса не поддерживаются: все datetime — наивные, без tzinfo. Фраза с зоной (в 10 мск, GMT+3) намеренно возвращает None, а не догадку.
Это выбор, а не недоделка. Молча вернуть время без зоны, когда зона в тексте была, — худший из возможных вариантов: ошибка обнаружится в календаре пользователя, а не в коде.
Что получилось
Стабильная версия 1.0.0 с зафиксированным публичным API, MIT, пакет в PyPI, тесты в репозитории и отдельные наборы для русской и английской грамматики.