简体   繁体   中英

Quartz.net RFC 2445 or RFC 5545 instead of CRON

We have a web server running in .NET which uses Quartz to schedule jobs. The triggers for the jobs are provided in RFC 2445 format, but Quartz uses the CRON format . I would now like to either

  • A: Find a library which can convert my RFC 2445 rule to a CRON Rule
  • B: Rather, give Quartz the RFC rule.

In the latter case, I found some Java libraries but not for .NET.

I also tried writing my own library but I'm stuck with intervals. An RFC2445 rule can define a biweekly (or triweekly or n -weekly) job with

FREQ=WEEKLY;BYDAY=MO;INTERVAL=2

Ie Every other monday. Yet CRON does not seem to have this functionality.

I have a similar requirement and I couldn't find a RFC 5545 compliant library to work with Quartz scheduler and ended up implementing a custom trigger myself following this suggestion

In my case we are using Telerik Scheduler control to populate the RRULE but you could probably do the same with iCal.Net library as well. This is not the full implementation here but it will get you started and the code is UNTESTED .

Another note: "FREQ=WEEKLY;BYDAY=MO;INTERVAL=2" will probably fail if you try to parse it using Telerik RecurrenceRule, since it's missing DTSTART, DTEND etc. This is an example of a recurrence rule string that will not fail: "DTSTART:20210309T050000Z\r\nDTEND:20210309T060000Z\r\nRRULE:FREQ=WEEKLY;BYDAY=TU;INTERVAL=1" .

You need to implement ITrigger interface. A lot of it can be copied from CroneTriggerImpl class and modified.

public interface IRRuleTrigger : ITrigger
{
    string RecurrenceRuleString { get; set; }
} 

Then you need an implementation class inherited from AbstractTrigger

public class MyTriggerImpl: AbstractTrigger, IRRuleTrigger
{
   //implement all members here. Look at CronTriggerImpl class in Quartz.Net source. I'm pasting some of the implementation code but not all.
   //...

    private RecurrenceRule rRule;

    /// <summary>
    /// Gets or sets the RRULE expression string.
    /// </summary>
    /// <value>The expression string.</value>
    public string RecurrenceRuleString
    {
        set
        {
            TimeZoneInfo originalTimeZone = TimeZone;
            var success = RecurrenceRule.TryParse(value, out var parsedRule);
            if(success) rRule = parsedRule ;// RecurrenceRule(value!);
        }
        get => rRule?.ToString();
    }


    /// <summary>
    /// Gets or sets the RRULE expression string.
    /// </summary>
    /// <value>The expression string like RRULE:FREQ=WEEKLY;BYDAY=MO;INTERVAL=2.</value>
    public string RecurrenceRuleString
    {
        set
        {
            TimeZoneInfo originalTimeZone = TimeZone;
            var success = RecurrenceRule.TryParse(value, out var parsedRule);
            if(success) rRule = parsedRule ;// RecurrenceRule(value!);
        }
        get => rRule?.ToString();
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // Computation Functions
    //
    ////////////////////////////////////////////////////////////////////////////

    /// <summary>
    /// Gets the next time to fire after the given time.
    /// </summary>
    /// <param name="afterTime">The time to compute from.</param>
    /// <returns></returns>
    protected DateTimeOffset? GetTimeAfter(DateTimeOffset afterTime)
    {
        return rRule?.HasOccurrences == true ?
            rRule?.Occurrences.Where(o => o > afterTime).Min()
            : null;
    }

    /// <summary>
    /// Returns the time before the given time
    /// that this <see cref="IRRuleTrigger" /> will fire.
    /// </summary>
    /// <param name="date">The date.</param>
    /// <returns></returns>
    protected DateTimeOffset? GetTimeBefore(DateTimeOffset? date)
    {
        return rRule?.HasOccurrences == true ? 
            rRule?.Occurrences.Where(o=> o < date).Max()
            : null;
    }
}

public class RRuleScheduleBuilder : ScheduleBuilder<IRRuleTrigger>
{
    private int misfireInstruction = MisfireInstruction.SmartPolicy;
    private RecurrenceRule recurrenceRule;

    public override IMutableTrigger Build()
    {
        MyTriggerImpl myTriggerImpl = new MyTriggerImpl();


        myTriggerImpl.MisfireInstruction = misfireInstruction;
        myTriggerImpl.RecurrenceRuleString = this.recurrenceRule.ToString();
        return myTriggerImpl;
    }
    /// <summary>
    /// Create a RRuleScheduleBuilder with the given string expression - which
    /// is presumed to be valid expression (and hence only a RuntimeException
    /// will be thrown if it is not).
    /// </summary>
    /// <remarks>
    /// </remarks>
    /// <param name="recurrenceRuleString">the RRule expression to base the schedule on.</param>
    /// <returns>the new RRuleScheduleBuilder</returns>
    public static RRuleScheduleBuilder RecurrenceRuleSchedule(string recurrenceRuleString)
    {
        var success = RecurrenceRule.TryParse(recurrenceRuleString, out var rRule);
        if(!success) throw new ArgumentException($"Recurrence Rule String ({recurrenceRuleString}) is invalid.");
        return new RRuleScheduleBuilder(rRule);
    }

    protected RRuleScheduleBuilder(RecurrenceRule rule)
    {
        this.recurrenceRule = rule ?? throw new ArgumentNullException(nameof(rule), "recurrenceRule cannot be null");
    }
}
/// <summary>
/// Extension methods that attach <see cref="RRuleScheduleBuilder" /> to <see cref="TriggerBuilder" />.
/// </summary>
public static class RRuleScheduleTriggerBuilderExtensions
{
    public static TriggerBuilder WithRRuleSchedule(this TriggerBuilder triggerBuilder, string recurrenceRuleString)
    {
        RRuleScheduleBuilder builder = RRuleScheduleBuilder.RecurrenceRuleSchedule(recurrenceRuleString);
        return triggerBuilder.WithSchedule(builder);
    }

    public static TriggerBuilder WithRRuleSchedule(this TriggerBuilder triggerBuilder, string recurrenceRuleString, Action<RRuleScheduleBuilder> action)
    {
        RRuleScheduleBuilder builder = RRuleScheduleBuilder.RecurrenceRuleSchedule(recurrenceRuleString);
        action(builder);
        return triggerBuilder.WithSchedule(builder);
    }
}

After implementing that, you can create and use your trigger like this:

        // Grab the Scheduler instance from the Factory
        StdSchedulerFactory factory = new StdSchedulerFactory();
        var scheduler = await factory.GetScheduler();
        await scheduler.Start();

        var job = JobBuilder.Create<MyBusinessClassThatImplementsIJobInterface>()
            .WithIdentity("someIdentity", "someGroupName")
            .Build();

        var trigger = (IRRuleTrigger)TriggerBuilder.Create()
            .WithIdentity("someName", "myGroup")
            .WithRRuleSchedule(rule.ToString())
            .Build();

        await scheduler.ScheduleJob(job, trigger);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM