atifaziz / NCrontab

Crontab for .NET
Apache License 2.0
891 stars 137 forks source link

Supporting a corner case, last day of a month #127

Open yangyud-cn opened 2 months ago

yangyud-cn commented 2 months ago

It would be great if we could expand the cron expression to support one corner case, last day of a month.

https://stackoverflow.com/questions/6139189/cron-job-to-run-on-the-last-day-of-the-month

Also, instead of using "L" for it, an alternative is to use "0" for this special day, such that it works easier with the existing integer valued parser.

atifaziz commented 1 month ago

I'd like to avoid supporting extensions and corner cases if it can be helped unless someone helps to maintain and support those. That said, I think some extensions can be easily supported and built on top of NCrontab without changing the core implementation. I'm definitely planning to ship the feature developed in PR #37 that allows building occurrences out of multiple schedules. If you checkout that branch then something like L in your application could be done like so:

static IEnumerable<DateTime> EvaluateCrontabExpression(string expression, DateTime baseTime)
{
    if (expression.Split(' ', 5, StringSplitOptions.RemoveEmptyEntries)
        is not [var min, var hour, var day, var mon, var dow])
    {
        throw new FormatException("Invalid crontab expression.");
    }

    if (day.IndexOfAny(['L', 'l']) < 0) // short-circuit if no "L"
    {
        return CrontabSchedule.Parse(expression)
                              .GetNextOccurrences(baseTime, DateTime.MaxValue);
    }
    else
    {
        var days = day.Split(',', StringSplitOptions.TrimEntries).ToList();
        _ = days.RemoveAll(d => d is "L" or "l");

        // Schedule without "L"
        var schedule1 = CrontabSchedule.Parse(string.Join(' ', [min, hour, string.Join(",", days), mon, dow]));

        // Schedule with all possible "L" days
        var schedule2 = CrontabSchedule.Parse(string.Join(' ', [min, hour, "28-31", mon, dow]));

        // Prepare to combine the schedules
        var schedules = ImmutableArray.Create(schedule1, schedule2);

        return
            from e in schedules.GetNextOccurrences(baseTime, DateTime.MaxValue,
                                                   (s, dt) => (Schedule: s, Occurrence: dt))
            where e.Schedule == schedule1 // all of first schedule, or...
               || e.Occurrence.AddDays(1).Day == 1 // ...just where next day is 1st of next month
            select e.Occurrence;
    }
}
stevehansen commented 3 weeks ago

@atifaziz do you have an idea when a new version might be expected?

atifaziz commented 55 minutes ago

@atifaziz do you have an idea when a new version might be expected?

@stevehansen If you're asking a general question, please post to the general discussions area.