0

I am trying to calculate monthly recurring events based on a calendar date range. However I stumbled upon a problem with February (28 days) calculation when an event falls on the 29th of the month. Hope some expert may give me some pointers as I tried searching around with no help.

I have included the following code for easy to run straight as a script. The January range is working fine which gives me 2 dates which falls on the range:

// JANUARY 2013 Range (Working)
2012-12-29
2013-01-29

but if you uncomment the February range, it starts to output:

2013-01-29 // This is correct
2013-02-28 // This is the date which I want to ignore because its not on the 29th.

And if you uncomment the April range, it starts to output nothing at all. It should output one date which is 2013-04-29. So therefore the problem lies with Feb calculation I suppose.

<?php

// Start Calendar Range

// JANUARY 2013 Range (Working)
$rangeStart = new DateTime( '2012-12-30' );
$rangeEnd   = new DateTime( '2013-02-03' );

// FEBRUARY 2013 Range (Not Working)
//$rangeStart = new DateTime( '2013-01-27' );
//$rangeEnd   = new DateTime( '2013-03-03' );

// APRIL 2013 Range (Not Working)
//$rangeStart = new DateTime( '2013-03-31' );
//$rangeEnd   = new DateTime( '2013-04-05' );

// MAY 2013 Range (Working)
//$rangeStart = new DateTime( '2013-04-28' );
//$rangeEnd   = new DateTime( '2013-06-02' );

// Event date start
$eventStart = new DateTime( '2012-10-29' );
$recurTimes = 1;

// Loop thru the days of month
while( $rangeStart->format('U') <= $rangeEnd->format('U') ) {
    $currView = mktime( 0, 0, 0, $rangeStart->format('m'), $eventStart->format('d'), $rangeStart->format('Y') );
    $interval = round(($currView-$eventStart->format('U')) / 60 / 60 / 24 / 30);

    $monthsAway = $eventStart->format('m')+$interval;
    $recurMonth = $eventStart->format('m')%$recurTimes;

    if( $monthsAway%$recurTimes == $recurMonth ) {
        $nextRecur = getNextRecur( $eventStart->format('U'), $interval );
        echo date( 'Y-m-d', $nextRecur ) . '<br />';
    }
    $rangeStart->modify('+1 month');
}


// function to add 1 month with leap year in consideration
function getNextRecur( $baseTime=null, $months=1 ) {
    if( is_null( $baseTime ) ) $baseTime = time( );
    $xMonths = strtotime( '+' . $months . ' months', $baseTime );
    $before = (int)date( 'm', $baseTime )+12*(int)date( 'Y', $baseTime );
    $after  = (int)date( 'm', $xMonths )+12*(int)date( 'Y', $xMonths );
    if( $after > $months+$before ) {
        $xMonths = strtotime( date('Ym01His', $xMonths) . ' -1 day' );
    }
    return $xMonths;
}
?>
Teena Thomas
  • 5,139
  • 1
  • 13
  • 17
Yuan Fen
  • 153
  • 1
  • 3
  • 10
  • Tested your code it just returned .. 2012-12-29 , 2013-01-29 .. I would to focus on the objective of your code for a moment ... what is the even interval why why do you choose to calculate it with round ???? – Baba Oct 10 '12 at 14:41
  • You need to uncomment the FEBRUARY 2013 range and it will output 2013-01-29 and 2013-02-28. 2013-02-28 is the date which I hope to ignore. Initially I was using ceil but I got hit with the issue with February with 28days as well. So currently only round seems to not giving problem. – Yuan Fen Oct 10 '12 at 14:50
  • I think i get you now .... Correct me if am wrong .. instead of `2013-02-28` it should shown `2013-03-01` right ? – Baba Oct 10 '12 at 14:59
  • hm no, I only want events to show on every month of the 29th. For February case, I want it to skip since it has no 29th? – Yuan Fen Oct 10 '12 at 15:01
  • That is very easy .. but your code makes it complex .... Do you want to output the date for each of the months ? – Baba Oct 10 '12 at 15:02
  • Yes as it is a recurring event. Actually I am coding the backend for a jquery calendar module called FullCalendar (http://arshaw.com/fullcalendar/). Each month there will be a start and end date to parse. So there might be a month for example Dec 29 and Jan 29 falls on the same range if you get what I meant. – Yuan Fen Oct 10 '12 at 15:08
  • I would just leave `$recurTimes` for now ... is that ok – Baba Oct 10 '12 at 15:15
  • Yes ok. Its actually from the database which may varies. – Yuan Fen Oct 10 '12 at 15:16
  • am done .. it gives the rage of the event days ...in a given range starting form the even start date – Baba Oct 10 '12 at 15:32
  • @Yuan I am not sure what that code is trying to achive but it looks wrong. The point of the DateTime API is exactly so you don't have to weird arithmetics. Can you explain what you are trying to do? what is the input and what is the expected output? – Gordon Oct 10 '12 at 16:02
  • Hi Gordon, as I mentioned, I am trying to do recurring event for fullcalendar (http://arshaw.com/fullcalendar/), so I have events start date and event end date to output to the calendar. Seems like its getting complicated for me. – Yuan Fen Oct 10 '12 at 16:45

2 Answers2

0

Perhaps there is a reason for your approch, but in my mind it is a bit round the houses. We all know that all the months are constant in days with Febuary being the odd one. So I would sagest that you look at it from that view.

I have not looked at how "EXACTLY" I would do it, but I would simplify it to just working on Febuary. Perhaps you can do a year check to see if it is a leap year first. Then have your code do what you need it to do with Febuarys date inserted.

Tempus
  • 787
  • 1
  • 8
  • 18
  • Its more complicated than just do a check on February because if Feb is not taken into calculation consideration, like what I've posted in my first post, its affecting the month of April not outputting the event as well. – Yuan Fen Oct 10 '12 at 15:29
  • Well if you are simply un-commenting Feb, Mar, Apr and so on, the last VARIABLE value will over write the earlyer value. How are you keeping them distinct and seporite for culculation? – Tempus Oct 10 '12 at 15:51
  • Because I'm working on http://arshaw.com/fullcalendar/ so when you scroll monthly, the range may starts from for example: Oct 30 to Nov 10 or Nov 28 to Dec 8th. – Yuan Fen Oct 10 '12 at 15:55
0

You can try

Example 1

$rangeStart = new DateTime('2012-12-30');
$rangeEnd = new DateTime('2013-02-03');
$eventStart = new DateTime('2012-10-29');
var_dump(getRange($rangeStart, $rangeEnd, $eventStart));

Output

array
  0 => string '2013-01-29' (length=10)

Example 2

$rangeStart = new DateTime('2012-01-27');
$rangeEnd = new DateTime('2013-03-03');
$eventStart = new DateTime('2012-10-29');
var_dump(getRange($rangeStart, $rangeEnd, $eventStart));

Output

array
  0 => string '2012-10-29' (length=10)
  1 => string '2012-11-29' (length=10)
  2 => string '2012-12-29' (length=10)
  3 => string '2013-01-29' (length=10) 

Function Used

function getRange($rangeStart, $rangeEnd, $eventStart, $fixedDay = 29) {
    $lastStart = clone $rangeStart;
    $rangeStart->setDate($eventStart->format('Y'), $eventStart->format('m'), $eventStart->format('d'));
    $list = array();
    while ( $rangeStart <= $rangeEnd ) {
        if ($rangeStart > $rangeEnd)
            break;
        if ($rangeStart->format('m') == 2 || $rangeStart < $lastStart) {
            $rangeStart->modify('+1 month');
            $rangeStart->setDate($rangeStart->format('Y'), $rangeStart->format('m'), $fixedDay);
            continue;
        }
        $list[] = $rangeStart->format("Y-m-d");
        $rangeStart->modify('+1 month');
        $rangeStart->setDate($rangeStart->format('Y'), $rangeStart->format('m'), $fixedDay);
    }
    return $list;
}
Baba
  • 94,024
  • 28
  • 166
  • 217
  • Thanks Baba. But if you try the April range, it is still not giving any output though. Any idea? – Yuan Fen Oct 10 '12 at 15:39
  • And also for March, it is giving me 2013-01-29 instead of 2013-03-29 – Yuan Fen Oct 10 '12 at 15:40
  • 1. I always skip Feb , Your even if you have your rage .. your event date also affects output .... – Baba Oct 10 '12 at 15:42
  • Sorry I did not include the March range in my first post. The March range is $rangeStart = new DateTime( '2013-02-24' ); $rangeEnd = new DateTime( '2013-03-07' ); – Yuan Fen Oct 10 '12 at 15:46
  • @Yuan Fen ok let me test it .. hold on – Baba Oct 10 '12 at 15:53
  • @Yuan Fen it should return empty date since `2013-02-24` is Feb .. no date on that month is valid and `2013-03-07` is not up to `29th` – Baba Oct 10 '12 at 16:11
  • Oh sorry Baba, I screwed up the range and your function is working correctly now. But may I ask, what if a user creates an event on 30th or 31st? Does your function caters for that? What am I suppose to provide from my database for the parameter $fixedDay=29? – Yuan Fen Oct 10 '12 at 16:16
  • Hi Baba, r u still around? There's a problem I've encountered – Yuan Fen Oct 10 '12 at 16:58
  • If my event has an End date for example: $eventStart = new DateTime( '2012-10-29' ); $eventEnd = new DateTime( '2012-10-30' ); How should I go about it? On the Month of January, it starts from 30th so it seems that it is not including the event. – Yuan Fen Oct 10 '12 at 17:00
  • if `$eventStart` = `'2012-10-29' then it should be valid .. if not every other should return false – Baba Oct 10 '12 at 17:02
  • Yes but I met a problem when a particular month starts from the 30th. The event start date is 29th but end date is on the 30th. Is there an easy way to detect the end date? – Yuan Fen Oct 10 '12 at 17:12
  • Not sure what you mean but it works perfectly : http://codepad.viper-7.com/igkBZW – Baba Oct 10 '12 at 17:16
  • The code i wrote for you does not support `$eventEnd` ??? that was not in question or above code ... – Baba Oct 10 '12 at 17:33
  • Yes I realized that Baba but anyway thanks very much for the help. I also realized since your function skipped Feb entirely, any events I create on Feb (12th of Feb for example) will not be shown lol. I'm working on a project and if you are interested to work this together, do let me know. Nice to work together with like-minded people. :) – Yuan Fen Oct 10 '12 at 17:53
  • @Yuan Fen .. You can easily modify the function to work for Feb 12th .... it ok .. let me know when its getting hot ..... :) – Baba Oct 10 '12 at 17:58