using Microsoft.Extensions.Logging; using PARR.DAL.Contracts; using PARR.DAL.DomainModels; using PARR.DAL.Services.Interfaces; using PARR.DAL.TransformServices; namespace PARR.DAL.NextRunServices.Subservices { internal class EsppScheduleTransformService : IEsppScheduleTransformService { private readonly IEsppSchTypeConfigService esppSchTypeConfigService; //private readonly INextRunModifierService nextRunModifierService; private readonly ILogger logger; private static readonly Dictionary monthDict = new Dictionary() { { "Январь", 1 }, { "Февраль", 2 }, { "Март", 3 }, { "Апрель", 4 }, { "Май", 5 }, { "Июнь", 6 }, { "Июль", 7 }, { "Август", 8 }, { "Сентябрь", 9 }, { "Октябрь", 10 }, { "Ноябрь", 11 }, { "Декабрь", 12 } }; private static readonly Dictionary orderDict = new Dictionary() { { "Первый", 1 }, { "Второй", 2 }, { "Третий", 3 }, { "Четвертый", 4 }, { "Последний", 5 } }; private static readonly Dictionary dwDict = new Dictionary() { {"Понедельник", 1 }, {"Вторник", 2 }, {"Среда", 3 }, {"Четверг", 4 }, {"Пятница", 5 }, {"Суббота", 6 }, {"Воскресенье", 0 } }; public EsppScheduleTransformService( ILogger logger, IEsppSchTypeConfigService esppSchTypeConfigService //INextRunModifierService nextRunModifierService ) { this.esppSchTypeConfigService = esppSchTypeConfigService; //this.nextRunModifierService = nextRunModifierService; this.logger = logger; } public async Task GetNextDateAsync(Guid jobGroupId, DateTimeOffset referenceDate) { var esppSchedule = await GetEsppScheduleAsync(jobGroupId); return GetNextDate(esppSchedule, referenceDate); } private DateTimeOffset GetNextDate(EsppScheduleDto esppSchedule, DateTimeOffset referenceDate) { var nextRun = referenceDate; //--------- // Для рассчета по МСК времени, потому что в ЮТС может быть еще ВСК, а по МСК это уже ПНД var offsetReferenceDate = referenceDate.ToOffset(new TimeSpan(3, 0, 0)); //--------- switch (esppSchedule.TypeSchedule.Id) { case (int)EsppSchTypeScheduleEnum.Regularly: nextRun = GetNextDateRegularly(esppSchedule.Values, offsetReferenceDate); break; case (int)EsppSchTypeScheduleEnum.Weekly: nextRun = GetNextDateWeekly(esppSchedule.Values, offsetReferenceDate); break; case (int)EsppSchTypeScheduleEnum.Monthly: nextRun = GetNextDateMonthly(esppSchedule.Values, offsetReferenceDate); break; case (int)EsppSchTypeScheduleEnum.Monthly2: nextRun = GetNextDateMonthly2(esppSchedule.Values, offsetReferenceDate); break; case (int)EsppSchTypeScheduleEnum.Annually: nextRun = GetNextDateAnnually(esppSchedule.Values, offsetReferenceDate); break; case (int)EsppSchTypeScheduleEnum.Annually2: nextRun = GetNextDateAnnually2(esppSchedule.Values, offsetReferenceDate); break; } // Возвращаем обратно в ЮТС nextRun = nextRun.ToOffset(TimeSpan.Zero); return nextRun; } //private List GetNextSchedule(EsppScheduleDto esppSchedule, DateTimeOffset referenceDate) //{ // // расписание на оставшееся на сегодня время. его так мало... // referenceDate = referenceDate.UtcDateTime; // var schedules = new List(); // var curTime = referenceDate; // //Если ещё не произошло то добавляем расписание, возможно это новое AiW и он ещё ниразу не запускался. // //lastRun это будущее // if (referenceDate >= DateTimeOffset.UtcNow && referenceDate <= DateTimeOffset.UtcNow.EndOfDay()) // schedules.Add(referenceDate); // while (curTime < DateTimeOffset.UtcNow.EndOfDay()) // { // switch (esppSchedule.TypeSchedule.Id) // { // case (int)EsppSchTypeScheduleEnum.Regularly: // curTime = GetNextDateRegularly(esppSchedule.Values, curTime); // break; // case (int)EsppSchTypeScheduleEnum.Weekly: // curTime = GetNextDateWeekly(esppSchedule.Values, curTime); // break; // case (int)EsppSchTypeScheduleEnum.Monthly: // curTime = GetNextDateMonthly(esppSchedule.Values, curTime); // break; // case (int)EsppSchTypeScheduleEnum.Monthly2: // curTime = GetNextDateMonthly2(esppSchedule.Values, curTime); // break; // case (int)EsppSchTypeScheduleEnum.Annually: // curTime = GetNextDateAnnually(esppSchedule.Values, curTime); // break; // case (int)EsppSchTypeScheduleEnum.Annually2: // curTime = GetNextDateAnnually2(esppSchedule.Values, curTime); // break; // } // schedules.Add(curTime); // } // schedules.RemoveAll(s => s > DateTimeOffset.UtcNow.EndOfDay()); // return schedules.OrderBy(t => t).ToList(); //} //public async Task> GetNextScheduleAsync(Guid jobGroupId, DateTimeOffset referenceDate) //{ // //TODO: тут не проверен переход через выходные дни!!! Переход не используется, так как тут не рассчитываем NextRun. // //В общем проверить, когда будем тестировать агента, что с датами все ок // var esppSchedule = await GetEsppScheduleAsync(jobGroupId); // var nextSchedule = GetNextSchedule(esppSchedule, referenceDate); // return nextSchedule; //} private async Task GetEsppScheduleAsync(Guid jobGroupId) { var esppSchedule = await esppSchTypeConfigService.GetEsppScheduleDtoAsync(jobGroupId); if (esppSchedule == null) { logger.LogError($"Не найдено расписание в БД, jobId: {jobGroupId}"); throw new Exception($"Не найдено расписание в БД, jobId: {jobGroupId}"); } return esppSchedule; } private DateTimeOffset GetNextDateRegularly(List values, DateTimeOffset referenceDate) { var regDict = new Dictionary { {"Каждый час",1}, {"Каждые 2 часа",2}, {"Каждые 3 часа", 3}, {"Каждые 4 часа", 4}, {"Каждые 6 часов", 6}, {"Каждые 12 часов", 12}, {"Ежедневно", 24}, {"Каждые 72 часа", 72}, {"Каждые 96 часов", 96}, {"Каждые 2 недели", 336}, {"Каждые 60 дней", 1440}, {"Каждые 80 дней", 1920}, {"Каждые 90 дней", 2160}, {"Каждые полгода", 4380}, {"Каждые 1,5 года", 13140}, {"Каждые 3 года", 26280}, }; if (!regDict.TryGetValue(values[0].Value.Value, out int regNum)) { logger.LogError("Неизвестное значение: {Value}", values[0].Value.Value); throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}"); } var calcDay = referenceDate; //TODO: вот это повторяется от метода к методу //Если итоговая дата указывает на прошлое, то повторяем расчёт и уходим в рекурсию if (calcDay < DateTimeOffset.UtcNow) calcDay = GetNextDateRegularly(values, calcDay.AddHours(regNum)); return calcDay; } private DateTimeOffset GetNextDateWeekly(List values, DateTimeOffset referenceDate) { if (!dwDict.TryGetValue(values[0].Value.Value, out int dwNum)) { logger.LogError("Неизвестное значение дня недели: {Value}", values[0].Value.Value); throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}"); } var calcDay = referenceDate; if (calcDay < DateTimeOffset.UtcNow) calcDay = new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, DateTimeOffset.UtcNow.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset); if (((int)calcDay.DayOfWeek) != dwNum) calcDay = OffsetToDayOfWeek(dwNum, calcDay); //TODO: вот это повторяется от метода к методу //Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию if (calcDay < DateTimeOffset.UtcNow) calcDay = GetNextDateWeekly(values, calcDay.AddDays(7));//а вот тут что-то новенькое return calcDay; } private DateTimeOffset GetNextDateMonthly(List values, DateTimeOffset referenceDate) { if (!int.TryParse(values[0].Value.Value, out int dayNum)) { 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) calcDay = new DateTimeOffset(DateTimeOffset.UtcNow.Year, DateTimeOffset.UtcNow.Month, dayNum, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset); //TODO: вот это повторяется от метода к методу //Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию if (calcDay < DateTimeOffset.UtcNow) calcDay = GetNextDateMonthly(values, calcDay.AddMonths(1)); return calcDay; } private DateTimeOffset GetNextDateMonthly2(List values, DateTimeOffset referenceDate) { if (!orderDict.TryGetValue(values[0].Value.Value, out int orderNum)) { logger.LogError("Неизвестное значение порядка дня: {Value}", values[0].Value.Value); throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}"); } if (!dwDict.TryGetValue(values[1].Value.Value, out int dwNum)) { logger.LogError("Неизвестное значение дня недели: {Value}", values[1].Value.Value); throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}"); } 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); 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) return GetNextDateMonthly2(values, startDay.AddMonths(1)); return calcDay; } private DateTimeOffset GetNextDateAnnually(List values, DateTimeOffset referenceDate) { if (!monthDict.TryGetValue(values[0].Value.Value, out int monthNum)) { logger.LogError("Неизвестное значение номера месяца: {Value}", values[0].Value.Value); throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}"); } if (!int.TryParse(values[1].Value.Value, out int dayNum)) { logger.LogError("Неизвестное значение дня месяца: {Value}", values[0].Value.Value); throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}"); } var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year; 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)); return calcDay; } private DateTimeOffset GetNextDateAnnually2(List values, DateTimeOffset referenceDate) { var monthADict = new Dictionary { { "Января", 1 }, { "Февраля", 2 }, { "Марта", 3 }, { "Апреля", 4 }, { "Мая", 5 }, { "Июня", 6 }, { "Июля", 7 }, { "Августа", 8 }, { "Сентября", 9 }, { "Октября", 10 }, { "Ноября", 11 }, { "Декабря", 12 } }; if (!monthADict.TryGetValue(values[2].Value.Value, out int monthNum)) { logger.LogError("Неизвестное значение номера месяца: {Value}", values[2].Value.Value); throw new ArgumentException($"Неизвестное значение: {values[2].Value.Value}"); } if (!orderDict.TryGetValue(values[0].Value.Value, out int orderNum)) { logger.LogError("Неизвестное значение порядка дня: {Value}", values[0].Value.Value); throw new ArgumentException($"Неизвестное значение: {values[0].Value.Value}"); } if (!dwDict.TryGetValue(values[1].Value.Value, out int dwNum)) { logger.LogError("Неизвестное значение дня недели: {Value}", values[1].Value.Value); throw new ArgumentException($"Неизвестное значение: {values[1].Value.Value}"); } var year = (referenceDate.Year < DateTimeOffset.UtcNow.Year) ? DateTimeOffset.UtcNow.Year : referenceDate.Year; if (year == DateTimeOffset.UtcNow.Year && referenceDate.Month < DateTimeOffset.UtcNow.Month) year += 1; var calcDay = new DateTimeOffset( year, monthNum, 1, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset); List daysList = FindAllDaysInMonth(dwNum, calcDay); if (orderNum == 5)//5-последний calcDay = daysList.Last(); else calcDay = daysList[orderNum - 1]; //TODO: вот это повторяется от метода к методу //Если итоговая дата указывает на прошлое, то повторяем расчёт и ухоим в рекурсию if (calcDay < DateTimeOffset.UtcNow) calcDay = GetNextDateAnnually2(values, calcDay.AddYears(1)); return calcDay; } private List FindAllDaysInMonth(int dwNum, DateTimeOffset startDay) { var calcDay = startDay; if (((int)calcDay.DayOfWeek) != dwNum) calcDay = OffsetToDayOfWeek(dwNum, calcDay); var daysList = new List(); var monthNum = calcDay.Month; while (calcDay.Month == monthNum) { daysList.Add(calcDay); calcDay = calcDay.AddDays(7); } return daysList; } private DateTimeOffset OffsetToDayOfWeek(int dwNum, DateTimeOffset calcDay) { var daysOffset = dwNum - ((int)calcDay.DayOfWeek); if (daysOffset < 0) daysOffset = 7 + daysOffset; return calcDay.AddDays(daysOffset); } //public DateTimeOffset GetNextDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod) //{ // var nextRun = lastRun; // switch (periodType) // { // case (DistributionPeriodTypeEnum.Day): // nextRun = lastRun.AddDays(ParseInt(distributionPeriod)); // break; // case (DistributionPeriodTypeEnum.Month): // nextRun = lastRun.AddMonths(ParseInt(distributionPeriod)); // break; // case (DistributionPeriodTypeEnum.Year): // nextRun = lastRun.AddYears(ParseInt(distributionPeriod)); // break; // } // return nextRunModifierService.GetWorkDayAsync(nextRun).GetAwaiter().GetResult(); //} //private DateTimeOffset GetPrevDateForDistributionRun(DateTimeOffset lastRun, DistributionPeriodTypeEnum periodType, string distributionPeriod) //{ // switch (periodType) // { // case (DistributionPeriodTypeEnum.Day): // return lastRun.AddDays(-ParseInt(distributionPeriod)); // case (DistributionPeriodTypeEnum.Month): // return lastRun.AddMonths(-ParseInt(distributionPeriod)); // case (DistributionPeriodTypeEnum.Year): // return lastRun.AddYears(-ParseInt(distributionPeriod)); // } // return lastRun; //} //public async Task GetStartPeriodForDateAsync(Guid jobGroupId, DateTimeOffset date, DateTimeOffset referenceDate, DistributionPeriodTypeEnum periodType, string distributionPeriod) //{ // var esppSchedule = await GetEsppScheduleAsync(jobGroupId); // var currentStartPeriod = referenceDate; // var currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod); // while (!(date >= currentStartPeriod && date < currentEndPeriod)) // { // if (date > currentEndPeriod) // { // currentStartPeriod = currentEndPeriod; // currentEndPeriod = GetNextDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod); // } // else // { // currentEndPeriod = currentStartPeriod; // currentStartPeriod = GetPrevDateForDistributionRun(currentStartPeriod, periodType, distributionPeriod); // } // } // return DateOnly.FromDateTime(currentStartPeriod.DateTime); //} //private int ParseInt(string value) //{ // try // { // return int.Parse(value); // } // catch (Exception ex) // { // logger.LogError(ex, $"{nameof(this.GetType)}, получение конца периода. Не смог распарсить string в int для значения {value}."); // return 0; // } //} //private DistributionPeriodTypeEnum ParseDistributionPeriodType(string value) //{ // var result = (DistributionPeriodTypeEnum)Enum.Parse(typeof(DistributionPeriodTypeEnum), value); // return result; //} } }