From f21d12b9e88f07dcc2fb4bb7da3f3d2b2ff0af2c Mon Sep 17 00:00:00 2001 From: "Frank R. Haugen" Date: Sun, 21 Jan 2024 18:37:28 +0100 Subject: [PATCH] "Create new CronJobs.Cron library and update CronJobs" A new library, CronJobs.Cron, was created from CronJobs. This library focuses on robust Cron parsing, and has been made independent to avoid unnecessary dependencies. Existing extensions and internal classes were moved to this new library from CronJobs. Moreover, modifications in code include updates to namespaces, functions, and clarifications through added summary comments. Corresponding changes have also been made in the test project. --- Directory.Build.props | 13 +- .../CronExpression.cs | 4 +- Frank.CronJobs.Cron/CronHelper.cs | 132 ++++++++++++++++ .../Frank.CronJobs.Cron.csproj | 8 + .../PredefinedCronExpressions.cs | 143 ++++++++++++++++++ Frank.CronJobs.Cron/README.md | 5 + .../internals}/CronField.cs | 4 +- .../internals}/CronSyntax.cs | 6 +- .../internals}/CronValue.cs | 3 +- .../internals}/DateTimeExtensions.cs | 4 +- .../internals}/Int32Extensions.cs | 2 +- .../internals}/TimeUnit.cs | 2 +- .../ServiceCollectionExtensionsTests.cs | 14 +- .../Frank.CronJobs.Tests.csproj | 9 +- Frank.CronJobs.sln | 6 + Frank.CronJobs/Cron/CronHelper.cs | 45 ------ .../Cron/PredefinedCronExpressions.cs | 22 --- .../DependencyInjection/CronJobsBuilder.cs | 9 +- .../DependencyInjection/ICronJobsBuilder.cs | 3 + .../ServiceCollectionExtensions.cs | 9 ++ Frank.CronJobs/Frank.CronJobs.csproj | 11 +- Frank.CronJobs/Jobs/CronJobRunner.cs | 17 ++- .../Options/CronJobOptionsCollection.cs | 3 + README.md | 23 +++ 24 files changed, 377 insertions(+), 120 deletions(-) rename {Frank.CronJobs/Cron => Frank.CronJobs.Cron}/CronExpression.cs (97%) create mode 100644 Frank.CronJobs.Cron/CronHelper.cs create mode 100644 Frank.CronJobs.Cron/Frank.CronJobs.Cron.csproj create mode 100644 Frank.CronJobs.Cron/PredefinedCronExpressions.cs create mode 100644 Frank.CronJobs.Cron/README.md rename {Frank.CronJobs/Cron => Frank.CronJobs.Cron/internals}/CronField.cs (98%) rename {Frank.CronJobs/Cron => Frank.CronJobs.Cron/internals}/CronSyntax.cs (89%) rename {Frank.CronJobs/Cron => Frank.CronJobs.Cron/internals}/CronValue.cs (98%) rename {Frank.CronJobs/Extensions => Frank.CronJobs.Cron/internals}/DateTimeExtensions.cs (98%) rename {Frank.CronJobs/Extensions => Frank.CronJobs.Cron/internals}/Int32Extensions.cs (97%) rename {Frank.CronJobs/Cron => Frank.CronJobs.Cron/internals}/TimeUnit.cs (98%) delete mode 100644 Frank.CronJobs/Cron/CronHelper.cs delete mode 100644 Frank.CronJobs/Cron/PredefinedCronExpressions.cs diff --git a/Directory.Build.props b/Directory.Build.props index 97825eb..8de5e4b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,10 +4,7 @@ enable enable true - - Frank.CronJobs is a library for running cron jobs in .NET Core applications. - dotnet, cron, job, jobs, cronjob, cronjobs, run, running, async, di, dependency, injection, host, hosting, schedule, scheduled, Frank Haugen, Frank, Haugen, Frank R. Haugen - + true $(NoWarn);1591 @@ -17,9 +14,9 @@ Frank R. Haugen Frank R. Haugen - Copyright (c) 2023 Frank R. Haugen + Copyright (c) 2024 Frank R. Haugen - readme.md + README.md icon.png true MIT @@ -31,8 +28,8 @@ - - + + diff --git a/Frank.CronJobs/Cron/CronExpression.cs b/Frank.CronJobs.Cron/CronExpression.cs similarity index 97% rename from Frank.CronJobs/Cron/CronExpression.cs rename to Frank.CronJobs.Cron/CronExpression.cs index 04a6fd9..c172750 100644 --- a/Frank.CronJobs/Cron/CronExpression.cs +++ b/Frank.CronJobs.Cron/CronExpression.cs @@ -22,7 +22,7 @@ * SOFTWARE. */ -using Frank.CronJobs.Extensions; +using Frank.CronJobs.Cron.internals; namespace Frank.CronJobs.Cron; @@ -41,7 +41,7 @@ public CronExpression(string expression) { _expression = expression ?? throw new ArgumentNullException(nameof(expression)); var subExpressions = expression.ToUpper().Split(' '); - var syntax = new CronSyntax(subExpressions); + var syntax = new internals.CronSyntax(subExpressions); IsValid = syntax.IsValid(); diff --git a/Frank.CronJobs.Cron/CronHelper.cs b/Frank.CronJobs.Cron/CronHelper.cs new file mode 100644 index 0000000..659f5fd --- /dev/null +++ b/Frank.CronJobs.Cron/CronHelper.cs @@ -0,0 +1,132 @@ +namespace Frank.CronJobs.Cron; + +/// +/// Provides helper methods to work with cron expressions. +/// +public static class CronHelper +{ + /// + /// Calculates the next occurrence of a cron expression based on the current UTC time. + /// + /// The cron expression string to be evaluated. + /// A DateTime object representing the next occurrence of the cron expression. + public static DateTime GetNextOccurrence(string cronExpression) + => new CronExpression(cronExpression).Next(DateTime.UtcNow); + + /// + /// Returns the next occurrence of a given cron expression. + /// + /// The cron expression used to calculate the next occurrence. + /// The next occurrence as a DateTime object. + public static DateTime GetNextOccurrence(CronExpression cronExpression) + => cronExpression.Next(DateTime.UtcNow); + + /// + /// Calculates the next occurrence of a scheduled event based on the given cron expression and starting date/time. + /// + /// The cron expression specifying the schedule. + /// The starting date/time to calculate the next occurrence from. + /// The next occurrence of the scheduled event as a DateTime object. + public static DateTime GetNextOccurrence(CronExpression cronExpression, DateTime fromUtc) + => cronExpression.Next(fromUtc); + + /// + /// Returns the next occurrence of the specified cron expression after the given DateTime. + /// + /// The cron expression to evaluate. + /// The DateTime to start searching for the next occurrence. + /// The DateTime of the next occurrence based on the specified cron expression and starting DateTime. + public static DateTime GetNextOccurrence(string cronExpression, DateTime fromUtc) + => new CronExpression(cronExpression).Next(fromUtc); + + /// + /// Calculates the time difference between the current UTC time and the next occurrence of a cron expression. + /// + /// The cron expression specifying the schedule pattern. + /// The time interval until the next occurrence. + public static TimeSpan GetTimeUntilNextOccurrence(string cronExpression) + => GetNextOccurrence(cronExpression) - DateTime.UtcNow; + + /// + /// Returns the time duration until the next occurrence specified by the cron expression. + /// + /// The cron expression defining the occurrence pattern. + /// The time duration until the next occurrence. + public static TimeSpan GetTimeUntilNextOccurrence(CronExpression cronExpression) + => GetNextOccurrence(cronExpression) - DateTime.UtcNow; + + /// + /// Calculates the time remaining until the next occurrence of the provided cron expression, starting from the specified UTC time. + /// + /// The cron expression used to determine occurrence times. + /// The starting date and time in UTC. + /// The time remaining until the next occurrence as a TimeSpan. + public static TimeSpan GetTimeUntilNextOccurrence(CronExpression cronExpression, DateTime fromUtc) + => GetNextOccurrence(cronExpression, fromUtc) - fromUtc; + + /// + /// Calculates the time remaining until the next occurrence of the cron expression + /// from the given DateTime in UTC timezone. + /// + /// The cron expression representing the schedule. + /// The DateTime from which to start calculating. + /// + /// A TimeSpan representing the time remaining until the next occurrence of the cron expression. + /// + public static TimeSpan GetTimeUntilNextOccurrence(string cronExpression, DateTime fromUtc) + => GetNextOccurrence(cronExpression, fromUtc) - fromUtc; + + /// + /// Checks if the next occurrence of a recurring event specified by the cron expression is due. + /// + /// The cron expression representing the recurring event schedule. + /// + /// True if the next occurrence of the recurring event is due; otherwise, false. + /// + public static bool IsDue(string cronExpression) + => GetTimeUntilNextOccurrence(cronExpression) == TimeSpan.Zero; + + /// + /// Determines if the next occurrence of a specified cron expression is due. + /// + /// The cron expression to evaluate. + /// True if the next occurrence is due; otherwise, false. + public static bool IsDue(CronExpression cronExpression) + => GetTimeUntilNextOccurrence(cronExpression) == TimeSpan.Zero; + + /// + /// Determines whether the given is due at the specified time. + /// + /// The cron expression to evaluate. + /// The reference time in UTC. + /// + /// true if the is due at the specified time; + /// otherwise, false. + /// + public static bool IsDue(CronExpression cronExpression, DateTime fromUtc) + => GetTimeUntilNextOccurrence(cronExpression, fromUtc) == TimeSpan.Zero; + + /// + /// Determines if the specified cron expression is due relative to the provided starting date and time. + /// + /// The cron expression to evaluate. + /// The starting date and time in UTC format. + /// + /// True if the cron expression is due; otherwise, false. + /// + public static bool IsDue(string cronExpression, DateTime fromUtc) + => GetTimeUntilNextOccurrence(cronExpression, fromUtc) == TimeSpan.Zero; + + /// + /// Checks if the provided cron expression is valid. + /// + /// The cron expression to validate. + /// Returns true if the cron expression is valid; otherwise, returns false. + public static bool IsValid(string cronExpression) + => new CronExpression(cronExpression).IsValid; + + /// + /// Provides access to a singleton instance of PredefinedCronExpressions. + /// + public static PredefinedCronExpressions Predefined => PredefinedCronExpressions.Instance; +} \ No newline at end of file diff --git a/Frank.CronJobs.Cron/Frank.CronJobs.Cron.csproj b/Frank.CronJobs.Cron/Frank.CronJobs.Cron.csproj new file mode 100644 index 0000000..0d61f3b --- /dev/null +++ b/Frank.CronJobs.Cron/Frank.CronJobs.Cron.csproj @@ -0,0 +1,8 @@ + + + + More or less a copy of the CronQuery project's Cron parts, as a separate library. This id a dependency of the CronJobs library, but it was separated out to allow it to be used for its excellent Cron parsing, without any nuget dependencies + Cron, CronExpression, CronParser, CronQuery, CronJobs, CronJob, Parser, Expression, Schedule, Scheduling, ScheduleExpression, SchedulingExpression, ScheduleParser, SchedulingParser, ScheduleExpressionParser, SchedulingExpressionParser + + + diff --git a/Frank.CronJobs.Cron/PredefinedCronExpressions.cs b/Frank.CronJobs.Cron/PredefinedCronExpressions.cs new file mode 100644 index 0000000..c08636b --- /dev/null +++ b/Frank.CronJobs.Cron/PredefinedCronExpressions.cs @@ -0,0 +1,143 @@ +namespace Frank.CronJobs.Cron; + +/// +/// Represents a collection of predefined cron expressions. +/// +public class PredefinedCronExpressions +{ + private PredefinedCronExpressions() { } // Prevent instantiation + + /// + /// Gets the singleton instance of the PredefinedCronExpressions class. + /// + /// + /// The singleton instance of the PredefinedCronExpressions class. + /// + public static PredefinedCronExpressions Instance { get; } = new(); + + /// + /// Represents a constant string value that represents every second. + /// + public const string EverySecond = "* * * * * *"; + + /// + /// Represents the cron expression for executing a task every five seconds. + /// + public const string EveryFiveSeconds = "*/5 * * * * *"; + + /// + /// Represents a string constant that specifies a cron expression for every minute. + /// + public const string EveryMinute = "0 * * * * *"; + + /// + /// Represents the cron expression for running a task every five minutes. + /// + public const string EveryFiveMinutes = "0 */5 * * * *"; + + /// + /// Represents a cron expression that runs every hour. + /// + public const string EveryHour = "0 0 * * * *"; + + /// + /// Represents a constant string that defines a cron expression for running a task every day. + /// The expression "0 0 0 * * *" corresponds to the start of every day. + /// + public const string EveryDay = "0 0 0 * * *"; + + /// + /// Represents the CRON expression for executing a recurring task every weekday. + /// + public const string EveryWeekday = "0 0 0 * * 1-5"; + + /// + /// Represents a constant variable that holds the cron expression for every Sunday. + /// + public const string EverySunday = "0 0 0 * * 0"; + + /// + /// Represents a constant that specifies a cron expression for every Monday at midnight. + /// + public const string EveryMonday = "0 0 0 * * 1"; + + /// + /// Represents a schedule expression that occurs every Tuesday. + /// + public const string EveryTuesday = "0 0 0 * * 2"; + + /// + /// Represents the cron expression for every Wednesday. + /// + public const string EveryWednesday = "0 0 0 * * 3"; + + /// + /// Represents a cron expression for running a task on every Thursday. + /// + public const string EveryThursday = "0 0 0 * * 4"; + + /// + /// Represents a constant variable that specifies the schedule for every Friday. + /// + public const string EveryFriday = "0 0 0 * * 5"; + + /// + /// Represents a constant string that represents a cron expression for every Saturday. + /// + public const string EverySaturday = "0 0 0 * * 6"; + + /// + /// Represents a constant string that defines a cron expression for every weekend day. + /// The expression "0 0 0 * * 0,6" means to execute the specified task at 00:00 (midnight) + /// on Sunday (0) and Saturday (6) of every month. + /// + public const string EveryWeekendDay = "0 0 0 * * 0,6"; + + /// + /// Represents the cron expression for scheduling a task to run every day at noon. + /// + public const string EveryDayAtNoon = "0 0 12 * * *"; + + /// + /// Represents a schedule for a recurring event that occurs every month. + /// The schedule is defined by a Cron expression. + /// + public const string EveryMonth = "0 0 0 1 * *"; + + /// + /// Represents a cron expression that triggers an event at + /// midnight on the last day of every month. + /// + public const string EveryLastDayOfMonth = "0 0 0 L * *"; + + /// + /// Represents a cron expression that triggers every year on January 1st at midnight. + /// + public const string EveryYear = "0 0 0 1 1 *"; + + /// + /// The EveryYearOn class contains constants representing specific dates that occur every year. + /// + public static class EveryYearOn + { + /// + /// Represents a schedule pattern for a leap day. + /// + public const string LeapDay = "0 0 0 29 2 *"; + + /// + /// Represents the cron expression for New Year's Day. + /// + public const string NewYearsDay = "0 0 0 1 1 *"; + + /// + /// Holds the cron expression for Christmas Day. + /// + public const string ChristmasDay = "0 0 0 25 12 *"; + + /// + /// Represents the cron expression for Halloween. + /// + public const string Halloween = "0 0 0 31 10 *"; + } +} \ No newline at end of file diff --git a/Frank.CronJobs.Cron/README.md b/Frank.CronJobs.Cron/README.md new file mode 100644 index 0000000..4a2f87f --- /dev/null +++ b/Frank.CronJobs.Cron/README.md @@ -0,0 +1,5 @@ +# Frank.CronJobs.Cron + +This is based on CronQuery, which I am a contributor to. This is a fork of that code that concerns the cron expression part of the code. + +I have added some documentation and I intend to "lock" the code to the current state in this library, because I just want a no frills cron expression parser. \ No newline at end of file diff --git a/Frank.CronJobs/Cron/CronField.cs b/Frank.CronJobs.Cron/internals/CronField.cs similarity index 98% rename from Frank.CronJobs/Cron/CronField.cs rename to Frank.CronJobs.Cron/internals/CronField.cs index 4aa018a..8ce3a53 100644 --- a/Frank.CronJobs/Cron/CronField.cs +++ b/Frank.CronJobs.Cron/internals/CronField.cs @@ -22,9 +22,7 @@ * SOFTWARE. */ -using Frank.CronJobs.Extensions; - -namespace Frank.CronJobs.Cron; +namespace Frank.CronJobs.Cron.internals; internal sealed class CronField { diff --git a/Frank.CronJobs/Cron/CronSyntax.cs b/Frank.CronJobs.Cron/internals/CronSyntax.cs similarity index 89% rename from Frank.CronJobs/Cron/CronSyntax.cs rename to Frank.CronJobs.Cron/internals/CronSyntax.cs index d049175..31fc504 100644 --- a/Frank.CronJobs/Cron/CronSyntax.cs +++ b/Frank.CronJobs.Cron/internals/CronSyntax.cs @@ -24,7 +24,7 @@ using System.Text.RegularExpressions; -namespace Frank.CronJobs.Cron; +namespace Frank.CronJobs.Cron.internals; internal sealed partial class CronSyntax(IEnumerable expressions) { @@ -67,7 +67,6 @@ private bool IsWellFormed() { const string list = $@"^{ListValue}(,({ListValue}))*$"; const string pattern = $@"{Asterisk}|{Dash}|{Hash}|{Slash}|{LAndW}|{list}"; - // var regex = IsWellFormedRegex(); var regex = new Regex(pattern, RegexOptions.Compiled); return expressions.All(exp => regex.IsMatch(exp)); @@ -75,7 +74,4 @@ private bool IsWellFormed() [GeneratedRegex(@"\d")] private static partial Regex AllowedCharactersRegex(); - - [GeneratedRegex(@"^\*$|^\d{1,2}-\d{1,2}$|^\d{1,2}#[1-5]$|^(\*|\d{1,2}(-\d{1,2})?)/\d{1,2}$|^(\d)?L$|L-\d{1,2}|^LW$|^\d{1,2}W$|^\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(-\d{1,2})?/\d{1,2}(,(\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(-\d{1,2})?/\d{1,2}))*$", RegexOptions.Compiled)] - private static partial Regex IsWellFormedRegex(); } diff --git a/Frank.CronJobs/Cron/CronValue.cs b/Frank.CronJobs.Cron/internals/CronValue.cs similarity index 98% rename from Frank.CronJobs/Cron/CronValue.cs rename to Frank.CronJobs.Cron/internals/CronValue.cs index 9de9f3d..e7940ac 100644 --- a/Frank.CronJobs/Cron/CronValue.cs +++ b/Frank.CronJobs.Cron/internals/CronValue.cs @@ -23,9 +23,8 @@ */ using System.Text.RegularExpressions; -using Frank.CronJobs.Extensions; -namespace Frank.CronJobs.Cron; +namespace Frank.CronJobs.Cron.internals; internal sealed class CronValue { diff --git a/Frank.CronJobs/Extensions/DateTimeExtensions.cs b/Frank.CronJobs.Cron/internals/DateTimeExtensions.cs similarity index 98% rename from Frank.CronJobs/Extensions/DateTimeExtensions.cs rename to Frank.CronJobs.Cron/internals/DateTimeExtensions.cs index 2c2b149..3eb3d9b 100644 --- a/Frank.CronJobs/Extensions/DateTimeExtensions.cs +++ b/Frank.CronJobs.Cron/internals/DateTimeExtensions.cs @@ -22,9 +22,7 @@ * SOFTWARE. */ -using Frank.CronJobs.Cron; - -namespace Frank.CronJobs.Extensions; +namespace Frank.CronJobs.Cron.internals; internal static class DateTimeExtensions { diff --git a/Frank.CronJobs/Extensions/Int32Extensions.cs b/Frank.CronJobs.Cron/internals/Int32Extensions.cs similarity index 97% rename from Frank.CronJobs/Extensions/Int32Extensions.cs rename to Frank.CronJobs.Cron/internals/Int32Extensions.cs index 8bbd7d9..db60a62 100644 --- a/Frank.CronJobs/Extensions/Int32Extensions.cs +++ b/Frank.CronJobs.Cron/internals/Int32Extensions.cs @@ -22,7 +22,7 @@ * SOFTWARE. */ -namespace Frank.CronJobs.Extensions; +namespace Frank.CronJobs.Cron.internals; internal static class Int32Extensions { diff --git a/Frank.CronJobs/Cron/TimeUnit.cs b/Frank.CronJobs.Cron/internals/TimeUnit.cs similarity index 98% rename from Frank.CronJobs/Cron/TimeUnit.cs rename to Frank.CronJobs.Cron/internals/TimeUnit.cs index e2fe15b..b27d451 100644 --- a/Frank.CronJobs/Cron/TimeUnit.cs +++ b/Frank.CronJobs.Cron/internals/TimeUnit.cs @@ -22,7 +22,7 @@ * SOFTWARE. */ -namespace Frank.CronJobs.Cron; +namespace Frank.CronJobs.Cron.internals; internal sealed class TimeUnit { diff --git a/Frank.CronJobs.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs b/Frank.CronJobs.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs index 5569760..3afddce 100644 --- a/Frank.CronJobs.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs +++ b/Frank.CronJobs.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs @@ -1,21 +1,15 @@ using Frank.CronJobs.DependencyInjection; using Frank.CronJobs.Jobs; +using Frank.Testing.Logging; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Xunit.Abstractions; namespace Frank.CronJobs.Tests.DependencyInjection; -public class ServiceCollectionExtensionsTests +public class ServiceCollectionExtensionsTests(ITestOutputHelper outputHelper) { - private readonly ITestOutputHelper _outputHelper; - public ServiceCollectionExtensionsTests(ITestOutputHelper outputHelper) - { - _outputHelper = outputHelper; - } - [Fact] public async Task AddCronJob_WithCronExpression_ShouldRunAsync() { @@ -31,7 +25,7 @@ public async Task AddCronJob_WithCronExpression_ShouldRunAsync() }) .ConfigureServices((context, services) => { - services.AddLogging(builder => builder.AddXunit(_outputHelper)); + services.AddTestLogging(outputHelper); services.AddCronJobs(context.Configuration, cronJobBuilder => { cronJobBuilder.AddCronJob(options => @@ -52,7 +46,7 @@ public async Task AddCronJob_WithCronExpression_ShouldRunAsync() await host.RunAsync(cancellationTokenSource.Token); // Assert - _outputHelper.WriteLine("Finished"); + outputHelper.WriteLine("Finished"); } private class MyService : ICronJob diff --git a/Frank.CronJobs.Tests/Frank.CronJobs.Tests.csproj b/Frank.CronJobs.Tests/Frank.CronJobs.Tests.csproj index 0d23d26..b3e9d67 100644 --- a/Frank.CronJobs.Tests/Frank.CronJobs.Tests.csproj +++ b/Frank.CronJobs.Tests/Frank.CronJobs.Tests.csproj @@ -6,14 +6,13 @@ - - + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Frank.CronJobs.sln b/Frank.CronJobs.sln index c8f46e1..f7082e8 100644 --- a/Frank.CronJobs.sln +++ b/Frank.CronJobs.sln @@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.CronJobs.Tests", "Frank.CronJobs.Tests\Frank.CronJobs.Tests.csproj", "{95666C2E-71F1-4F75-AB9E-7EB1BEBD74A9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.CronJobs.Cron", "Frank.CronJobs.Cron\Frank.CronJobs.Cron.csproj", "{B5F0B1C0-693F-4D3A-9426-EF362879437F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,5 +36,9 @@ Global {95666C2E-71F1-4F75-AB9E-7EB1BEBD74A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {95666C2E-71F1-4F75-AB9E-7EB1BEBD74A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {95666C2E-71F1-4F75-AB9E-7EB1BEBD74A9}.Release|Any CPU.Build.0 = Release|Any CPU + {B5F0B1C0-693F-4D3A-9426-EF362879437F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5F0B1C0-693F-4D3A-9426-EF362879437F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5F0B1C0-693F-4D3A-9426-EF362879437F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5F0B1C0-693F-4D3A-9426-EF362879437F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Frank.CronJobs/Cron/CronHelper.cs b/Frank.CronJobs/Cron/CronHelper.cs deleted file mode 100644 index aa41939..0000000 --- a/Frank.CronJobs/Cron/CronHelper.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Frank.CronJobs.Cron; - -public static class CronHelper -{ - public static DateTime GetNextOccurrence(string cronExpression) - => new CronExpression(cronExpression).Next(DateTime.UtcNow); - - public static DateTime GetNextOccurrence(CronExpression cronExpression) - => cronExpression.Next(DateTime.UtcNow); - - public static DateTime GetNextOccurrence(CronExpression cronExpression, DateTime fromUtc) - => cronExpression.Next(fromUtc); - - public static DateTime GetNextOccurrence(string cronExpression, DateTime fromUtc) - => new CronExpression(cronExpression).Next(fromUtc); - - public static TimeSpan GetTimeUntilNextOccurrence(string cronExpression) - => GetNextOccurrence(cronExpression) - DateTime.UtcNow; - - public static TimeSpan GetTimeUntilNextOccurrence(CronExpression cronExpression) - => GetNextOccurrence(cronExpression) - DateTime.UtcNow; - - public static TimeSpan GetTimeUntilNextOccurrence(CronExpression cronExpression, DateTime fromUtc) - => GetNextOccurrence(cronExpression, fromUtc) - fromUtc; - - public static TimeSpan GetTimeUntilNextOccurrence(string cronExpression, DateTime fromUtc) - => GetNextOccurrence(cronExpression, fromUtc) - fromUtc; - - public static bool IsDue(string cronExpression) - => GetTimeUntilNextOccurrence(cronExpression) == TimeSpan.Zero; - - public static bool IsDue(CronExpression cronExpression) - => GetTimeUntilNextOccurrence(cronExpression) == TimeSpan.Zero; - - public static bool IsDue(CronExpression cronExpression, DateTime fromUtc) - => GetTimeUntilNextOccurrence(cronExpression, fromUtc) == TimeSpan.Zero; - - public static bool IsDue(string cronExpression, DateTime fromUtc) - => GetTimeUntilNextOccurrence(cronExpression, fromUtc) == TimeSpan.Zero; - - public static bool IsValid(string cronExpression) - => new CronExpression(cronExpression).IsValid; - - public static PredefinedCronExpressions Predefined => PredefinedCronExpressions.Instance; -} \ No newline at end of file diff --git a/Frank.CronJobs/Cron/PredefinedCronExpressions.cs b/Frank.CronJobs/Cron/PredefinedCronExpressions.cs deleted file mode 100644 index f0a8628..0000000 --- a/Frank.CronJobs/Cron/PredefinedCronExpressions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Frank.CronJobs.Cron; - -public class PredefinedCronExpressions -{ - public static PredefinedCronExpressions Instance { get; } = new PredefinedCronExpressions(); - - public const string EverySecond = "* * * * * *"; - - public const string EveryFiveSeconds = "*/5 * * * * *"; - - public const string EveryMinute = "0 * * * * *"; - - public const string EveryFiveMinutes = "0 */5 * * * *"; - - public const string EveryHour = "0 0 * * * *"; - - public const string EveryDay = "0 0 0 * * *"; - - public const string EveryWeekday = "0 0 0 * * 1-5"; - - public const string EverySunday = "0 0 0 * * 0"; -} \ No newline at end of file diff --git a/Frank.CronJobs/DependencyInjection/CronJobsBuilder.cs b/Frank.CronJobs/DependencyInjection/CronJobsBuilder.cs index eb80b38..ba6e276 100644 --- a/Frank.CronJobs/DependencyInjection/CronJobsBuilder.cs +++ b/Frank.CronJobs/DependencyInjection/CronJobsBuilder.cs @@ -1,4 +1,5 @@ -using Frank.CronJobs.Cron; +using System.Diagnostics; +using Frank.CronJobs.Cron; using Frank.CronJobs.Jobs; using Frank.CronJobs.Options; using Frank.Reflection; @@ -8,8 +9,6 @@ namespace Frank.CronJobs.DependencyInjection; internal sealed class CronJobsBuilder(IServiceCollection services, CronJobRunnerOptions options) : ICronJobsBuilder { - public ICronJobsBuilder AddCronJob() where T : class, ICronJob => AddCronJob("* * * * * *"); - public ICronJobsBuilder AddCronJob(string cron) where T : class, ICronJob => AddCronJob(new CronExpression(cron)); public ICronJobsBuilder AddCronJob(CronExpression cronExpression) where T : class, ICronJob => AddCronJob(jobOptions => @@ -28,7 +27,7 @@ public ICronJobsBuilder AddCronJob(Action configure) where T public ICronJobsBuilder AddCronJob(CronJobOptions jobOptions) where T : class, ICronJob { var serviceName = typeof(T).GetDisplayName(); - var service = new ServiceDescriptor(typeof(ICronJob), serviceName, typeof(T), ServiceLifetime.Singleton); + var service = new ServiceDescriptor(typeof(ICronJob), serviceName, typeof(T), ServiceLifetime.Scoped); jobOptions.Name = serviceName; options.Jobs.Add(jobOptions); @@ -36,6 +35,4 @@ public ICronJobsBuilder AddCronJob(CronJobOptions jobOptions) where T : class return this; } - - } \ No newline at end of file diff --git a/Frank.CronJobs/DependencyInjection/ICronJobsBuilder.cs b/Frank.CronJobs/DependencyInjection/ICronJobsBuilder.cs index fb64ffd..1cae961 100644 --- a/Frank.CronJobs/DependencyInjection/ICronJobsBuilder.cs +++ b/Frank.CronJobs/DependencyInjection/ICronJobsBuilder.cs @@ -4,6 +4,9 @@ namespace Frank.CronJobs.DependencyInjection; +/// +/// Represents a builder for configuring cron jobs. +/// public interface ICronJobsBuilder { /// diff --git a/Frank.CronJobs/DependencyInjection/ServiceCollectionExtensions.cs b/Frank.CronJobs/DependencyInjection/ServiceCollectionExtensions.cs index 11fd86e..295717c 100644 --- a/Frank.CronJobs/DependencyInjection/ServiceCollectionExtensions.cs +++ b/Frank.CronJobs/DependencyInjection/ServiceCollectionExtensions.cs @@ -29,8 +29,17 @@ namespace Frank.CronJobs.DependencyInjection; +/// +/// Provides extension methods for the IServiceCollection class. +/// public static class ServiceCollectionExtensions { + /// + /// Adds cron jobs to the specified service collection using the provided configuration and builder action. + /// + /// The service collection to add the cron jobs to. + /// The configuration containing the cron job options. + /// An action to configure the cron job builder. public static void AddCronJobs(this IServiceCollection services, IConfiguration configuration, Action builderAction) { var options = new CronJobRunnerOptions(); diff --git a/Frank.CronJobs/Frank.CronJobs.csproj b/Frank.CronJobs/Frank.CronJobs.csproj index d87bb50..477febd 100644 --- a/Frank.CronJobs/Frank.CronJobs.csproj +++ b/Frank.CronJobs/Frank.CronJobs.csproj @@ -1,10 +1,19 @@  + + + Frank.CronJobs is a library for running cron jobs in .NET Core applications. + dotnet, cron, job, jobs, cronjob, cronjobs, run, running, async, di, dependency, injection, host, hosting, schedule, scheduled, Frank Haugen, Frank, Haugen, Frank R. Haugen + - + + + + + diff --git a/Frank.CronJobs/Jobs/CronJobRunner.cs b/Frank.CronJobs/Jobs/CronJobRunner.cs index 7a6ad2b..fc6340c 100644 --- a/Frank.CronJobs/Jobs/CronJobRunner.cs +++ b/Frank.CronJobs/Jobs/CronJobRunner.cs @@ -22,6 +22,7 @@ * SOFTWARE. */ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Frank.CronJobs.Cron; using Frank.CronJobs.Options; @@ -55,13 +56,17 @@ private async Task RunAsync(string jobName) { using var scope = _serviceScopeFactory.CreateScope(); var jobInstance = scope.ServiceProvider.GetRequiredKeyedService(jobName); + + var stopwatch = Stopwatch.StartNew(); try { await jobInstance.RunAsync(); + stopwatch.Stop(); } catch (Exception error) { + stopwatch.Stop(); _logger.LogError(error, "Job '{ServiceTypeName}' failed during running", jobName); } finally @@ -71,7 +76,7 @@ private async Task RunAsync(string jobName) if (jobInstance is IAsyncDisposable asyncDisposable) await asyncDisposable.DisposeAsync(); - _logger.LogInformation("Job '{ServiceTypeName}' finished running", jobName); + _logger.LogDebug("Job '{ServiceTypeName}' finished running in {TimeElapsed}", jobName, stopwatch.Elapsed); } } @@ -107,28 +112,28 @@ private void Stop() foreach (var timer in _timers) timer.Dispose(); _timers.Clear(); - _logger.LogInformation("Cron job runner stopped"); + _logger.LogDebug("Cron job runner stopped"); } private void Restart(CronJobRunnerOptions options) { - _logger.LogInformation("Restarting cron job runner"); + _logger.LogWarning("Restarting cron job runner"); _options = options; Stop(); Start(); - _logger.LogInformation("Cron job runner restarted"); + _logger.LogWarning("Cron job runner restarted"); } Task IHostedService.StartAsync(CancellationToken cancellationToken) { - _logger.LogInformation("Starting cron job runner"); + _logger.LogDebug("Starting cron job runner"); Start(); return Task.CompletedTask; } Task IHostedService.StopAsync(CancellationToken cancellationToken) { - _logger.LogInformation("Stopping cron job runner"); + _logger.LogWarning("Stopping cron job runner"); Stop(); return Task.CompletedTask; } diff --git a/Frank.CronJobs/Options/CronJobOptionsCollection.cs b/Frank.CronJobs/Options/CronJobOptionsCollection.cs index 701a623..2e2d2e4 100644 --- a/Frank.CronJobs/Options/CronJobOptionsCollection.cs +++ b/Frank.CronJobs/Options/CronJobOptionsCollection.cs @@ -1,5 +1,8 @@ namespace Frank.CronJobs.Options; +/// +/// Represents a collection of CronJobOptions. +/// public sealed class CronJobOptionsCollection : List { public void Replace(CronJobOptions cronJobOptions) diff --git a/README.md b/README.md index 1f0c728..365835f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,25 @@ # Frank.CronJobs This is based on CronQuery, which I am a contributor to. This is built on that code to extend its functionality in experimental ways + +___ +[![GitHub License](https://img.shields.io/github/license/frankhaugen/Frank.CronJobs)](LICENSE) +[![NuGet](https://img.shields.io/nuget/v/Frank.CronJobs.svg)](https://www.nuget.org/packages/Frank.CronJobs) +[![NuGet](https://img.shields.io/nuget/dt/Frank.CronJobs.svg)](https://www.nuget.org/packages/Frank.CronJobs) + +![GitHub contributors](https://img.shields.io/github/contributors/frankhaugen/Frank.CronJobs) +![GitHub Release Date - Published_At](https://img.shields.io/github/release-date/frankhaugen/Frank.CronJobs) +![GitHub last commit](https://img.shields.io/github/last-commit/frankhaugen/Frank.CronJobs) +![GitHub commit activity](https://img.shields.io/github/commit-activity/m/frankhaugen/Frank.CronJobs) +![GitHub pull requests](https://img.shields.io/github/issues-pr/frankhaugen/Frank.CronJobs) +![GitHub issues](https://img.shields.io/github/issues/frankhaugen/Frank.CronJobs) +![GitHub closed issues](https://img.shields.io/github/issues-closed/frankhaugen/Frank.CronJobs) +___ + +## Usage +```c# +using Frank.CronJobs; + +var cron = new CronJob("* * * * *"); +var next = cron.GetNextOccurrence(DateTime.Now); +``` +