commit 1ee30fa7040ff0aa6e7530888d1eeed2c7c63061 Author: Kevin Colyar Date: Thu Jan 5 09:05:00 2012 -0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aabe2e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +_Resharper* +bin +obj diff --git a/CronNET/Cron.csproj b/CronNET/Cron.csproj new file mode 100644 index 0000000..ff762de --- /dev/null +++ b/CronNET/Cron.csproj @@ -0,0 +1,49 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {F31D7AF3-FDFA-44F1-9C63-305BAF11D002} + Library + Properties + Cron + Cron + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CronNET/CronDaemon.cs b/CronNET/CronDaemon.cs new file mode 100644 index 0000000..0863448 --- /dev/null +++ b/CronNET/CronDaemon.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Timers; + +namespace Cron +{ + public class CronDaemon + { + private readonly Timer timer = new Timer(60000); + private readonly List cron_jobs = new List(); + + public CronDaemon() + { + timer.Elapsed += timer_elapsed; + } + + public void add_job(CronJob cron_job) + { + cron_jobs.Add(cron_job); + } + + public void start() + { + timer.Start(); + } + + public void stop() + { + timer.Stop(); + + foreach (CronJob job in cron_jobs) + job.abort(); + } + + private void timer_elapsed(object sender, ElapsedEventArgs e) + { + foreach (CronJob job in cron_jobs) + job.execute(DateTime.Now); + } + } +} diff --git a/CronNET/CronJob.cs b/CronNET/CronJob.cs new file mode 100644 index 0000000..17cb2ec --- /dev/null +++ b/CronNET/CronJob.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; + +namespace CronNET +{ + public class CronJob + { + private readonly CronSchedule _cron_schedule = new CronSchedule(); + private readonly ThreadStart _thread_start; + private Thread _thread; + + public CronJob(string schedule, ThreadStart thread_start) + { + _cron_schedule = new CronSchedule(schedule); + _thread_start = thread_start; + _thread = new Thread(thread_start); + } + + public void execute(DateTime date_time) + { + if (!_cron_schedule.is_time(date_time)) + return; + + if (_thread.ThreadState == ThreadState.Running) + return; + + _thread = new Thread(_thread_start); + _thread.Start(); + } + + public void abort() + { + _thread.Abort(); + } + + } +} diff --git a/CronNET/CronSchedule.cs b/CronNET/CronSchedule.cs new file mode 100644 index 0000000..880d839 --- /dev/null +++ b/CronNET/CronSchedule.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace CronNET +{ + public class CronSchedule + { + #region Readonly Class Members + + readonly static Regex divided_regex = new Regex(@"(\*/\d+)"); + readonly static Regex range_regex = new Regex(@"(\d+\-\d+)"); + readonly static Regex wild_regex = new Regex(@"(\*)"); + readonly static Regex list_regex = new Regex(@"(((\d+,)*\d+)+)"); + readonly static Regex validation_regex = new Regex(divided_regex + "|" + range_regex + "|" + wild_regex + "|" + list_regex); + + #endregion + + #region Private Instance Members + + private readonly string _expression; + public List minutes; + public List hours; + public List days_of_month; + public List months; + public List days_of_week; + + #endregion + + #region Public Constructors + + public CronSchedule() + { + } + + public CronSchedule(string expressions) + { + this._expression = expressions; + generate(); + } + + #endregion + + #region Public Methods + + public bool is_valid() + { + return is_valid(this._expression); + } + + public bool is_valid(string expression) + { + MatchCollection matches = validation_regex.Matches(expression); + return matches.Count == 5; + } + + public bool is_time(DateTime date_time) + { + return minutes.Contains(date_time.Minute) && + hours.Contains(date_time.Hour) && + days_of_month.Contains(date_time.Day) && + months.Contains(date_time.Month) && + days_of_week.Contains((int)date_time.DayOfWeek); + } + + public void generate() + { + if(!is_valid()) return; + + MatchCollection matches = validation_regex.Matches(this._expression); + + generate_minutes(matches[0]); + generate_hours(matches[1]); + generate_days_of_month(matches[2]); + generate_months(matches[3]); + generate_days_of_weeks(matches[4]); + } + + public void generate_minutes(Match match) + { + this.minutes = generate_values(match.ToString(), 0, 60); + } + + public void generate_hours(Match match) + { + this.hours = generate_values(match.ToString(), 0, 24); + } + + public void generate_days_of_month(Match match) + { + this.days_of_month = generate_values(match.ToString(), 1, 32); + } + + public void generate_months(Match match) + { + this.months = generate_values(match.ToString(), 1, 13); + } + + public void generate_days_of_weeks(Match match) + { + this.days_of_week = generate_values(match.ToString(), 0, 7); + } + + public List generate_values(string configuration, int start, int max) + { + if (divided_regex.IsMatch(configuration)) return divided_array(configuration, start, max); + if (range_regex.IsMatch(configuration)) return range_array(configuration); + if (wild_regex.IsMatch(configuration)) return wild_array(configuration, start, max); + if (list_regex.IsMatch(configuration)) return list_array(configuration); + + return new List(); + } + + public List divided_array(string configuration, int start, int max) + { + if(!divided_regex.IsMatch(configuration)) + return new List(); + + List ret = new List(); + string[] split = configuration.Split("/".ToCharArray()); + int divisor = int.Parse(split[1]); + + for(int i=start; i < max; ++i) + if(i % divisor == 0) + ret.Add(i); + + return ret; + } + + public List range_array(string configuration) + { + if(!range_regex.IsMatch(configuration)) + return new List(); + + List ret = new List(); + string[] split = configuration.Split("-".ToCharArray()); + int start = int.Parse(split[0]); + int end = int.Parse(split[1]); + + for (int i = start; i <= end; ++i) + ret.Add(i); + + return ret; + } + + public List wild_array(string configuration, int start, int max) + { + if(!wild_regex.IsMatch(configuration)) + return new List(); + + List ret = new List(); + + for (int i = start; i < max; ++i) + ret.Add(i); + + return ret; + } + + public List list_array(string configuration) + { + if(!list_regex.IsMatch(configuration)) + return new List(); + + List ret = new List(); + + string[] split = configuration.Split(",".ToCharArray()); + + foreach(string s in split) + ret.Add(int.Parse(s)); + + return ret; + } + + #endregion + } +} diff --git a/CronNET/Properties/AssemblyInfo.cs b/CronNET/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..46d110f --- /dev/null +++ b/CronNET/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CronNET")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CronNET")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("aac36739-a1a8-433d-88f6-8834bbd6655f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CronNETTests/CronScheduleTests.cs b/CronNETTests/CronScheduleTests.cs new file mode 100644 index 0000000..2ca0184 --- /dev/null +++ b/CronNETTests/CronScheduleTests.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using Cron; +using MbUnit.Framework; + +namespace CronTests +{ + [TestFixture] + public class CronScheduleTests + { + + [Test] + public void is_valid_test() + { + CronSchedule cron_schedule = new CronSchedule(); + Assert.IsTrue(cron_schedule.is_valid("* * * * *")); + Assert.IsTrue(cron_schedule.is_valid("0 * * * *")); + Assert.IsTrue(cron_schedule.is_valid("0,1,2 * * * *")); + Assert.IsTrue(cron_schedule.is_valid("*/2 * * * *")); + Assert.IsTrue(cron_schedule.is_valid("1-4 * * * *")); + Assert.IsTrue(cron_schedule.is_valid("1-55 * * * *")); + Assert.IsTrue(cron_schedule.is_valid("1,10,20 * * * *")); + Assert.IsTrue(cron_schedule.is_valid("* 1,10,20 * * *")); + } + + [Test] + public void divided_array_test() + { + CronSchedule cron_schedule = new CronSchedule(); + List results = cron_schedule.divided_array("*/2", 0, 10); + ArrayAssert.AreEqual(results.ToArray(), new int[]{0, 2, 4, 6, 8 } ); + } + + [Test] + public void range_array_test() + { + CronSchedule cron_schedule = new CronSchedule(); + List results = cron_schedule.range_array("1-10"); + ArrayAssert.AreEqual(results.ToArray(), new int[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } ); + } + [Test] + public void wild_array_test() + { + CronSchedule cron_schedule = new CronSchedule(); + List results = cron_schedule.wild_array("*", 0, 10); + ArrayAssert.AreEqual(results.ToArray(), new int[]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } ); + } + [Test] + public void list_array_test() + { + CronSchedule cron_schedule = new CronSchedule(); + List results = cron_schedule.list_array("1,2,3,4,5,6,7,8,9,10"); + ArrayAssert.AreEqual(results.ToArray(), new int[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } ); + } + + [Test] + public void generate_values_divided_test() + { + CronSchedule cron_schedule = new CronSchedule(); + List results = cron_schedule.generate_values("*/2", 0, 10); + ArrayAssert.AreEqual(results.ToArray(), new int[]{0, 2, 4, 6, 8 } ); + } + + [Test] + public void generate_values_range_test() + { + CronSchedule cron_schedule = new CronSchedule(); + List results = cron_schedule.generate_values("1-10", 0, 10); + ArrayAssert.AreEqual(results.ToArray(), new int[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } ); + } + + [Test] + public void generate_values_wild_test() + { + CronSchedule cron_schedule = new CronSchedule(); + List results = cron_schedule.generate_values("*", 0, 10); + ArrayAssert.AreEqual(results.ToArray(), new int[]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } ); + } + + [Test] + public void generate_values_list_test() + { + CronSchedule cron_schedule = new CronSchedule(); + List results = cron_schedule.generate_values("1,2,3,4,5,6,7,8,9,10", 0, 10); + ArrayAssert.AreEqual(results.ToArray(), new int[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } ); + } + + [Test] + public void generate_minutes_test() + { + CronSchedule cron_schedule = new CronSchedule("1,2,3 * * * *"); + ArrayAssert.AreEqual(cron_schedule.minutes.ToArray(), new int[]{ 1, 2, 3 } ); + } + + [Test] + public void generate_hours_test() + { + CronSchedule cron_schedule = new CronSchedule("* 1,2,3 * * *"); + ArrayAssert.AreEqual(cron_schedule.hours.ToArray(), new int[]{ 1, 2, 3 } ); + } + + [Test] + public void generate_days_of_month_test() + { + CronSchedule cron_schedule = new CronSchedule("* * 1,2,3 * *"); + ArrayAssert.AreEqual(cron_schedule.days_of_month.ToArray(), new int[]{ 1, 2, 3 } ); + } + + [Test] + public void generate_months_test() + { + CronSchedule cron_schedule = new CronSchedule("* * * 1,2,3 *"); + ArrayAssert.AreEqual(cron_schedule.months.ToArray(), new int[]{ 1, 2, 3 } ); + } + + [Test] + public void generate_days_of_weeks() + { + CronSchedule cron_schedule = new CronSchedule("* * * * 1,2,3 " ); + ArrayAssert.AreEqual(cron_schedule.days_of_week.ToArray(), new int[]{ 1, 2, 3 } ); + } + + [Test] + public void is_time_minute_test() + { + CronSchedule cron_schedule = new CronSchedule("0 * * * *"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("8:00 am"))); + Assert.IsFalse(cron_schedule.is_time(DateTime.Parse("8:01 am"))); + + cron_schedule = new CronSchedule("0-10 * * * *"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("8:00 am"))); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("8:03 am"))); + + cron_schedule = new CronSchedule("*/2 * * * *"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("8:00 am"))); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("8:02 am"))); + Assert.IsFalse(cron_schedule.is_time(DateTime.Parse("8:03 am"))); + } + + [Test] + public void is_time_hour_test() + { + CronSchedule cron_schedule = new CronSchedule("* 0 * * *"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("12:00 am"))); + + cron_schedule = new CronSchedule("* 0,12 * * *"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("12:00 am"))); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("12:00 pm"))); + } + + [Test] + public void is_time_day_of_month_test() + { + CronSchedule cron_schedule = new CronSchedule("* * 1 * *"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("10/1/08"))); + } + + [Test] + public void is_time_month_test() + { + CronSchedule cron_schedule = new CronSchedule("* * * 1 *"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("1/1/08"))); + + cron_schedule = new CronSchedule("* * * 12 *"); + Assert.IsFalse(cron_schedule.is_time(DateTime.Parse("1/1/08"))); + + cron_schedule = new CronSchedule("* * * */3 *"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("3/1/08"))); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("6/1/08"))); + } + + [Test] + public void is_time_day_of_week_test() + { + CronSchedule cron_schedule = new CronSchedule("* * * * 0"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("10/12/08"))); + Assert.IsFalse(cron_schedule.is_time(DateTime.Parse("10/13/08"))); + + cron_schedule = new CronSchedule("* * * * */2"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("10/14/08"))); + } + + [Test] + public void is_time_test() + { + CronSchedule cron_schedule = new CronSchedule("0 0 12 10 *"); + Assert.IsTrue(cron_schedule.is_time(DateTime.Parse("12:00:00 am 10/12/08"))); + Assert.IsFalse(cron_schedule.is_time(DateTime.Parse("12:01:00 am 10/12/08"))); + } + } +} diff --git a/CronNETTests/CronTests.csproj b/CronNETTests/CronTests.csproj new file mode 100644 index 0000000..2b3490d --- /dev/null +++ b/CronNETTests/CronTests.csproj @@ -0,0 +1,54 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {6FCFBDF4-ECB7-4FC2-A376-E962B11D487D} + Library + Properties + CronTests + CronTests + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + {F31D7AF3-FDFA-44F1-9C63-305BAF11D002} + Cron + + + + + \ No newline at end of file diff --git a/CronNETTests/Properties/AssemblyInfo.cs b/CronNETTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..dafa720 --- /dev/null +++ b/CronNETTests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CronNETTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CronNETTests")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6a430089-ab18-4d72-9d7a-5a4ee0883153")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..4d89723 --- /dev/null +++ b/Readme.md @@ -0,0 +1,93 @@ +CronNET +--------------------------- + +CronNET is a simple C# library for running tasks based on a cron schedule. + +Cron Schedules +=============== + +CronNET supports most cron scheduling. + +* * * * * +┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ +│ │ │ │ │ +│ │ │ │ └───── day of week (0 - 6) (Sunday=0 ) +│ │ │ └────────── month (1 - 12) +│ │ └─────────────── day of month (1 - 31) +│ └──────────────────── hour (0 - 23) +└───────────────────────── min (0 - 59) + + `* * * * *` Every minute. + `0 * * * *` Top of every hour. + `0,1,2 * * * *` Every hour at minutes 0, 1, and 2. + `*/2 * * * *` Every two minutes. + `1-55 * * * *` Every minute through the 55th minute. + `* 1,10,20 * * *` Every 1st, 10th, and 20th hours. + +Console Example +=============== + +``` c# +using System.Threading; +using CronNET; + +namespace CronNETExample.Console +{ + class Program + { + private static readonly CronDaemon cron_daemon = new CronDaemon(); + + static void Main(string[] args) + { + cron_daemon.add_job(new CronJob("* * * * *", task)); + cron_daemon.start(); + + // Wait and sleep forever. Let the cron daemon run. + while(true) Thread.Sleep(6000); + } + + static void task() + { + Console.WriteLine("Hello, world.") + } + } +} +``` + +Windows Service Example +======================= + +``` c# +using System.Threading; +using CronNET; + +namespace CronNETExample.WindowsService +{ + public partial class Service : ServiceBase + { + private readonly CronDaemon cron_daemon = new CronDaemon(); + + public Service() + { + InitializeComponent(); + } + + protected override void OnStart(string[] args) + { + cron_daemon.add_job(new CronJob("*/2 * * * *", task)); + cron_daemon.start(); + } + + protected override void OnStop() + { + cron_daemon.stop(); + } + + private void task() + { + Console.WriteLine("Hello, world.") + } + } +} +```