@@ -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 ;
}
/// <summary>
/// Регулярно. Каждые 1 час, 2 часа, Каждые пол года...
/// </summary>
/// <param name="values"></param>
/// <param name="referenceDate"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
private DateTimeOffset GetNextDateRegularly ( List < EsppScheduleValDto > values , DateTimeOffset referenceDate )
{
var regDict = new Dictionary < string , int >
@@ -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 ;
// Считаем, сколько полных интервалов помещается в этот отрезок времени
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 ) ;
}
}
/// <summary>
/// Еженедельно, каждый понедельник, вторник, среду...
/// </summary>
/// <param name="values"></param>
/// <param name="referenceDate"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
private DateTimeOffset GetNextDateWeekly ( List < EsppScheduleValDto > 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 , c alc Day) ;
// Вычисляем, сколько полных недель (по 7 дней) нужно прибавить
long weeksToSkip = ( long ) Math . Ceiling ( tot alDaysPast / 7 ) ;
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = GetNextDateWeekly ( values , calcDay . AddDays ( 7 ) ) ; //а вот тут что-то новенькое
calcDay = calcDay . AddDays ( weeksToSkip * 7 ) ;
}
while ( true )
{
// Условие выхода: дата в будущем относительно точки старта И текущего времени
if ( calcDay > = referenceDate & & calcDay > = nowInTargetZone )
{
return calcDay ;
}
// Если условия не выполнены, делаем шаг ровно в одну неделю (7 дней)
calcDay = calcDay . AddDays ( 7 ) ;
}
}
/// <summary>
/// Ежемесячно, 1, 2, 3... числа
/// </summary>
/// <param name="values"></param>
/// <param name="referenceDate"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
private DateTimeOffset GetNextDateMonthly ( List < EsppScheduleValDto > 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
) ;
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = GetNextDateMonthly ( values , calcDay . AddMonths ( 1 ) ) ;
// Условие выхода: дата строго в будущем относительно referenceDate и текущего UTC-времени
if ( calcDay > = referenceDate & & calcDay > = nowInTargetZone )
{
return calcDay ;
}
// Если дата правильная, но она в прошлом — переходим к следующему месяцу
monthsToAdd + + ;
}
}
/// <summary>
/// Ежемесячно-2, Первый понедельник, Третий вторник
/// </summary>
/// <param name="values"></param>
/// <param name="referenceDate"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private DateTimeOffset GetNextDateMonthly2 ( List < EsppScheduleValDto > values , DateTimeOffset referenceDate )
{
if ( ! orderDict . TryGetValue ( values [ 0 ] . Value . Value , out int orderNum ) )
@@ -267,23 +387,65 @@ 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 ,
//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];
//}
//// Если дата <= текущей, переходим на следующий месяц
//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 )
{
var currentMonthDate = referenceDate . AddMonths ( monthsToAdd ) ;
// Безопасно формируем первое число для проверяемого месяца
var startDayOfMonth = new DateTimeOffset (
currentMonthDate . Year ,
currentMonthDate . Month ,
1 ,
referenceDate . Hour ,
referenceDate . Minute ,
@@ -291,32 +453,47 @@ namespace PARR.Core.Services.NextRunServices.Subservices
referenceDate . Offset
) ;
var daysList = FindAllDaysInMonth ( dwNum , startDay ) ;
// Ищем все нужные дни недели в этом месяце
var daysList = FindAllDaysInMonth ( dwNum , startDayOfMonth ) ;
DateTimeOffset calcDay ;
if ( orderNum = = 5 ) // Последний
if ( orderNum = = 5 ) // Последний день недели в месяце (например, последняя пятница)
{
calcDay = daysList . Last ( ) ;
}
else
{
// Умная защита: если в текущем месяце нет 5-г о вторника,
// мы не выкидываем ошибку приложения, а мягко идем искать в следующем месяце
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} дней." ) ;
monthsToAdd + + ;
continue ;
}
else
calcDay = daysList [ orderNum - 1 ] ;
}
// Если дата <= текущей, переходим на сле дую щий месяц
if ( calcDay < = DateTimeOffset . UtcNow )
return GetNextDateMonthly2 ( values , startDay . AddMonths ( 1 ) ) ;
// 3. Условие выхода (точно такое же, как во всех преды дущих методах)
if ( calcDay > = referenceDate & & calcDay > = nowInTargetZone )
{
return calcDay ;
}
// Если дата правильная, но она в прошлом — переходим к следующему календарному месяцу
monthsToAdd + + ;
}
}
/// <summary>
/// Ежегодно, Январь 17, Февраль 20
/// </summary>
/// <param name="values"></param>
/// <param name="referenceDate"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
private DateTimeOffset GetNextDateAnnually ( List < EsppScheduleValDto > 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);
////Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
//if (calcDay < DateTimeOffset.UtcNow /*|| calcDay <= referenceDate*/)
// calcDay = GetNextDateAnnually(values, calcDay.AddYears(1));
//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 ) ;
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = GetNextDateAnnually ( values , calcDay . AddYears ( 1 ) ) ;
year ,
monthNum ,
dayNum ,
referenceDate . Hour ,
referenceDate . Minute ,
referenceDate . Second ,
referenceDate . Offset
) ;
// Проверяем условия выхода (абсолютно одинаково для всех методов)
if ( calcDay > = referenceDate & & calcDay > = nowInTargetZone )
{
return calcDay ;
}
// Если дата в прошлом — переходим на следующий год
yearsToAdd + + ;
}
}
/// <summary>
/// Ежегодно-2, Первый понедельник января
/// </summary>
/// <param name="values"></param>
/// <param name="referenceDate"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
private DateTimeOffset GetNextDateAnnually2 ( List < EsppScheduleValDto > values , DateTimeOffset referenceDate )
{
var monthADict = new Dictionary < string , int >
@@ -386,36 +610,92 @@ 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 < DateTimeOffset > 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-последний
//var calcDay = new DateTimeOffset(
// year, monthNum, 1,
// referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset);
//List<DateTimeOffset> daysList = FindAllDaysInMonth(dwNum, 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 ] ;
}
//TODO: вот это повторяется от метода к методу
//Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию
if ( calcDay < DateTimeOffset . UtcNow )
calcDay = GetNextDateAnnually2 ( values , calcDay . AddYears ( 1 ) ) ;
if ( calcDay > = referenceDate & & calcDay > = nowInTargetZone )
{
return calcDay ;
}
// Если вычисленная дата в прошлом — переходим на следующий год
yearsToAdd + + ;
}
}
private List < DateTimeOffset > FindAllDaysInMonth ( int dwNum , DateTimeOffset startDay )
{