Skip to content

Commit

Permalink
"Create new CronJobs.Cron library and update CronJobs"
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
frankhaugen committed Jan 21, 2024
1 parent 6438dba commit f21d12b
Show file tree
Hide file tree
Showing 24 changed files with 377 additions and 120 deletions.
13 changes: 5 additions & 8 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>

<Description>Frank.CronJobs is a library for running cron jobs in .NET Core applications.</Description>
<PackageTags>dotnet, cron, job, jobs, cronjob, cronjobs, run, running, async, di, dependency, injection, host, hosting, schedule, scheduled, Frank Haugen, Frank, Haugen, Frank R. Haugen</PackageTags>


<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>

Expand All @@ -17,9 +14,9 @@

<Authors>Frank R. Haugen</Authors>
<PublisherName>Frank R. Haugen</PublisherName>
<Copyright>Copyright (c) 2023 Frank R. Haugen</Copyright>
<Copyright>Copyright (c) 2024 Frank R. Haugen</Copyright>

<PackageReadmeFile>readme.md</PackageReadmeFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand All @@ -31,8 +28,8 @@

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
<None Include="../readme.md" Pack="true" PackagePath="\"/>
<None Include="../icon.png" Pack="true" PackagePath="\"/>
<None Include="../README.md" Pack="true" PackagePath="\" Condition="!Exists('README.md')"/>
<None Include="../icon.png" Pack="true" PackagePath="\" Condition="!Exists('icon.png')"/>
<InternalsVisibleTo Include="$(AssemblyName).Tests"/>
<InternalsVisibleTo Include="LINQPadQuery"/>
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* SOFTWARE.
*/

using Frank.CronJobs.Extensions;
using Frank.CronJobs.Cron.internals;

namespace Frank.CronJobs.Cron;

Expand All @@ -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();

Expand Down
132 changes: 132 additions & 0 deletions Frank.CronJobs.Cron/CronHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
namespace Frank.CronJobs.Cron;

/// <summary>
/// Provides helper methods to work with cron expressions.
/// </summary>
public static class CronHelper
{
/// <summary>
/// Calculates the next occurrence of a cron expression based on the current UTC time.
/// </summary>
/// <param name="cronExpression">The cron expression string to be evaluated.</param>
/// <returns>A DateTime object representing the next occurrence of the cron expression.</returns>
public static DateTime GetNextOccurrence(string cronExpression)
=> new CronExpression(cronExpression).Next(DateTime.UtcNow);

/// <summary>
/// Returns the next occurrence of a given cron expression.
/// </summary>
/// <param name="cronExpression">The cron expression used to calculate the next occurrence.</param>
/// <returns>The next occurrence as a DateTime object.</returns>
public static DateTime GetNextOccurrence(CronExpression cronExpression)
=> cronExpression.Next(DateTime.UtcNow);

/// <summary>
/// Calculates the next occurrence of a scheduled event based on the given cron expression and starting date/time.
/// </summary>
/// <param name="cronExpression">The cron expression specifying the schedule.</param>
/// <param name="fromUtc">The starting date/time to calculate the next occurrence from.</param>
/// <returns>The next occurrence of the scheduled event as a DateTime object.</returns>
public static DateTime GetNextOccurrence(CronExpression cronExpression, DateTime fromUtc)
=> cronExpression.Next(fromUtc);

/// <summary>
/// Returns the next occurrence of the specified cron expression after the given DateTime.
/// </summary>
/// <param name="cronExpression">The cron expression to evaluate.</param>
/// <param name="fromUtc">The DateTime to start searching for the next occurrence.</param>
/// <returns>The DateTime of the next occurrence based on the specified cron expression and starting DateTime.</returns>
public static DateTime GetNextOccurrence(string cronExpression, DateTime fromUtc)
=> new CronExpression(cronExpression).Next(fromUtc);

/// <summary>
/// Calculates the time difference between the current UTC time and the next occurrence of a cron expression.
/// </summary>
/// <param name="cronExpression">The cron expression specifying the schedule pattern.</param>
/// <returns>The time interval until the next occurrence.</returns>
public static TimeSpan GetTimeUntilNextOccurrence(string cronExpression)
=> GetNextOccurrence(cronExpression) - DateTime.UtcNow;

/// <summary>
/// Returns the time duration until the next occurrence specified by the cron expression.
/// </summary>
/// <param name="cronExpression">The cron expression defining the occurrence pattern.</param>
/// <returns>The time duration until the next occurrence.</returns>
public static TimeSpan GetTimeUntilNextOccurrence(CronExpression cronExpression)
=> GetNextOccurrence(cronExpression) - DateTime.UtcNow;

/// <summary>
/// Calculates the time remaining until the next occurrence of the provided cron expression, starting from the specified UTC time.
/// </summary>
/// <param name="cronExpression">The cron expression used to determine occurrence times.</param>
/// <param name="fromUtc">The starting date and time in UTC.</param>
/// <returns>The time remaining until the next occurrence as a TimeSpan.</returns>
public static TimeSpan GetTimeUntilNextOccurrence(CronExpression cronExpression, DateTime fromUtc)
=> GetNextOccurrence(cronExpression, fromUtc) - fromUtc;

/// <summary>
/// Calculates the time remaining until the next occurrence of the cron expression
/// from the given DateTime in UTC timezone.
/// </summary>
/// <param name="cronExpression">The cron expression representing the schedule.</param>
/// <param name="fromUtc">The DateTime from which to start calculating.</param>
/// <returns>
/// A TimeSpan representing the time remaining until the next occurrence of the cron expression.
/// </returns>
public static TimeSpan GetTimeUntilNextOccurrence(string cronExpression, DateTime fromUtc)
=> GetNextOccurrence(cronExpression, fromUtc) - fromUtc;

/// <summary>
/// Checks if the next occurrence of a recurring event specified by the cron expression is due.
/// </summary>
/// <param name="cronExpression">The cron expression representing the recurring event schedule.</param>
/// <returns>
/// True if the next occurrence of the recurring event is due; otherwise, false.
/// </returns>
public static bool IsDue(string cronExpression)
=> GetTimeUntilNextOccurrence(cronExpression) == TimeSpan.Zero;

/// <summary>
/// Determines if the next occurrence of a specified cron expression is due.
/// </summary>
/// <param name="cronExpression">The cron expression to evaluate.</param>
/// <returns>True if the next occurrence is due; otherwise, false.</returns>
public static bool IsDue(CronExpression cronExpression)
=> GetTimeUntilNextOccurrence(cronExpression) == TimeSpan.Zero;

/// <summary>
/// Determines whether the given <paramref name="cronExpression"/> is due at the specified <paramref name="fromUtc"/> time.
/// </summary>
/// <param name="cronExpression">The cron expression to evaluate.</param>
/// <param name="fromUtc">The reference time in UTC.</param>
/// <returns>
/// <c>true</c> if the <paramref name="cronExpression"/> is due at the specified <paramref name="fromUtc"/> time;
/// otherwise, <c>false</c>.
/// </returns>
public static bool IsDue(CronExpression cronExpression, DateTime fromUtc)
=> GetTimeUntilNextOccurrence(cronExpression, fromUtc) == TimeSpan.Zero;

/// <summary>
/// Determines if the specified cron expression is due relative to the provided starting date and time.
/// </summary>
/// <param name="cronExpression">The cron expression to evaluate.</param>
/// <param name="fromUtc">The starting date and time in UTC format.</param>
/// <returns>
/// True if the cron expression is due; otherwise, false.
/// </returns>
public static bool IsDue(string cronExpression, DateTime fromUtc)
=> GetTimeUntilNextOccurrence(cronExpression, fromUtc) == TimeSpan.Zero;

/// <summary>
/// Checks if the provided cron expression is valid.
/// </summary>
/// <param name="cronExpression">The cron expression to validate.</param>
/// <returns>Returns true if the cron expression is valid; otherwise, returns false.</returns>
public static bool IsValid(string cronExpression)
=> new CronExpression(cronExpression).IsValid;

/// <summary>
/// Provides access to a singleton instance of PredefinedCronExpressions.
/// </summary>
public static PredefinedCronExpressions Predefined => PredefinedCronExpressions.Instance;
}
8 changes: 8 additions & 0 deletions Frank.CronJobs.Cron/Frank.CronJobs.Cron.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>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</Description>
<PackageTags>Cron, CronExpression, CronParser, CronQuery, CronJobs, CronJob, Parser, Expression, Schedule, Scheduling, ScheduleExpression, SchedulingExpression, ScheduleParser, SchedulingParser, ScheduleExpressionParser, SchedulingExpressionParser</PackageTags>
</PropertyGroup>

</Project>
143 changes: 143 additions & 0 deletions Frank.CronJobs.Cron/PredefinedCronExpressions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
namespace Frank.CronJobs.Cron;

/// <summary>
/// Represents a collection of predefined cron expressions.
/// </summary>
public class PredefinedCronExpressions
{
private PredefinedCronExpressions() { } // Prevent instantiation

/// <summary>
/// Gets the singleton instance of the PredefinedCronExpressions class.
/// </summary>
/// <value>
/// The singleton instance of the PredefinedCronExpressions class.
/// </value>
public static PredefinedCronExpressions Instance { get; } = new();

/// <summary>
/// Represents a constant string value that represents every second.
/// </summary>
public const string EverySecond = "* * * * * *";

/// <summary>
/// Represents the cron expression for executing a task every five seconds.
/// </summary>
public const string EveryFiveSeconds = "*/5 * * * * *";

/// <summary>
/// Represents a string constant that specifies a cron expression for every minute.
/// </summary>
public const string EveryMinute = "0 * * * * *";

/// <summary>
/// Represents the cron expression for running a task every five minutes.
/// </summary>
public const string EveryFiveMinutes = "0 */5 * * * *";

/// <summary>
/// Represents a cron expression that runs every hour.
/// </summary>
public const string EveryHour = "0 0 * * * *";

/// <summary>
/// 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.
/// </summary>
public const string EveryDay = "0 0 0 * * *";

/// <summary>
/// Represents the CRON expression for executing a recurring task every weekday.
/// </summary>
public const string EveryWeekday = "0 0 0 * * 1-5";

/// <summary>
/// Represents a constant variable that holds the cron expression for every Sunday.
/// </summary>
public const string EverySunday = "0 0 0 * * 0";

/// <summary>
/// Represents a constant that specifies a cron expression for every Monday at midnight.
/// </summary>
public const string EveryMonday = "0 0 0 * * 1";

/// <summary>
/// Represents a schedule expression that occurs every Tuesday.
/// </summary>
public const string EveryTuesday = "0 0 0 * * 2";

/// <summary>
/// Represents the cron expression for every Wednesday.
/// </summary>
public const string EveryWednesday = "0 0 0 * * 3";

/// <summary>
/// Represents a cron expression for running a task on every Thursday.
/// </summary>
public const string EveryThursday = "0 0 0 * * 4";

/// <summary>
/// Represents a constant variable that specifies the schedule for every Friday.
/// </summary>
public const string EveryFriday = "0 0 0 * * 5";

/// <summary>
/// Represents a constant string that represents a cron expression for every Saturday.
/// </summary>
public const string EverySaturday = "0 0 0 * * 6";

/// <summary>
/// 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.
/// </summary>
public const string EveryWeekendDay = "0 0 0 * * 0,6";

/// <summary>
/// Represents the cron expression for scheduling a task to run every day at noon.
/// </summary>
public const string EveryDayAtNoon = "0 0 12 * * *";

/// <summary>
/// Represents a schedule for a recurring event that occurs every month.
/// The schedule is defined by a Cron expression.
/// </summary>
public const string EveryMonth = "0 0 0 1 * *";

/// <summary>
/// Represents a cron expression that triggers an event at
/// midnight on the last day of every month.
/// </summary>
public const string EveryLastDayOfMonth = "0 0 0 L * *";

/// <summary>
/// Represents a cron expression that triggers every year on January 1st at midnight.
/// </summary>
public const string EveryYear = "0 0 0 1 1 *";

/// <summary>
/// The EveryYearOn class contains constants representing specific dates that occur every year.
/// </summary>
public static class EveryYearOn
{
/// <summary>
/// Represents a schedule pattern for a leap day.
/// </summary>
public const string LeapDay = "0 0 0 29 2 *";

/// <summary>
/// Represents the cron expression for New Year's Day.
/// </summary>
public const string NewYearsDay = "0 0 0 1 1 *";

/// <summary>
/// Holds the cron expression for Christmas Day.
/// </summary>
public const string ChristmasDay = "0 0 0 25 12 *";

/// <summary>
/// Represents the cron expression for Halloween.
/// </summary>
public const string Halloween = "0 0 0 31 10 *";
}
}
5 changes: 5 additions & 0 deletions Frank.CronJobs.Cron/README.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@
* SOFTWARE.
*/

using Frank.CronJobs.Extensions;

namespace Frank.CronJobs.Cron;
namespace Frank.CronJobs.Cron.internals;

internal sealed class CronField
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

using System.Text.RegularExpressions;

namespace Frank.CronJobs.Cron;
namespace Frank.CronJobs.Cron.internals;

internal sealed partial class CronSyntax(IEnumerable<string> expressions)
{
Expand Down Expand Up @@ -67,15 +67,11 @@ 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));
}

[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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Loading

0 comments on commit f21d12b

Please sign in to comment.