SBECK-github / Date-Manip

Other
10 stars 11 forks source link

Question about Date::Manip::Recur::prev() #11

Closed PJ-DSI-DevOps closed 7 years ago

PJ-DSI-DevOps commented 7 years ago

I'm trying to get the first occurence of a recurring event before a given date.

Here is the code that I tried :

$recur = new Date::Manip::Recur;
$recur->parse('0:1*2:2:0:0:0', undef, '28 aug 2017');
($prev, $err) = $recur->prev();

This should get "2nd Tuesday of every month" before the 28 aug 2017, which is the 8th of august.

But this code return the 11th of july, for whatever reason it goes straight to the previous month.

Am I missing something here, is there a way to do what I'm trying to do (get the first occurence before a given date) ?

Thanks.

SBECK-github commented 7 years ago

You're close, but you're a little off in understanding how the base date is used. The useage for the parse method is: (STRING, MODIFIERS, BASE, START, END) so you've supplied STRING and BASE.

The base date is used to define the 0th occurence. Events after that are numbered 1, 2, etc. and events before that are numbered -1, -2, etc.

For a monthly event, the base date is used only to define which month the 0th event will occur in. For a weekly event, the base date specifies which week, for a daily event, which day, etc. So for your monthly event, your base date (Aug 28, 2017) is used to specify the month, which means that the 0th event will be in Aug 2017. The day-of-month (28) portion of the base date is ignored entirely.

So when you create the recurrence, it is pointing at the 0th event which is (based on your base date) the 2nd Tuesday in August or Aug 8, 2017.

The prev method goes back one event, which means the 2nd Tuesday in July, or July 11. So everything is working as expected.

If you need to find the first event (going back from a specific date, you will need to test the date. Because it's difficult to say that the base date will be before or after the 0th event, you can't assume 'prev' will give it to you. With modifiers, you can't even assume that it will be the first one before or the first one after the base date (though without modifiers, you CAN make that assumption).

So, without modifiers, you might want to check:

   ($date1,$err) = $recur->nth(-1);
   ($date2,$err) = $recur->nth(0);
   ($date3,$err) = $recur->nth(1);

and one of those will meet your criteria. For a general solution, you'll need to do a loop:

    date = recur->nth(0)
    if (date < TARGET)
       while (date2 = recur->next) {
          if (date2 > TARGET)
             date is the date you're looking for
          else
             date = date2
    else
       while (date2 = recur->prev) {
          if (date2 < TARGET)
             date2 is the date you're looking for
    end

Assuming I haven't made a mistake in the algorithm, that's the fastest way because you test the minimum number of dates.

For a simpler solution, you could just do:

@date = $recur->dates(START,END) where END is the date you are interested in and START is chosen to be sure that the recurrence will match at least 1 event - in the case of a monthly event, just make START be a couple months before END). Then the last date in the list will be the one you're interested in. Just remember that the START and END dates are inclusive.

PJ-DSI-DevOps commented 7 years ago

Thank you very much for this very detailed explanation.

I went for the simpler solution, and it's working brilliantly.