Files
parr_api/PARR.DAL/NextRunServices/Subservices/TemplateDistributor.cs

714 lines
34 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.DAL.NextRunServices.Models;
using PARR.DAL.Services.Interfaces;
namespace PARR.DAL.NextRunServices.Subservices
{
internal class TemplateDistributor : ITemplateDistributor
{
private readonly ILogger<TemplateDistributor> logger;
private readonly IWeekendDayService weekendDayService;
/// <summary>
/// Смещение nextRun на несколько лет вперед, если не смог его рассчитать
/// </summary>
private const int offsetYearsIfErrorCalcNextRun = 5;
public TemplateDistributor(
ILogger<TemplateDistributor> logger,
IWeekendDayService weekendDayService
)
{
this.logger = logger;
this.weekendDayService = weekendDayService;
}
public async Task<TemplateNextRunResultDto> GetValidNextRunForTemplateAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, TemplateNextRunDto targetTemplate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends, bool isNew)
{
//todo: allTemplates - пока не используется. Будет использоваться когда каждый раз будем строить план
// Пока работает так:
// Получаем список рабочих дней, если работа попадает в эти дни, то считаем дату валидной
// Если нет, то перемещаем на последний день периода распределения или первый в зависимости от параметра isNew
var _startDate = GetStartPeriodDate(startDate, referenceDate);
var _endDate = GetEndPeriodDate(_startDate, duration);
logger.LogDebug("Получаю актуальный nextRun для шаблона {templateId}. Текущий nextRun: {nextRun}. Период {duration} дней, с {_startDate} по {_endDate}. Исключить выходные и праздники: {excludeWeekends}. Для нового шаблона: {isNew}", targetTemplate.Id, targetTemplate.NextRun, duration, _startDate, _endDate, excludeWeekends, isNew);
// Получаем список рабочих дней (исключая выходные и праздники)
var workDays = await GetWorkDaysAsync(_startDate, _endDate, excludeWeekends);
if (workDays.Count == 0)
{
logger.LogWarning("Нет доступных рабочих дней в диапазоне {_startDate} - {_endDate}. Возвращаем шаблон со смещением: {offsetYearsIfErrorCalcNextRun} лет.", _startDate, _endDate, offsetYearsIfErrorCalcNextRun);
// смещаем nextRun вперед
var result = FillTemplateResultWithOffsetError(targetTemplate, _startDate, referenceDate);
logger.LogInformation("Получение актуального NextRun завершено, templateId: {templateId}. NextRun {nextRun}. OldNextRun у {oldNextRun} шт.", result.Id, result.NextRun, result.NextRunOld);
return result;
}
// если isNew, то берем первый день периода
if (isNew)
{
var result = new TemplateNextRunResultDto
(
Id: targetTemplate.Id,
NextRun: FindNextAvailableDateIgnoreLoad(workDays, referenceDate, true), //CreateDateTimeOffset(workDays.First(), referenceDate),
NextRunOld: targetTemplate.NextRun
);
logger.LogInformation("Получение актуального NextRun завершено, templateId: {templateId}. Это было создание нового, поместили его в начало периода. NextRun {nextRun}. OldNextRun у {oldNextRun} шт.", result.Id, result.NextRun, result.NextRunOld);
return result;
}
// есть ли NextRun и попадает ли он на рабочий день
// проверяем чтоб было не меньше чем UtcNow
//TODO: тут еще сделать проверку нагрузки на день
if (targetTemplate.NextRun.HasValue && workDays.Contains(DateOnly.FromDateTime(targetTemplate.NextRun.Value.Date)) && targetTemplate.NextRun.Value > DateTimeOffset.UtcNow)
{
var result = new TemplateNextRunResultDto
(
Id: targetTemplate.Id,
NextRun: CreateDateTimeOffset(targetTemplate.NextRun.Value, referenceDate),
NextRunOld: targetTemplate.NextRun
);
logger.LogInformation("Получение актуального NextRun завершено, templateId: {templateId}. NextRun актуальный, возвращаем его. NextRun {nextRun}. OldNextRun у {oldNextRun} шт.", result.Id, result.NextRun, result.NextRunOld);
return result;
}
//TODO: Временный костыль, перемещаем на конец периода
var endPeriodResult = new TemplateNextRunResultDto
(
Id: targetTemplate.Id,
NextRun: FindNextAvailableDateIgnoreLoad(workDays, referenceDate, false),
NextRunOld: targetTemplate.NextRun
);
logger.LogInformation("Получение актуального NextRun завершено, templateId: {templateId}. NextRun перемещен на конец периода. NextRun {nextRun}. OldNextRun у {oldNextRun} шт.", endPeriodResult.Id, endPeriodResult.NextRun, endPeriodResult.NextRunOld);
return endPeriodResult;
}
public async Task<List<TemplateNextRunResultDto>> DistributeTemplatesAsync(DateOnly startDate, int duration, DateTimeOffset referenceDate, List<TemplateNextRunDto> allTemplates, bool excludeWeekends)
{
var _startDate = GetStartPeriodDate(startDate, referenceDate);
var _endDate = GetEndPeriodDate(_startDate, duration);
logger.LogInformation("Начинаем распределение {Count} шаблонов на {duration} дней, с {_startDate} по {_endDate}. ReferenceDate: {referenceDate}. Исключить выходные и праздники: {excludeWeekends}.", allTemplates.Count, duration, _startDate, _endDate, referenceDate, excludeWeekends);
// Получаем список рабочих дней (исключая выходные и праздники)
var workDays = await GetWorkDaysAsync(_startDate, _endDate, excludeWeekends);
if (workDays.Count == 0)
{
logger.LogWarning("Нет доступных рабочих дней в диапазоне {_startDate} - {_endDate}. Возвращаем список шаблонов со смещением: {offsetYearsIfErrorCalcNextRun} лет.", _startDate, _endDate, offsetYearsIfErrorCalcNextRun);
// смещаем все nextRun вперед
var result = allTemplates.Select(t =>
FillTemplateResultWithOffsetError(t, _startDate, referenceDate)
).OrderBy(t => t.NextRun).ToList();
logger.LogInformation("Распределение завершено. Возвращаем {Count} записей. NextRun не изменился у {oldCount} шт. Обновлен NextRun у {changedCount} шт.", result.Count, result.Count(t => t.NextRun == t.NextRunOld), result.Count(t => t.NextRun != t.NextRunOld));
return result;
}
var totalWorks = allTemplates.Count;
// интервал между шаблонами
var interval = GetInterval(workDays.Count, totalWorks);
// строим план, дни на которые должны быть назанчены шаблоны
var plan = GetPlan(totalWorks, workDays);
// Подсчитываем, сколько работ должно быть на каждый день
var expectedDailyLoad = new Dictionary<DateOnly, int>();
foreach (var day in plan)
expectedDailyLoad[day] = expectedDailyLoad.GetValueOrDefault(day, 0) + 1;
#region Группируем шаблоны по дням, на которые они уже назначены
var templatesByDay = new Dictionary<DateOnly, List<TemplateNextRunDto>>();
foreach (var template in allTemplates)
{
if (template.NextRun.HasValue)
{
var day = DateOnly.FromDateTime(template.NextRun.Value.Date);
if (!templatesByDay.ContainsKey(day))
{
templatesByDay[day] = new List<TemplateNextRunDto>();
}
templatesByDay[day].Add(template);
}
}
#endregion
#region Подсчитываем текущую нагрузку на каждый день
var currentDailyLoad = new Dictionary<DateOnly, int>();
foreach (var day in workDays)
{
currentDailyLoad[day] = 0;
}
foreach (var kvp in templatesByDay)
{
var day = kvp.Key;
var count = kvp.Value.Count;
currentDailyLoad[day] = count;
}
#endregion
#region Определяем, какие шаблоны нужно оставить, а какие переназначить
var templatesToKeep = new List<TemplateNextRunResultDto>();
var templatesToReassign = new List<TemplateNextRunDto>();
foreach (var kvp in templatesByDay)
{
var day = kvp.Key;
var templates = kvp.Value;
// Если день — нерабочий, то все шаблоны нужно переназначить
if (!workDays.Contains(day))
{
foreach (var template in templates)
{
templatesToReassign.Add(template);
logger.LogDebug("Шаблон {TemplateId} назначен на нерабочий день {NextRun} — будет переназначен.", template.Id, template.NextRun);
}
continue;
}
var expectedCount = expectedDailyLoad.GetValueOrDefault(day, 0);
var currentCount = templates.Count;
if (currentCount <= expectedCount)
{
// Все шаблоны в этом дне можно оставить
foreach (var template in templates)
{
templatesToKeep.Add(new TemplateNextRunResultDto
(
Id: template.Id,
NextRun: template.NextRun.Value,
NextRunOld: template.NextRun
));
logger.LogDebug("Шаблон {TemplateId} оставлен на дату {NextRun}, т.к. нагрузка в день не превышена.", template.Id, template.NextRun);
}
}
else
{
// Оставляем только expectedCount штук, остальные — переназначить
for (int i = 0; i < expectedCount; i++)
{
var template = templates[i];
templatesToKeep.Add(new TemplateNextRunResultDto
(
Id: template.Id,
NextRun: template.NextRun.Value,
NextRunOld: template.NextRun
));
logger.LogDebug("Шаблон {TemplateId} оставлен на дату {NextRun}, т.к. нагрузка в день не превышена.", template.Id, template.NextRun);
}
for (int i = expectedCount; i < currentCount; i++)
{
var template = templates[i];
templatesToReassign.Add(template);
logger.LogDebug("Шаблон {TemplateId} переназначается, т.к. нагрузка в день превышена.", template.Id);
}
}
}
#endregion
#region Добавляем шаблоны, у которых не было даты тоже нужно распределить
foreach (var template in allTemplates)
{
if (!template.NextRun.HasValue)
{
templatesToReassign.Add(template);
logger.LogDebug("Шаблон {TemplateId} не имел даты — будет распределён.", template.Id);
}
}
#endregion
#region Распределяем шаблоны, которые нужно переназначить, учитывая нагрузку
var updatedTemplates = new List<TemplateNextRunResultDto>(templatesToKeep);
foreach (var template in templatesToReassign)
{
var nextRun = FindNextAvailableDateWithLoad(currentDailyLoad, workDays, referenceDate, expectedDailyLoad);
if (nextRun.HasValue)
{
var day = DateOnly.FromDateTime(nextRun.Value.Date);
currentDailyLoad[day]++;
updatedTemplates.Add(new TemplateNextRunResultDto
(
Id: template.Id,
NextRun: nextRun.Value,
NextRunOld: template.NextRun
));
logger.LogDebug("Шаблону {TemplateId} назначен NextRun: {NextRun}, NextRunOld: {NextRunOld}", template.Id, nextRun, template.NextRun);
}
else
{
logger.LogWarning("Не удалось назначить дату для шаблона {TemplateId}", template.Id);
// Возвращаем с ошибкой
updatedTemplates.Add(new TemplateNextRunResultDto
(
Id: template.Id,
NextRun: CreateDateTimeOffset(DateTimeOffset.UtcNow.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate),
NextRunOld: template.NextRun
));
}
}
#endregion
updatedTemplates = updatedTemplates.OrderBy(t => t.NextRun).ToList();
logger.LogDebug("--- Результат распределения ---");
foreach (var item in updatedTemplates)
logger.LogDebug("Шаблон {templateId}, NextRun: {NextRun}, NextRunOld: {NextRunOld}", item.Id, item.NextRun, item.NextRunOld);
logger.LogInformation("Распределение завершено. Возвращаем {Count} записей. NextRun не изменился у {oldCount} шт. Обновлен NextRun у {changedCount} шт.", updatedTemplates.Count, updatedTemplates.Count(t => t.NextRun == t.NextRunOld), updatedTemplates.Count(t => t.NextRun != t.NextRunOld));
return updatedTemplates;
}
/// <summary>
/// Заполнить TemplateNextRunResultDto, если нет рабочих дней со смещением
/// </summary>
/// <param name="template"></param>
/// <returns></returns>
private TemplateNextRunResultDto FillTemplateResultWithOffsetError(TemplateNextRunDto template, DateOnly startDate, DateTimeOffset referenceDate)
{
var result = new TemplateNextRunResultDto
(
Id: template.Id,
//NextRun = template.NextRun.HasValue
// // если есть nextRun, то прибавляем к нему offsetYearsIfErrorCalcNextRun
// ? CreateNextRun(template.NextRun.Value.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate)
// // если нет nextRun, берем сегодня и прибавляем offsetYearsIfErrorCalcNextRun
// : CreateNextRun(DateTimeOffset.UtcNow.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate),
NextRun: CreateDateTimeOffset(startDate.AddYears(offsetYearsIfErrorCalcNextRun), referenceDate),
NextRunOld: template.NextRun
);
logger.LogDebug("Нет рабочих дней, сдвинул nextRun у шаблона {Id} на {offsetYearsIfErrorCalcNextRun} лет вперед, nextRun: {NextRun}, nextRunOld: {NextRunOld}", result.Id, offsetYearsIfErrorCalcNextRun, result.NextRun, result.NextRunOld);
return result;
}
/// <summary>
/// Получает NextRun не учитывая нагрузку на день
/// </summary>
/// <param name="workDays"></param>
/// <param name="referenseDate"></param>
/// <param name="isSearchFirst">Искать с начала = true, false - искать с конца</param>
/// <returns></returns>
private DateTimeOffset FindNextAvailableDateIgnoreLoad(List<DateOnly> workDays, DateTimeOffset referenseDate, bool isSearchFirst)
{
// возможно это временная реализация метода, используется для GetValidNextRunForTemplateAsync
if (!isSearchFirst)
{
// берем первый рабочий день с конца и возвращаем его
var lastDate = CreateDateTimeOffset(workDays.Last(), referenseDate);
logger.LogDebug("Берем первый рабочий день с конца. Сгенерированный NextRun: {lastDate}", lastDate);
return lastDate;
}
// будем искать сначала
// берем первый день, и смотрим не прошло ли уже время
var firstDate = CreateDateTimeOffset(workDays.First(), referenseDate);
if (firstDate > DateTimeOffset.UtcNow)
{
logger.LogDebug("Берем первый день с начала. Сгенерированный NextRun: {firstDate}", firstDate);
return firstDate;
}
// тогда берем второй день
if (workDays.Count > 1)
{
var secondDate = CreateDateTimeOffset(workDays.ElementAt(1), referenseDate);
logger.LogDebug("Берем второй день сначала, так как у первого дня уже время прошло. Сгенерированный NextRun: {secondDate}", secondDate);
return secondDate;
}
logger.LogWarning("Вернули nextRun: {firstDate} из первого дня распределения, хотя время уже просрочено. Всего рабочих дней: {workDays}", firstDate, workDays.Count);
// если вдруг список рабочих дней был не >1, то тогда берем первый и вренем его. Так никогда не должно быть
return firstDate;
}
/// <summary>
/// Находит следующий день с доступной нагрузкой, учитывая expectedDailyLoad
/// </summary>
/// <param name="currentDailyLoad"></param>
/// <param name="workDays"></param>
/// <param name="referenceDate"></param>
/// <param name="expectedDailyLoad"></param>
/// <returns></returns>
private DateTimeOffset? FindNextAvailableDateWithLoad(Dictionary<DateOnly, int> currentDailyLoad, List<DateOnly> workDays, DateTimeOffset referenceDate, Dictionary<DateOnly, int> expectedDailyLoad)
{
foreach (var day in workDays)
{
var expected = expectedDailyLoad.GetValueOrDefault(day, 0);
var current = currentDailyLoad.GetValueOrDefault(day, 0);
if (current < expected)
{
return CreateDateTimeOffset(day, referenceDate);
}
}
// Если нет дня с доступной нагрузкой — ищем день с минимальной текущей нагрузкой, но не превышающий план
foreach (var day in workDays)
{
var expected = expectedDailyLoad.GetValueOrDefault(day, 0);
var current = currentDailyLoad.GetValueOrDefault(day, 0);
if (current < expected)
{
return CreateDateTimeOffset(day, referenceDate);
}
}
// Если все дни уже загружены по плану — ищем день с минимальной нагрузкой
var minLoad = currentDailyLoad.Values.Min();
var minDay = currentDailyLoad.First(x => x.Value == minLoad).Key;
return CreateDateTimeOffset(minDay, referenceDate);
}
/// <summary>
/// Получить план: дни, на которые должны быть назначены шаблоны
/// </summary>
/// <param name="totalWorks"></param>
/// <param name="workDays"></param>
/// <returns></returns>
private List<DateOnly> GetPlan(int totalWorks, List<DateOnly> workDays)
{
var totalDays = workDays.Count();
var interval = GetInterval(totalDays, totalWorks);
var plan = new List<DateOnly>();
for (int i = 0; i < totalWorks; i++)
{
#region Вынести в метод
// Вычисляем индекс дня, на который нужно назначить i-ю работу
//var dayIndex = (int)Math.Round(i * interval);
var dayIndex = (int)Math.Floor(i * interval);
// Защита от возможного выхода за границы массива из-за округления
if (dayIndex >= totalDays)
dayIndex = totalDays - 1;
#endregion
var date = workDays[dayIndex];
plan.Add(date);
}
logger.LogDebug("--- План распределения ---");
foreach (var item in plan.GroupBy(t => t))
logger.LogDebug("Дата: {date}, кол-во работ: {workCount}", item.Key, item.Count());
return plan;
}
/// <summary>
/// Рассчитать интервал между шаблонами
/// </summary>
/// <param name="totalDays">Кол-во рабочих дней</param>
/// <param name="totalWorks">Кол-во шаблонов</param>
/// <returns></returns>
private double GetInterval(int totalDays, int totalWorks)
{
var interval = (double)totalDays / totalWorks;
logger.LogDebug("Интевал между шаблонами: {interval}", interval);
return interval;
}
/// <summary>
/// Создать DateTimeOffset (из DateTimeOffset + refDate)
/// </summary>
/// <param name="date">Откуда взять дату</param>
/// <param name="refDate">Откуда взять время</param>
/// <returns></returns>
private DateTimeOffset CreateDateTimeOffset(DateTimeOffset date, DateTimeOffset refDate)
{
return CreateDateTimeOffset(DateOnly.FromDateTime(date.Date), refDate);
}
/// <summary>
/// Создать DateTimeOffset (из DateOnly + refDate)
/// </summary>
/// <param name="date"></param>
/// <param name="refDate"></param>
/// <returns></returns>
private DateTimeOffset CreateDateTimeOffset(DateOnly date, DateTimeOffset refDate)
{
return new DateTimeOffset(date.Year, date.Month, date.Day, refDate.Hour, refDate.Minute, refDate.Second, TimeSpan.Zero);
}
/// <summary>
/// Получить дату начала распределения относительно StartDate, ReferenceDate и текущей даты
/// </summary>
/// <param name="startDate"></param>
/// <param name="referenceDate"></param>
/// <returns></returns>
private DateOnly GetStartPeriodDate(DateOnly startDate, DateTimeOffset referenceDate)
{
// дата начала не может быть раньше referenceDate
// и не может быть раньше чем сегодня
var dateStart = startDate;
// если dateStart < чем refDate, то берем refDate
if (dateStart < DateOnly.FromDateTime(referenceDate.Date))
dateStart = DateOnly.FromDateTime(referenceDate.Date);
// если dateStart < чем текущая дата, то берем текущую дату
if (dateStart < DateOnly.FromDateTime(DateTime.UtcNow.Date))
dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Date);
// если текущее время > referenceDate, то прибавляем 1 день
var dateTimeStart = CreateDateTimeOffset(dateStart, referenceDate);
if (dateTimeStart < DateTimeOffset.UtcNow)
dateStart = dateStart.AddDays(1);
return dateStart;
}
/// <summary>
/// Получить дату конца периода распределения
/// </summary>
/// <param name="startDate"></param>
/// <param name="duration"></param>
/// <returns></returns>
private DateOnly GetEndPeriodDate(DateOnly startDate, int duration)
{
// duration — количество дней, включая startDate
// поэтому вычитаем 1, чтобы получить сдвиг
return startDate.AddDays(duration - 1);
}
/// <summary>
/// Получить список выходных дней
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <returns></returns>
private async Task<HashSet<DateOnly>> GetWeekendsAsync(DateOnly startDate, DateOnly endDate)
{
var weekends = await weekendDayService.GetWeekends(startDate, endDate).ToListAsync();
logger.LogDebug("Найдено не рабочих дней: {Count}", weekends.Count);
foreach (var weekend in weekends)
logger.LogDebug("Не рабочий день: {weekend}", weekend);
return weekends.ToHashSet();
}
/// <summary>
/// Получить список выходных дней
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <returns></returns>
public async Task<HashSet<DateOnly>> GetWeekendsTESTAsync(DateOnly startDate, DateOnly endDate, DateTimeOffset referenceDate)
{
// проверяем это текущие или прошлые сутки относительно refDate, если надо, корректируем dateStart, dateEnd
bool isPreviousDays = IsPreviousDays(referenceDate);
if (isPreviousDays)
{
// Это предыдущие сутки, корректируем
startDate = startDate.AddDays(-1);
// так как мы потом будем сдвигать выходные дни влево, то надо взять на один день дальше, чтоб знать какой он
// вроде бы это не костыль
endDate = endDate.AddDays(1);
//endDate = endDate.AddDays(-1);
logger.LogDebug("Это предыдущие сутки относительно referenceDate: {referenceDate}, корректируем даты для получения выходных дней, новые значения dateStart: {dateStart}, dateEnd: {dateEnd}", referenceDate, startDate, endDate);
}
var weekends = await weekendDayService.GetWeekends(startDate, endDate).ToListAsync();
var weekendsResult = new List<DateOnly>();
if (isPreviousDays)
{
// так как должны быть предыдущие дни, отнимаем 1 день
foreach (var weekend in weekends)
{
var previous = weekend.AddDays(-1);
logger.LogDebug("Выходной день, -1, так как день должен быть предыдущий, старое значение {weekend}, новое {previous}", weekend, previous);
weekendsResult.Add(previous);
}
}
else
{
weekendsResult = weekends;
}
logger.LogDebug("Найдено не рабочих дней: {Count}", weekendsResult.Count);
foreach (var weekend in weekendsResult)
logger.LogDebug("Не рабочий день: {weekend}", weekend);
return weekendsResult.ToHashSet();
}
/// <summary>
/// Возвращает список рабочих дней в диапазоне, исключая выходные и праздники.
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="excludeWeekends">Исключить выходные и праздники</param>
/// <returns></returns>
public async Task<List<DateOnly>> GetWorkDaysAsync(DateOnly start, DateOnly end, bool excludeWeekends)
{
// Получаем список выходных/праздничных дней
var weekends = new HashSet<DateOnly>();
if (excludeWeekends)
weekends = await GetWeekendsAsync(start, end);
var result = new List<DateOnly>();
var date = start;
while (date <= end)
{
var dayOfWeek = date.DayOfWeek;
if (!weekends.Contains(date))
{
result.Add(date);
}
date = date.AddDays(1);
}
logger.LogDebug("Найдено рабочих дней: {Count}", result.Count);
foreach (var workDay in result)
logger.LogDebug("Рабочий день: {workDay}", workDay);
return result;
}
public async Task<List<DateOnly>> GetWorkDaysTESTAsync(DateOnly start, DateOnly end, bool excludeWeekends, DateTimeOffset referenceDate)
{
logger.LogDebug("Формирование списка рабочих дней, start: {start}, end: {end}, excludeWeekends: {excludeWeekends}, referenceDate: {referenceDate}", start, end, excludeWeekends, referenceDate);
// проверяем это текущие или прошлые сутки относительно refDate, если надо, корректируем dateStart, dateEnd
if (IsPreviousDays(referenceDate))
{
// Это предыдущие сутки, корректируем
start = start.AddDays(-1);
end = end.AddDays(-1);
logger.LogDebug("Это предыдущие сутки относительно referenceDate: {referenceDate}, корректируем даты, новые значения dateStart: {dateStart}, dateEnd: {dateEnd}", referenceDate, start, end);
}
// Получаем список выходных/праздничных дней
var weekends = new HashSet<DateOnly>();
if (excludeWeekends)
weekends = await GetWeekendsTESTAsync(start, end, referenceDate);
var result = new List<DateOnly>();
var date = start;
while (date <= end)
{
var dayOfWeek = date.DayOfWeek;
if (!weekends.Contains(date))
{
result.Add(date);
}
date = date.AddDays(1);
}
logger.LogDebug("Найдено рабочих дней: {Count}", result.Count);
foreach (var workDay in result)
logger.LogDebug("Рабочий день: {workDay}", workDay);
return result;
}
/// <summary>
/// Это предыдущие сутки? Относительно referenceDate и МСК времени
/// </summary>
/// <param name="referenceDate"></param>
/// <returns></returns>
private bool IsPreviousDays(DateTimeOffset referenceDate)
{
// смещение часов относительно UTC - MSK
int offsetMskHour = 3;
// если время больше 21 часа, то это текущие сутки, если меньше, то прошлые
int maxHour = 24 - offsetMskHour;
if (referenceDate.Hour >= maxHour && referenceDate.Hour <= 23)
{
// это прошлые сутки
logger.LogDebug("ReferenceDate {refDate} - прошлые стуки", referenceDate);
return true;
}
else
{
// это текущие сутки
logger.LogDebug("ReferenceDate {refDate} - текущие стуки", referenceDate);
return false;
}
}
}
}