recurr icon indicating copy to clipboard operation
recurr copied to clipboard

Add a getNextRecurrence method

Open AdamEsterle opened this issue 8 years ago • 9 comments

I calculated the RecurrenceCollection and used it to find the number of Recurrences $recurrences->count()

and the first and last start dates $recurrences->first()->getStart() $recurrences->last()->getStart()

but I went to calculate what the next recurrence would be and there was no easy solution. I had to do a recalculation with another transform with a different end date.

It would be nice to just be able to call a method and have it return the next recurrence (perhaps by ignoring the current end date?)

AdamEsterle avatar May 20 '16 18:05 AdamEsterle

Can you clarify this request?

simshaun avatar May 20 '16 20:05 simshaun

I guess we're always most interested in the next occurrences, and I also find it hard to get. Eg. if DTSTART is far back in the past, what are the options to get next occurrence. It seems non-efficient to skip all past occurrences and to get new one, and even you can hit the array virtual limit. Or maybe I'm doing something wrong?

Thanks

rukavina avatar May 27 '16 08:05 rukavina

As part of the refactoring I'll be doing for #94, I'll keep this issue in mind.

I'm thinking that part of the public API will be $occurrenceGenerator->getNextRecurrence($recurrence), which should solve this issue.

simshaun avatar Aug 11 '16 20:08 simshaun

I'm not sure if that's what @rukavina wanted to say, but trying to find the next occurence, from the present moment is what I think would be really great.

Like he said, if the rule had a DTSTART far away in the past, and you just want to know when the next recurrence will happen, it's kind of counter productive to generate all of the recurrences...

jpmurray avatar Aug 17 '16 20:08 jpmurray

Like he said, if the rule had a DTSTART far away in the past, and you just want to know when the next recurrence will happen, it's kind of counter productive to generate all of the recurrences...

I'm not sure this is possible to do. You typically need some sort of seed date to start from. What we've don't is use redis to cache seed dates from previous expansions so we can pick a valid one to start from. The only tricky part of that is if there is a COUNT you have to factor that in too. You have to know which in the series your seed date is.

brianmuse avatar Aug 18 '16 17:08 brianmuse

@AdamEsterle using @simshaun's built in array transformer with a virtual limit worked for me:

    use Carbon\Carbon;
    use Recurr\Rule;
    use Recurr\Transformer\ArrayTransformer;
    use Recurr\Transformer\ArrayTransformerConfig;
    use Recurr\Transformer\Constraint\AfterConstraint;

    $rule = 'FREQ=MONTHLY;COUNT=12';

    $rrule = new Rule(
        $rule,
        Carbon::now(),
        null
    );

    $next_run_at = self::nextOccurrence($rule, $rrule, Carbon::now(), $this->count);
    ...

    public static function nextOccurrence($ruleText, Rule $rrule, Carbon $lastRun = null, $count = 0)
    {
        $maxCount = $rrule->getCount();

        if ($maxCount !== null && $count >= $maxCount)
        {
            return null;
        }

        $constraint = null;
        $arrayTransformer = new ArrayTransformer();
        $transformerConfig = new ArrayTransformerConfig();
        $transformerConfig->enableLastDayOfMonthFix();
        $transformerConfig->setVirtualLimit(2);
        $arrayTransformer->setConfig($transformerConfig);

        // if there is a last run supplied, meaning we are looking
        // for the next date, not the first date,
        // we will be adding an after contstraint
        if ($lastRun !== null)
        {
            // if there was a last run, and the 
            // original ruleText just happens
            // to contain a DTSTART, we *MUST* set the rrule object 
            // to use now's DTSTART instead... otherwise, the 
            // virtual limit will be surpassed and our result will be null
            if (str_contains($ruleText, 'DTSTART=')) {
                $rrule->setStartDate($lastRun);
            }

            $constraint = new AfterConstraint($lastRun, false);
        }

        $occurrences = $arrayTransformer->transform($rrule, $constraint)->map(function($occurrence) {
            return Carbon::instance($occurrence->getStart());
        });

        if (!$occurrences->isEmpty())
        {
            return $occurrences->first();
        }
        else
        {
            return null;
        }
    }

one weird thing is you have to set a virtual limit of 2, not one. you can ignore my $count logic unless you need it.

Also, the virtual limit will cause your results of nextOccurrence() to be null if you provide a DTSTART too far in the past. That's why there is a if (str_contains($ruleText, 'DTSTART=')) { block. str_contains is a laravel helper method.

What happens for me is I have a rule like: FREQ=MONTHLY;DTSTART=20150701T000000Z; And when I go to calculate the next date, it's null because the virtual limit of 2 caused the actual next run at date to not be found. That's why I have to reset the rule Text's start date to the last run at date. This works for me, because my cron calls this every minute... If your cron went every year, then you couldn't use the last run date, you'd need to use Carbon::now().

His library uses a big array for all the generated recurrences - so I'm essentially tricking it into only making two - using only one also breaks something which I forget now.

EDITED added case for resetting dtstart

lasergoat avatar Nov 23 '16 02:11 lasergoat

I see this request is five years old. Is there really no way to calculate the next occurrence of a date/time interval? It seems that would be the most critical thing to be able to do with a date/time interval.

benjaminkohl avatar Apr 03 '21 15:04 benjaminkohl

The lib was a port of rrule.js and it wasn't designed with that in mind at the time.

I started a rewrite based on an iterator design, but haven't finished it; It's not a priority for me at this time.

simshaun avatar Apr 05 '21 17:04 simshaun

Okay, thank you. I'll just leave a note here for anyone that is curious. For anyone that is looking to calculate occurrences, we found this package: https://github.com/rlanvin/php-rrule

We are still using this recurr package to write the rules then the rrule package has classes that provide methods for calculating occurrences based on the rule string.

benjaminkohl avatar Apr 05 '21 18:04 benjaminkohl