initial commit

This commit is contained in:
Simon Diesenreiter 2025-04-15 13:14:56 +02:00
commit 216ebeaff5
7 changed files with 219 additions and 0 deletions

12
Demo/Demo.csproj Normal file
View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net48;net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\src\CronTimer.csproj" />
</ItemGroup>
</Project>

22
Demo/Program.cs Normal file
View File

@ -0,0 +1,22 @@
using System;
namespace Demo
{
class Program
{
static void Main()
{
var expression = "0-30/5 * * * * *";
Console.WriteLine(expression);
var timer = new CronTimer(expression, "Asia/Hong_Kong", includingSeconds: true);
timer.OnOccurence += (s, ea) => Console.WriteLine($"{ea.At:T} - {DateTime.Now}");
timer.Start();
while (Console.ReadKey().Key != ConsoleKey.Escape)
{
}
timer.Stop();
}
}
}

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# CronTimer
Simple .net Timer that is based on cron expressions with second accuracy to fire timer events to a very specific schedule.
Regular timers are very useful for tasks that do not really require any precision like polling a service at a rough interval but sometimes there is a need for more precision based on time. There is already a great time schedule expression syntax that originated from [Cron](https://en.wikipedia.org/wiki/Cron). Normally you would likely schedule such jobs via the operating system if they are things like on every Friday at 18:00 run a report but there are schedules that make more sense to have running in process like to not have a certain overhead of launching a whole job process or just because the process is already running. This small library makes it super easy to define such timers on a specific schedule.
## Example
Fire a timer event every 10 minutes from Monday through Friday between 8:00 and 17:00
```c#
var timer = new CronTimer("*/10 08-17 * * 1-5", "Europe/Amsterdam", includingSeconds: false);
timer.OnOccurence += (s, ea) => Console.Out.WriteLineAsync(ea + " - " + DateTime.Now);
timer.Start();
```

6
src/CronEventArgs.cs Normal file
View File

@ -0,0 +1,6 @@
using System;
public class CronTimerEventArgs : EventArgs
{
public DateTime At { get; set; }
}

78
src/CronTimer.cs Normal file
View File

@ -0,0 +1,78 @@
using System;
using System.Threading;
using NCrontab;
public class CronTimer
{
public const string UTC = "Etc/UTC";
static readonly TimeSpan InfiniteTimeSpan = TimeSpan.FromMilliseconds(Timeout.Infinite); // net 3.5
readonly CrontabSchedule schedule;
readonly TimeZoneInfo tzi;
readonly string id;
readonly Timer t;
public string tz { get; }
public string Expression { get; }
public event EventHandler<CronTimerEventArgs> OnOccurence;
public DateTime Next { get; private set; }
public CronTimer(string expression, string tz = UTC, bool includingSeconds = false)
{
Expression = expression;
this.tz = tz;
id = TimeZoneConverter.TZConvert.IanaToWindows(tz);
tzi = TimeZoneInfo.FindSystemTimeZoneById(id);
schedule = CrontabSchedule.Parse(expression, new CrontabSchedule.ParseOptions { IncludingSeconds = includingSeconds });
Next = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzi);
OnOccurence += OnOccurenceScheduleNext;
t = new Timer(s =>
{
var ea = new CronTimerEventArgs
{
At = Next
};
OnOccurence(this, ea);
}, null, InfiniteTimeSpan, InfiniteTimeSpan);
}
void OnOccurenceScheduleNext(object sender, EventArgs e)
{
var delay = CalculateDelay();
//Console.WriteLine($"Next for [{tz} {expression}] in {delay}.");
t.Change(delay, InfiniteTimeSpan);
}
public void Start()
{
var delay = CalculateDelay();
//Console.WriteLine($"Next for [{tz} {expression}] in {delay}.");
t.Change(delay, InfiniteTimeSpan);
}
TimeSpan CalculateDelay()
{
var nowUtc = DateTime.UtcNow;
Next = schedule.GetNextOccurrence(Next);
TimeSpan delay;
if (tz != UTC)
{
var nextUtc = TimeZoneInfo.ConvertTimeToUtc(Next, tzi);
delay = nextUtc - nowUtc;
}
else
{
delay = Next - nowUtc;
}
//Console.WriteLine($"Now: {nowUtc} [utc] {now} [{tz}], Next: {next} [{tz}] {nextUtc} [utc], Delay: {delay}");
if (delay < TimeSpan.Zero) delay = TimeSpan.Zero;
return delay;
}
public void Stop()
{
t.Change(InfiniteTimeSpan, InfiniteTimeSpan);
}
}

49
src/CronTimer.csproj Normal file
View File

@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<LangVersion>latest</LangVersion>
<TargetFrameworks>net461;netstandard2.1</TargetFrameworks>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup>
<!-- AssemblyFileVersionAttribute -->
<FileVersion>2.0.0</FileVersion>
<!-- AssemblyInformationalVersionAttribute -->
<Version>$(FileVersion)</Version>
<!-- AssemblyVersionAttribute -->
<AssemblyVersion>2.0.0.0</AssemblyVersion>
<!-- Nuget -->
<PackageVersion>$(Version)</PackageVersion>
<PackageId>CronTimer</PackageId>
<Company>https://github.com/ramonsmits</Company>
<Authors>ramonsmits</Authors>
<Description>Simple .net Timer that is based on cron expressions with second accuracy to fire timer events to a very specific schedule.</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes></PackageReleaseNotes>
<PackageProjectUrl>https://github.com/ramonsmits/CronTimer/tree/$(PackageVersion)</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<IncludeSource>True</IncludeSource>
<RepositoryUrl>https://github.com/ramonsmits/CronTimer</RepositoryUrl>
<Copyright>Copyright 2022 (c) Ramon Smits</Copyright>
<PackageTags>cron timer</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ncrontab" Version="3.3.1" />
<PackageReference Include="TimeZoneConverter" Version="6.0.1" />
</ItemGroup>
<!--<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions">
<Version>3.1.4</Version>
</PackageReference>
</ItemGroup>-->
</Project>

36
src/CronTimer.sln Normal file
View File

@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30717.126
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CronTimer", "CronTimer.csproj", "{FB64C227-8615-4AE1-94E3-F9F9DF192B72}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CCDD0B34-653C-430C-9B17-5129618F8D7D}"
ProjectSection(SolutionItems) = preProject
..\README.md = ..\README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "..\Demo\Demo.csproj", "{C2638357-1621-4422-8701-B55BFB37ACCF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FB64C227-8615-4AE1-94E3-F9F9DF192B72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB64C227-8615-4AE1-94E3-F9F9DF192B72}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB64C227-8615-4AE1-94E3-F9F9DF192B72}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB64C227-8615-4AE1-94E3-F9F9DF192B72}.Release|Any CPU.Build.0 = Release|Any CPU
{C2638357-1621-4422-8701-B55BFB37ACCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2638357-1621-4422-8701-B55BFB37ACCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2638357-1621-4422-8701-B55BFB37ACCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2638357-1621-4422-8701-B55BFB37ACCF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A639B164-6581-40DF-ADC4-479DAB467CFE}
EndGlobalSection
EndGlobal