From a158b3365c1d755ee7c6948424b8e887f68272bf Mon Sep 17 00:00:00 2001 From: bez-tch Date: Tue, 30 Jun 2026 18:48:58 +0300 Subject: Fix L-N day-of-month crossing a month boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Проблема** TCronExpression::CronNext/CronPrev бросал `library/cpp/cron_expression/cron_expression.cpp: Requested date does not exist` для валидного выражения вроде `59 59 23 * *` (UTC) в определённые даты (например, когда now = 30 июня → следующее срабатывание должно быть 30 июля 23:59:59). **Причина** В DoNextPrev шаг по дню месяца определяет, изменилась ли дата, сравнивая только номер дня (value == updateValue). Однако FindDay может перевести календарь через границу месяца/года, попав при этом на тот же номер дня (30 июня → 30 июля, в обоих случаях день 30, потому что `` — это фиксированное смещение от конца месяца). Цикл тогда считает, что ничего не изменилось, пропускает continue, который пере-резолвит секунды/минуты/часы, и оставляет поля времени, которые FindDay уже сбросил в 00:00:00. Финальная проверка консистентности отклоняет 00:00:00 относительно second=\{59\} и бросает исключение. Это было замаскировано, потому что существующие тесты используют \* для полей времени, где 00:00:00 — валидное совпадение. **Фикс** Сохраняем месяц/год до вызова FindDay и требуем, чтобы они остались неизменными (в дополнение к номеру дня), прежде чем считать день окончательным; иначе — перезапускаем цикл. Затрагивает оба направления. commit_hash:62055eed169ba210e81c89aab860301f3e445f46 --- library/cpp/cron_expression/cron_expression.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'library/cpp/cron_expression/cron_expression.cpp') diff --git a/library/cpp/cron_expression/cron_expression.cpp b/library/cpp/cron_expression/cron_expression.cpp index 55b64ee6d24..31a538a0766 100644 --- a/library/cpp/cron_expression/cron_expression.cpp +++ b/library/cpp/cron_expression/cron_expression.cpp @@ -1060,12 +1060,15 @@ private: } value = GetField(calendar, ECronField::CF_DAY_OF_MONTH); + // FindDay may cross a month/year boundary keeping the same day number (e.g. 'L-1'), check them too + const auto prevMonth = calendar.month(); + const auto prevYear = calendar.year(); updateValue = FindDay(calendar, Target_.GetDayInMonth(), value, GetWeekday(calendar), Target_.GetFlags(), resets, offset); if (updateValue.Empty()) { ythrow yexception() << ErrorDateNotExists; } - if (value == updateValue) { + if (value == updateValue && prevMonth == calendar.month() && prevYear == calendar.year()) { resets.Set(static_cast(ECronField::CF_DAY_OF_MONTH)); } else { continue; -- cgit v1.3