diff --git a/PARR.Core/Services/NextRunServices/Subservices/EsppScheduleTransformService.cs b/PARR.Core/Services/NextRunServices/Subservices/EsppScheduleTransformService.cs
index 732ce214..aaa52040 100644
--- a/PARR.Core/Services/NextRunServices/Subservices/EsppScheduleTransformService.cs
+++ b/PARR.Core/Services/NextRunServices/Subservices/EsppScheduleTransformService.cs
@@ -70,23 +70,12 @@ namespace PARR.Core.Services.NextRunServices.Subservices
var nextRun = referenceDate;
- //var finalOffset = new TimeSpan(3, 0, 0) + (offset ?? TimeSpan.Zero);
var finalOffset = offset ?? TimeSpan.Zero;
var offsetReferenceDate = referenceDate.ToOffset(finalOffset);
logger.LogDebug("Дата после добавления offset - {offsetReferenceDate}", offsetReferenceDate);
- ////---------
- //// Для рассчета по МСК времени, потому что в ЮТС может быть еще ВСК, а по МСК это уже ПНД
- //var offsetReferenceDate = referenceDate.ToOffset(new TimeSpan(3, 0, 0));
- ////---------
-
- //// может быть offset ,это работает когда смещение для УЗ ЕСПП и применение ЗО РГ
- //if (offset.HasValue)
- // offsetReferenceDate = offsetReferenceDate.Off(offsetReferenceDate.Offset + offset);
- // //offsetReferenceDate = offsetReferenceDate.Add(offset.Value);
-
switch (esppSchedule.TypeSchedule.Id)
{
@@ -108,6 +97,9 @@ namespace PARR.Core.Services.NextRunServices.Subservices
case (int)EsppSchTypeScheduleEnum.Annually2:
nextRun = GetNextDateAnnually2(esppSchedule.Values, offsetReferenceDate);
break;
+ default:
+ logger.LogError("Неподдерживаемый тип расписания ЕСПП: {TypeId}", esppSchedule.TypeSchedule.Id);
+ throw new NotSupportedException($"Тип расписания {esppSchedule.TypeSchedule.Id} не поддерживается.");
}
logger.LogDebug("Рассчитанная дата, с учетом offset: {nextRun}", nextRun);
@@ -139,6 +131,14 @@ namespace PARR.Core.Services.NextRunServices.Subservices
return esppSchedule;
}
+
+ ///
+ /// Регулярно. Каждые 1 час, 2 часа, Каждые пол года...
+ ///
+ ///
+ ///
+ ///
+ ///
private DateTimeOffset GetNextDateRegularly(List values, DateTimeOffset referenceDate)
{
var regDict = new Dictionary
@@ -159,27 +159,63 @@ namespace PARR.Core.Services.NextRunServices.Subservices
{"Каждые полгода", 4380},
{"Каждые 1,5 года", 13140},
{"Каждые 3 года", 26280},
-
};
-
- if (!regDict.TryGetValue(values[0].Value.Value, out int regNum))
+ var key = values[0].Value.Value;
+ if (!regDict.TryGetValue(key, out int regMinutes))
{
- logger.LogError("Неизвестное значение: {Value}", values[0].Value.Value);
- throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
+ logger.LogError("Неизвестное значение: {Value}", key);
+ throw new ArgumentException($"Неизвестное значение: {key}");
}
+ #region Старая логика
+
+ //var calcDay = referenceDate;
+
+ ////TODO: вот это повторяется от метода к методу
+ ////Если итоговая дата указывает на прошлое, то повторяем расчёт и уходим в рекурсию
+ //if (calcDay < DateTimeOffset.UtcNow /* || calcDay <= referenceDate*/)
+ // calcDay = GetNextDateRegularly(values, calcDay.AddHours(regMinutes));
+
+ //return calcDay;
+
+ #endregion
+
+ var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+
var calcDay = referenceDate;
- //TODO: вот это повторяется от метода к методу
- //Если итоговая дата указывает на прошлое, то повторяем расчёт и уходим в рекурсию
- if (calcDay < DateTimeOffset.UtcNow)
- calcDay = GetNextDateRegularly(values, calcDay.AddHours(regNum));
+ // Если referenceDate далеко в прошлом, не шагаем по минутам в цикле.
+ // Сразу вычисляем, сколько целых интервалов нужно прибавить, чтобы догнать текущее время.
+ if (calcDay < nowInTargetZone)
+ {
+ double totalMinutesPast = (nowInTargetZone - calcDay).TotalMinutes;
- return calcDay;
+ // Считаем, сколько полных интервалов помещается в этот отрезок времени
+ long intervalsToSkip = (long)Math.Ceiling(totalMinutesPast / regMinutes);
+
+ calcDay = calcDay.AddMinutes(intervalsToSkip * regMinutes);
+ }
+
+ // Проверяем условия и делаем микро-шаги, если необходимо
+ while (true)
+ {
+ if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
+ {
+ return calcDay;
+ }
+ calcDay = calcDay.AddMinutes(regMinutes);
+ }
}
+ ///
+ /// Еженедельно, каждый понедельник, вторник, среду...
+ ///
+ ///
+ ///
+ ///
+ ///
private DateTimeOffset GetNextDateWeekly(List values, DateTimeOffset referenceDate)
{
if (!dwDict.TryGetValue(values[0].Value.Value, out int dwNum))
@@ -188,71 +224,155 @@ namespace PARR.Core.Services.NextRunServices.Subservices
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
+ #region Старая логика
+ //var calcDay = referenceDate;
+ //if (calcDay < DateTimeOffset.UtcNow)
+ //{
+ // var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+ // calcDay = new DateTimeOffset(
+ // nowInTargetTz.Year,
+ // nowInTargetTz.Month,
+ // nowInTargetTz.Day,
+ // referenceDate.Hour,
+ // referenceDate.Minute,
+ // referenceDate.Second,
+ // referenceDate.Offset
+ // );
+ //}
+
+
+ //if (((int)calcDay.DayOfWeek) != dwNum)
+ // calcDay = OffsetToDayOfWeek(dwNum, calcDay);
+
+ ////Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
+ //if (calcDay < DateTimeOffset.UtcNow || calcDay <= referenceDate)
+ // calcDay = GetNextDateWeekly(values, calcDay.AddDays(7));//а вот тут что-то новенькое
+
+ //return calcDay;
+ #endregion
+
+ var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+
var calcDay = referenceDate;
- if (calcDay < DateTimeOffset.UtcNow)
+
+ // Сдвигаем дату вперед до нужного дня недели
+ if ((int)calcDay.DayOfWeek != dwNum)
{
- var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
- calcDay = new DateTimeOffset(
- nowInTargetTz.Year,
- //DateTimeOffset.UtcNow.Year,
- nowInTargetTz.Month,
- //DateTimeOffset.UtcNow.Month,
- nowInTargetTz.Day,
- //DateTimeOffset.UtcNow.Day,
- referenceDate.Hour,
- referenceDate.Minute,
- referenceDate.Second,
- referenceDate.Offset
- );
+ calcDay = OffsetToDayOfWeek(dwNum, calcDay);
}
+ // Если получившаяся дата все еще глубоко в прошлом,
+ // не шагаем неделями в цикле, а мгновенно прыгаем ближе к текущему времени математикой.
+ if (calcDay < nowInTargetZone)
+ {
+ double totalDaysPast = (nowInTargetZone - calcDay).TotalDays;
- if (((int)calcDay.DayOfWeek) != dwNum)
- calcDay = OffsetToDayOfWeek(dwNum, calcDay);
+ // Вычисляем, сколько полных недель (по 7 дней) нужно прибавить
+ long weeksToSkip = (long)Math.Ceiling(totalDaysPast / 7);
- //TODO: вот это повторяется от метода к методу
- //Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
- if (calcDay < DateTimeOffset.UtcNow)
- calcDay = GetNextDateWeekly(values, calcDay.AddDays(7));//а вот тут что-то новенькое
+ calcDay = calcDay.AddDays(weeksToSkip * 7);
+ }
- return calcDay;
+ while (true)
+ {
+ // Условие выхода: дата в будущем относительно точки старта И текущего времени
+ if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
+ {
+ return calcDay;
+ }
+
+ // Если условия не выполнены, делаем шаг ровно в одну неделю (7 дней)
+ calcDay = calcDay.AddDays(7);
+ }
}
+ ///
+ /// Ежемесячно, 1, 2, 3... числа
+ ///
+ ///
+ ///
+ ///
+ ///
private DateTimeOffset GetNextDateMonthly(List values, DateTimeOffset referenceDate)
{
- if (!int.TryParse(values[0].Value.Value, out int dayNum))
+ if (!int.TryParse(values[0].Value.Value, out int targetDay))
{
logger.LogError("Неизвестное значение дня месяца: {Value}", values[0].Value.Value);
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
- var calcDay = new DateTimeOffset(referenceDate.Year, referenceDate.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- if (referenceDate < DateTimeOffset.UtcNow)
+ #region старая логика, не учтено попадание например 31 числа на февраль. Нет сравнения что расчитанная дата новее или равна refDate
+ //var calcDay = new DateTimeOffset(referenceDate.Year, referenceDate.Month, targetDay, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
+ //if (referenceDate < DateTimeOffset.UtcNow)
+ //{
+ // var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+ // calcDay = new DateTimeOffset(
+ // nowInTargetTz.Year,
+ // nowInTargetTz.Month,
+ // targetDay,
+ // referenceDate.Hour,
+ // referenceDate.Minute,
+ // referenceDate.Second,
+ // referenceDate.Offset
+ // );
+ //}
+
+ ////Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
+ //if (calcDay < DateTimeOffset.UtcNow /*|| calcDay <= referenceDate*/)
+ // calcDay = GetNextDateMonthly(values, calcDay.AddMonths(1));
+
+ //return calcDay;
+ #endregion
+
+ // Начинаем проверку с текущего месяца и года переданной referenceDate
+ var baseDate = referenceDate;
+ int monthsToAdd = 0;
+
+ var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+
+ while (true)
{
- var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
- calcDay = new DateTimeOffset(
- nowInTargetTz.Year,
- nowInTargetTz.Month,
- dayNum,
- referenceDate.Hour,
- referenceDate.Minute,
- referenceDate.Second,
- referenceDate.Offset
- );
- //calcDay = new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
+ // Каждую итерацию мы отталкиваемся от исходной точки и прибавляем +1, +2, +3 месяца
+ var nextMonthDate = baseDate.AddMonths(monthsToAdd);
+ int year = nextMonthDate.Year;
+ int month = nextMonthDate.Month;
+
+ // Если в этом конкретном месяце НЕТ нужного нам дня (например, 31 числа в апреле или 30 в феврале)
+ if (targetDay > DateTime.DaysInMonth(year, month))
+ {
+ // Пропускаем этот месяц и идем на следующий круг цикла
+ monthsToAdd++;
+ continue;
+ }
+
+ // Если день существует, безопасно собираем дату
+ var calcDay = new DateTimeOffset(
+ year, month, targetDay,
+ referenceDate.Hour, referenceDate.Minute, referenceDate.Second,
+ referenceDate.Offset
+ );
+
+ // Условие выхода: дата строго в будущем относительно referenceDate и текущего UTC-времени
+ if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
+ {
+ return calcDay;
+ }
+
+ // Если дата правильная, но она в прошлом — переходим к следующему месяцу
+ monthsToAdd++;
}
-
-
- //TODO: вот это повторяется от метода к методу
- //Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
- if (calcDay < DateTimeOffset.UtcNow)
- calcDay = GetNextDateMonthly(values, calcDay.AddMonths(1));
-
- return calcDay;
}
+ ///
+ /// Ежемесячно-2, Первый понедельник, Третий вторник
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
private DateTimeOffset GetNextDateMonthly2(List values, DateTimeOffset referenceDate)
{
if (!orderDict.TryGetValue(values[0].Value.Value, out int orderNum))
@@ -267,56 +387,113 @@ namespace PARR.Core.Services.NextRunServices.Subservices
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
}
+ #region Старая логика
//var startDay = referenceDate < DateTimeOffset.UtcNow
- // ? new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset)
- // : new DateTimeOffset(referenceDate.Year, referenceDate.Month, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
+ // ? new DateTimeOffset(
+ // DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).Year,
+ // DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).Month,
+ // 1,
+ // referenceDate.Hour,
+ // referenceDate.Minute,
+ // referenceDate.Second,
+ // referenceDate.Offset
+ // )
+ // : new DateTimeOffset(
+ // referenceDate.Year,
+ // referenceDate.Month,
+ // 1,
+ // referenceDate.Hour,
+ // referenceDate.Minute,
+ // referenceDate.Second,
+ // referenceDate.Offset
+ // );
- var startDay = referenceDate < DateTimeOffset.UtcNow
- ? new DateTimeOffset(
- DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).Year,
- DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset).Month,
- 1,
- referenceDate.Hour,
- referenceDate.Minute,
- referenceDate.Second,
- referenceDate.Offset
- )
- : new DateTimeOffset(
- referenceDate.Year,
- referenceDate.Month,
- 1,
- referenceDate.Hour,
- referenceDate.Minute,
- referenceDate.Second,
- referenceDate.Offset
- );
+ //var daysList = FindAllDaysInMonth(dwNum, startDay);
- var daysList = FindAllDaysInMonth(dwNum, startDay);
+ //DateTimeOffset calcDay;
+ //if (orderNum == 5) // Последний
+ //{
+ // calcDay = daysList.Last();
+ //}
+ //else
+ //{
+ // if (orderNum > daysList.Count)
+ // {
+ // logger.LogError("Запрошенный порядковый день недели ({OrderNum}) не существует в месяце {Month}/{Year}. Найдено только {Count} дней.", orderNum, startDay.Month, startDay.Year, daysList.Count);
+ // throw new InvalidOperationException($"Запрошенный порядковый день недели ({orderNum}) не существует в месяце {startDay.Month}/{startDay.Year}. Найдено только {daysList.Count} дней.");
+ // }
+ // else
+ // calcDay = daysList[orderNum - 1];
+ //}
- DateTimeOffset calcDay;
- if (orderNum == 5) // Последний
+ //// Если дата <= текущей, переходим на следующий месяц
+ //if (calcDay <= DateTimeOffset.UtcNow /*|| calcDay <= referenceDate*/)
+ // return GetNextDateMonthly2(values, startDay.AddMonths(1));
+
+ //return calcDay;
+ #endregion
+
+ var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+
+ // Начинаем расчет с текущего месяца referenceDate
+ int monthsToAdd = 0;
+
+ while (true)
{
- calcDay = daysList.Last();
- }
- else
- {
- if (orderNum > daysList.Count)
+ var currentMonthDate = referenceDate.AddMonths(monthsToAdd);
+
+ // Безопасно формируем первое число для проверяемого месяца
+ var startDayOfMonth = new DateTimeOffset(
+ currentMonthDate.Year,
+ currentMonthDate.Month,
+ 1,
+ referenceDate.Hour,
+ referenceDate.Minute,
+ referenceDate.Second,
+ referenceDate.Offset
+ );
+
+ // Ищем все нужные дни недели в этом месяце
+ var daysList = FindAllDaysInMonth(dwNum, startDayOfMonth);
+
+ DateTimeOffset calcDay;
+
+ if (orderNum == 5) // Последний день недели в месяце (например, последняя пятница)
{
- logger.LogError("Запрошенный порядковый день недели ({OrderNum}) не существует в месяце {Month}/{Year}. Найдено только {Count} дней.", orderNum, startDay.Month, startDay.Year, daysList.Count);
- throw new InvalidOperationException($"Запрошенный порядковый день недели ({orderNum}) не существует в месяце {startDay.Month}/{startDay.Year}. Найдено только {daysList.Count} дней.");
+ calcDay = daysList.Last();
}
else
+ {
+ // Умная защита: если в текущем месяце нет 5-го вторника,
+ // мы не выкидываем ошибку приложения, а мягко идем искать в следующем месяце
+ if (orderNum > daysList.Count)
+ {
+ monthsToAdd++;
+ continue;
+ }
+
calcDay = daysList[orderNum - 1];
+ }
+
+ // 3. Условие выхода (точно такое же, как во всех предыдущих методах)
+ if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
+ {
+ return calcDay;
+ }
+
+ // Если дата правильная, но она в прошлом — переходим к следующему календарному месяцу
+ monthsToAdd++;
}
-
- // Если дата <= текущей, переходим на следующий месяц
- if (calcDay <= DateTimeOffset.UtcNow)
- return GetNextDateMonthly2(values, startDay.AddMonths(1));
-
- return calcDay;
}
+ ///
+ /// Ежегодно, Январь 17, Февраль 20
+ ///
+ ///
+ ///
+ ///
+ ///
private DateTimeOffset GetNextDateAnnually(List values, DateTimeOffset referenceDate)
{
@@ -332,26 +509,73 @@ namespace PARR.Core.Services.NextRunServices.Subservices
throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}");
}
- //var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
- //сравниваем года в одном часовом поясе
- var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
- var year = (referenceDate.Year < nowInTargetTz.Year)
- ? nowInTargetTz.Year
- : referenceDate.Year;
+ #region Старая логика
+ ////сравниваем года в одном часовом поясе
+ //var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+ //var year = (referenceDate.Year < nowInTargetTz.Year)
+ // ? nowInTargetTz.Year
+ // : referenceDate.Year;
- var calcDay = new DateTimeOffset(
- year, monthNum, dayNum,
- referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
+ //var calcDay = new DateTimeOffset(
+ // year, monthNum, dayNum,
+ // referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- //TODO: вот это повторяется от метода к методу
- //Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
- if (calcDay < DateTimeOffset.UtcNow)
- calcDay = GetNextDateAnnually(values, calcDay.AddYears(1));
+ ////Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
+ //if (calcDay < DateTimeOffset.UtcNow /*|| calcDay <= referenceDate*/)
+ // calcDay = GetNextDateAnnually(values, calcDay.AddYears(1));
- return calcDay;
+ //return calcDay;
+ #endregion
+
+ var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+
+ // Начинаем расчет с года переданной referenceDate
+ int yearsToAdd = 0;
+
+ while (true)
+ {
+ // Вычисляем текущий проверяемый год относительно исходной referenceDate
+ var currentYearDate = referenceDate.AddYears(yearsToAdd);
+ int year = currentYearDate.Year;
+
+ // ЗАЩИТА: Если в этом году нет такого дня (например, 29 февраля в невисокосном году)
+ if (dayNum > DateTime.DaysInMonth(year, monthNum))
+ {
+ // Просто пропускаем этот год и переходим к следующему
+ yearsToAdd++;
+ continue;
+ }
+
+ // Если день существует, безопасно собираем дату
+ var calcDay = new DateTimeOffset(
+ year,
+ monthNum,
+ dayNum,
+ referenceDate.Hour,
+ referenceDate.Minute,
+ referenceDate.Second,
+ referenceDate.Offset
+ );
+
+ // Проверяем условия выхода (абсолютно одинаково для всех методов)
+ if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
+ {
+ return calcDay;
+ }
+
+ // Если дата в прошлом — переходим на следующий год
+ yearsToAdd++;
+ }
}
+ ///
+ /// Ежегодно-2, Первый понедельник января
+ ///
+ ///
+ ///
+ ///
+ ///
private DateTimeOffset GetNextDateAnnually2(List values, DateTimeOffset referenceDate)
{
var monthADict = new Dictionary
@@ -386,34 +610,90 @@ namespace PARR.Core.Services.NextRunServices.Subservices
throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}");
}
- //var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year;
- // сравниваем года в одном часовом поясе
- var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
- var year = (referenceDate.Year < nowInTargetTz.Year)
- ? nowInTargetTz.Year
- : referenceDate.Year;
- //if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month)
- if (year == nowInTargetTz.Year && referenceDate.Month < nowInTargetTz.Month)
- year += 1;
+ #region Старая логика
- var calcDay = new DateTimeOffset(
- year, monthNum, 1,
- referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
+ //// сравниваем года в одном часовом поясе
+ //var nowInTargetTz = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+ //var year = (referenceDate.Year < nowInTargetTz.Year)
+ // ? nowInTargetTz.Year
+ // : referenceDate.Year;
- List daysList = FindAllDaysInMonth(dwNum, calcDay);
+ ////if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month)
+ //if (year == nowInTargetTz.Year && referenceDate.Month < nowInTargetTz.Month)
+ // year += 1;
- if (orderNum == 5)//5-последний
- calcDay = daysList.Last();
- else
- calcDay = daysList[orderNum - 1];
+ //var calcDay = new DateTimeOffset(
+ // year, monthNum, 1,
+ // referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
- //TODO: вот это повторяется от метода к методу
- //Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
- if (calcDay < DateTimeOffset.UtcNow)
- calcDay = GetNextDateAnnually2(values, calcDay.AddYears(1));
+ //List daysList = FindAllDaysInMonth(dwNum, calcDay);
- return calcDay;
+ //if (orderNum == 5)//5-последний
+ // calcDay = daysList.Last();
+ //else
+ // calcDay = daysList[orderNum - 1];
+
+ ////Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
+ //if (calcDay < DateTimeOffset.UtcNow /*|| calcDay <= referenceDate*/)
+ // calcDay = GetNextDateAnnually2(values, calcDay.AddYears(1));
+
+ //return calcDay;
+
+ #endregion
+
+ var nowInTargetZone = DateTimeOffset.UtcNow.ToOffset(referenceDate.Offset);
+
+ // Начинаем расчет с года переданной referenceDate
+ int yearsToAdd = 0;
+
+ while (true)
+ {
+ // Сдвигаем проверяемый год относительно исходной даты
+ var currentYearDate = referenceDate.AddYears(yearsToAdd);
+ int year = currentYearDate.Year;
+
+ // Формируем первое число целевого месяца для проверяемого года
+ var startDayOfMonth = new DateTimeOffset(
+ year,
+ monthNum,
+ 1,
+ referenceDate.Hour,
+ referenceDate.Minute,
+ referenceDate.Second,
+ referenceDate.Offset
+ );
+
+ // Ищем все нужные дни недели в этом месяце
+ var daysList = FindAllDaysInMonth(dwNum, startDayOfMonth);
+
+ DateTimeOffset calcDay;
+
+ if (orderNum == 5) // 5 означает "Последний день недели в месяце"
+ {
+ calcDay = daysList.Last();
+ }
+ else
+ {
+ // Умная защита: если в этом году в этом месяце НЕТ 5-го выбранного дня недели
+ if (orderNum > daysList.Count)
+ {
+ // Мы не падаем с ошибкой, а просто переходим к проверке следующего года
+ yearsToAdd++;
+ continue;
+ }
+
+ calcDay = daysList[orderNum - 1];
+ }
+
+ if (calcDay >= referenceDate && calcDay >= nowInTargetZone)
+ {
+ return calcDay;
+ }
+
+ // Если вычисленная дата в прошлом — переходим на следующий год
+ yearsToAdd++;
+ }
}