Tuesday, 2 October 2012

Scheduling with Quartz and Mono on OS X

Quartz.Net is a brilliant library for scheduling in .Net and a great addition to the tool kit when developing C# applications on OS X.

At the time of writing the latest version is 2.0.1. Getting this to run on Mono is a little tricky though, for starters it uses a call to a method in the System.TimeZoneInfo that at the time of writing hasn't been implemented in Mono 2.10:


public static DateTimeOffset ConvertTime (DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone){throw new NotImplementedException();}
So we have a few options, trying to implement the missing feature is a little daunting so I took a shortcut and downloaded the previous version of Quartz.net 1.0.3, tweaked my code so that it used the old feature set and discovered that it works!

using System;
using Quartz;
using Quartz.Impl;
namespace Quartz
{
    class MainClass
{
        private static IScheduler _scheduler;
 public static void Main (string[] args)
 {
     ISchedulerFactory schedulerFactory = new StdSchedulerFactory(); 
     _scheduler = schedulerFactory.GetScheduler();
     _scheduler.Start();
     Console.WriteLine("Starting Scheduler");
     AddJob();
 }
public static void AddJob()
 {
     JobDetail job = new JobDetail("job1", "group1", typeof(MyJob));
     Trigger trigger = TriggerUtils.MakeSecondlyTrigger("MyTrigger", 10, 10);
     DateTime ft = _scheduler.ScheduleJob(job, trigger); 
}
}
    internal class MyJob : IMyJob
{
        public void Execute(JobExecutionContext context)
 {
     Console.WriteLine("In MyJob class");
     DoMoreWork();
 }
 public void DoMoreWork()
 {
     Console.WriteLine("Do More Work");
 }
}
internal interface IMyJob : IJob
{
}
}
Here is it running:

3 comments:

  1. What about the following function I just wrote (as of mono 3.0.0 ConvertTime with DateTimeOffset is still not implemented):

    private static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone) {
    var destinationDateTime = TimeZoneInfo.ConvertTimeFromUtc(dateTimeOffset.ToUniversalTime().DateTime, destinationTimeZone);
    return new DateTimeOffset(destinationDateTime, destinationTimeZone.GetUtcOffset(destinationDateTime));
    }

    ReplyDelete
  2. Thanks for that, I'll check it out. What this space!

    ReplyDelete
  3. Sorry I wrote unit tests to this code and it failed miserably at DST transition dates for some time zones. I have completely rewritten the function based on existing mono code for DateTime. I will submit a patch, hopefully it can make it into the next release of Mono. Here is the new code I suggest:

    #region Pending mono compatibility
    public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone) {
    return IsRunningOnMono ? ConvertTimeMono(dateTimeOffset: dateTimeOffset, destinationTimeZone: destinationTimeZone) : TimeZoneInfo.ConvertTime(dateTimeOffset: dateTimeOffset, destinationTimeZone: destinationTimeZone);
    }

    public static DateTimeOffset ConvertTimeMono(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone) {
    if (destinationTimeZone == null) throw new ArgumentNullException("destinationTimeZone");
    var utcDateTime = dateTimeOffset.UtcDateTime;
    TimeZoneInfo.AdjustmentRule rule = GetApplicableRule(timeZoneInfo: destinationTimeZone, dateTime: utcDateTime);

    if (rule != null && destinationTimeZone.IsDaylightSavingTime(utcDateTime)) {
    var offset = destinationTimeZone.BaseUtcOffset + rule.DaylightDelta;
    return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + offset, offset);
    }
    else {
    return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + destinationTimeZone.BaseUtcOffset, destinationTimeZone.BaseUtcOffset);
    }
    }

    private static TimeZoneInfo.AdjustmentRule GetApplicableRule(TimeZoneInfo timeZoneInfo, DateTime dateTime) {
    //Transitions are always in standard time
    DateTime date = dateTime;

    if (dateTime.Kind == DateTimeKind.Local && timeZoneInfo != TimeZoneInfo.Local)
    date = date.ToUniversalTime() + timeZoneInfo.BaseUtcOffset;

    if (dateTime.Kind == DateTimeKind.Utc && timeZoneInfo != TimeZoneInfo.Utc)
    date = date + timeZoneInfo.BaseUtcOffset;

    var adjustmentRules = timeZoneInfo.GetAdjustmentRules();
    if (adjustmentRules != null) {
    foreach (var rule in adjustmentRules) {
    if (rule.DateStart > date.Date)
    return null;
    if (rule.DateEnd < date.Date)
    continue;
    return rule;
    }
    }
    return null;
    }
    #endregion

    ReplyDelete