From ce6b0f2673f1a309ca11d7efce11b57d92cf8762 Mon Sep 17 00:00:00 2001 From: Polianin Nikita Date: Sun, 2 Feb 2025 03:30:52 +0300 Subject: [PATCH] feat: add cron skipping date --- .../Common/Services/CronUpdateSkipService.cs | 69 +++++++++++++++++++ .../BackgroundTasks/ScheduleSyncService.cs | 38 +++++++--- .../Model/GeneralSettings/ScheduleSettings.cs | 24 +++++++ 3 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 Endpoint/Common/Services/CronUpdateSkipService.cs diff --git a/Endpoint/Common/Services/CronUpdateSkipService.cs b/Endpoint/Common/Services/CronUpdateSkipService.cs new file mode 100644 index 0000000..4f44fea --- /dev/null +++ b/Endpoint/Common/Services/CronUpdateSkipService.cs @@ -0,0 +1,69 @@ +using Cronos; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mirea.Api.Endpoint.Common.Services; + +public static class CronUpdateSkipService +{ + public static ScheduleSettings.CronUpdateSkip Get(this Dto.Common.CronUpdateSkip date) + { + if (date.Date.HasValue) + return new ScheduleSettings.CronUpdateSkip(date.Date.Value); + if (date is { Start: not null, End: not null }) + return new ScheduleSettings.CronUpdateSkip(date.Start.Value, date.End.Value); + + throw new ArgumentException("It is impossible to create a structure because it has incorrect values."); + } + + public static List Filter(this List data, DateOnly? currentDate = null) + { + currentDate ??= DateOnly.FromDateTime(DateTime.Now); + return data.OrderBy(x => x.End ?? x.Date) + .Where(x => x.Date == currentDate || (x.Start <= currentDate && x.End >= currentDate)) + .ToList(); + } + + public static List Filter(this List data, DateTime? currentDate = null) => + data.Filter(DateOnly.FromDateTime(currentDate ?? DateTime.Now)); + + public static List GetNextTask(this List data, + CronExpression expression, int depth = 1, DateOnly? currentDate = null) + { + if (depth <= 0) + return []; + + currentDate ??= DateOnly.FromDateTime(DateTime.UtcNow); + DateTimeOffset nextRunTime = currentDate.Value.ToDateTime(new TimeOnly(0, 0, 0)); + + List result = []; + + do + { + var lastSkip = data.Filter(nextRunTime.DateTime).LastOrDefault(); + + if (lastSkip is { Start: not null, End: not null }) + nextRunTime = new DateTimeOffset(lastSkip.End.Value.AddDays(1), new TimeOnly(0, 0, 0), TimeSpan.Zero); + else if (lastSkip.Date.HasValue) + nextRunTime = new DateTimeOffset(lastSkip.Date.Value.AddDays(1), new TimeOnly(0, 0, 0), TimeSpan.Zero); + + var next = expression.GetNextOccurrence(nextRunTime, TimeZoneInfo.Local); + + if (!next.HasValue) + return result; + + nextRunTime = next.Value; + + if (!data.Filter(nextRunTime.DateTime).Any()) + continue; + + result.Add(nextRunTime); + nextRunTime = nextRunTime.AddMinutes(1); + + } while (result.Count < depth); + + return result; + } +} \ No newline at end of file diff --git a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs index a2896f8..5ed3575 100644 --- a/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs +++ b/Endpoint/Configuration/Core/BackgroundTasks/ScheduleSyncService.cs @@ -5,8 +5,11 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Configuration.Model; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; using Mirea.Api.Endpoint.Sync; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -16,6 +19,7 @@ public class ScheduleSyncService : IHostedService, IDisposable { private Timer? _timer; private string _cronUpdate; + private List _cronUpdateSkip; private readonly ILogger _logger; private CancellationTokenSource _cancellationTokenSource = new(); private readonly IServiceProvider _serviceProvider; @@ -26,15 +30,28 @@ public class ScheduleSyncService : IHostedService, IDisposable _logger = logger; _serviceProvider = serviceProvider; _cronUpdate = generalConfigMonitor.CurrentValue.ScheduleSettings!.CronUpdateSchedule; + _cronUpdateSkip = generalConfigMonitor.CurrentValue.ScheduleSettings!.CronUpdateSkipDateList; ScheduleSyncManager.OnForceSyncRequested += OnForceSyncRequested; _onChangeUpdateCron = generalConfigMonitor.OnChange((config) => { - if (config.ScheduleSettings?.CronUpdateSchedule == null || _cronUpdate == config.ScheduleSettings.CronUpdateSchedule) - return; + var updated = false; + if (config.ScheduleSettings?.CronUpdateSchedule != null && _cronUpdate != config.ScheduleSettings.CronUpdateSchedule) + { + _cronUpdate = config.ScheduleSettings.CronUpdateSchedule; + updated = true; + } - _cronUpdate = config.ScheduleSettings.CronUpdateSchedule; - OnUpdateIntervalRequested(); + if (config.ScheduleSettings?.CronUpdateSkipDateList != null && !config.ScheduleSettings.CronUpdateSkipDateList.SequenceEqual(_cronUpdateSkip)) + { + _cronUpdateSkip = config.ScheduleSettings.CronUpdateSkipDateList + .OrderBy(x => x.End ?? x.Date) + .ToList(); + updated = true; + } + + if (updated) + OnUpdateIntervalRequested(); }); } @@ -65,17 +82,20 @@ public class ScheduleSyncService : IHostedService, IDisposable return; } - var nextRunTime = CronExpression.Parse(_cronUpdate).GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local); + var expression = CronExpression.Parse(_cronUpdate); - if (!nextRunTime.HasValue) + var nextRunTime = _cronUpdateSkip.GetNextTask(expression).FirstOrDefault(); + + if (nextRunTime == default) { - _logger.LogWarning("No next run time found. The task will not be scheduled. Timezone: {TimeZone}", TimeZoneInfo.Local.DisplayName); + _logger.LogWarning("No next run time found. The task will not be scheduled. Timezone: {TimeZone}", + TimeZoneInfo.Local.DisplayName); return; } - _logger.LogInformation("Next task run in {Time}", nextRunTime.Value.ToString("G")); + _logger.LogInformation("Next task run in {Time}", nextRunTime.ToString("G")); - var delay = (nextRunTime.Value - DateTimeOffset.Now).TotalMilliseconds; + var delay = (nextRunTime - DateTimeOffset.Now).TotalMilliseconds; // The chance is small, but it's better to check if (delay <= 0) diff --git a/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs b/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs index ac1e6d4..bb66c7b 100644 --- a/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs +++ b/Endpoint/Configuration/Model/GeneralSettings/ScheduleSettings.cs @@ -31,9 +31,33 @@ public class ScheduleSettings : IIsConfigured public PairPeriodTime(Dto.Common.PairPeriodTime time) : this(time.Start, time.End) { } } + public record struct CronUpdateSkip + { + public DateOnly? Start { get; set; } + public DateOnly? End { get; set; } + public DateOnly? Date { get; set; } + + public CronUpdateSkip(DateOnly d1, DateOnly d2) + { + if (d1 > d2) + { + Start = d2; + End = d1; + } + else + { + Start = d1; + End = d2; + } + } + + public CronUpdateSkip(DateOnly d1) => Date = d1; + } + public required string CronUpdateSchedule { get; set; } public DateOnly StartTerm { get; set; } public required IDictionary PairPeriod { get; set; } + public List CronUpdateSkipDateList { get; set; } = []; public bool IsConfigured() {