Hello,

I created an events calendar which allows the user to add events. The events adding allows for daily, weekly, monthly (and yearly) events creation.

Well, everything works GREAT, except for this one little problem that I can't get my head around. when creating a repeating event that repeats on a certain weekday every x week of the month, the events are created, but every once in a while, whenever there's a 5th week in the previous month, the event for the following month ends up one week after it's supposed to.

I'm using strtotime and adding the repeat interval for each loop the event repeats. For example:

Let's say the user creates an event which will repeat every 1st Wednesday of each month for 6 months. The script will loop through for each repeat (6 times) and it is supposed to put the event into the database for each repeat, adding 1 month to the event date. It also (or it's supposed to) find the first day of the week in question each month and add x number of days to that week in order to determine the day of the event. Well, like I said, for some reason it doesn't do right when there are 5-week months.

Here's the actual code with some comments:

elseif($month_repeat_type == 'WDAY') {
                    $month = date('m', strtotime($startdate));
                    $year = date('Y', strtotime($startdate));
                    $month_begin = date('Y-m-d', mktime(0,0,0,$month,1,$year));
                    $rep_week = $month_weeknumber;
                    $rep_day = $month_day;
                    // each loop determines the day to repeat the event and adds to theDB
                    for($i=1;$i<=$occurrences;$i++) {
                         $int = $month_repeat_interval * $i;
                         $int = '+ '.$int.' month';
                         $newdate[$i] = strtotime($int, strtotime($month_begin));
                         $newdate[$i] = strtotime('+ '.$rep_week.' week', $newdate[$i]);

                         $newmonth[$i] = date('m', $newdate[$i]);
                         $newday[$i] = date('d', $newdate[$i]);
                         $newyear[$i] = date('Y', $newdate[$i]);
                         $newdate[$i] = findFirstDay($newmonth[$i], $newday[$i], $newyear[$i]);                                                   
                         $newdate[$i] = strtotime('+ '.$rep_day.' day', $newdate[$i]);
                         $data['event_date'] = date('Y-m-d', $newdate[$i]);
                         echo $data['event_date'].'<br /><br />';
                         //$dba->query_insert(MOD_CAL_EVENTS, $data);
                         $prevdate = $data['event_date'];                         
                    }
               }

Any help would be appreciated; I'm sure it something small, but I don't see it.

Member Avatar for diafol
//GET THESE SENT FROM A FORM and possibly parsed/exploded
$start_year = 2011;
$start_month = 3; // (1 = Jan, etc, so 3 = March)
$num_months = 6;
$repeat_day = 3; //(0=sun,1=mon etc, so weds = 3)
$calendar_info = "this, that and the other"; //event details

$x = 0;
$date_clause = "";
while($x < $num_months){
  $firstday = date("w", mktime(0, 0, 0, $start_month + $x, 1, $start_year));
  if($firstday <= $repeat_day){
     $daypart = $repeat_day - $first_day;
  }else{
     $daypart = 7 + $repeat_day - $first_day;
  }
  $date_event =  date("Y-m-d", mktime(0, 0, 0, $start_month + $x, 1 + $daypart, $start_year));
  $date_clauses[] = "('$date_event','$calendar_info')";
  $x++;
}
$date_clause = implode(",",$dateclauses);
$r = mysql_query("INSERT INTO events (`date`,`event_details`) VALUES $date_clause");

WARNING: off the top of my head. Not tested at all. Probably over-complicated it. This works onthe premise of finding out the first day of the week of the new month. Then just increment on until you reach the day in question. I* think* this will work even if you go beyond the set year, i.e. you can add 6 months to a start month of November and keep the same year and it should organize itself to change to the next year automatically. Pesky things these dates. I'm sure there must be a simple solution for this - perhaps even a two liner!

I posted this yesterday and actually found a quicker solution to my problem. I had originally thought that the function was doing the job properly, but upon further studying, I noticed that it was in fact the problem. What i had to do is check to see if the day fed to the function was within 7 days of the date the function comes up with, and if it is more, subtract 7. I could post the function if anyone would like, but it's pretty straight forward (with the exception of this comparison I had to add).

this problem only occurred when there were 5 week months or months with more than 30 days, and i 'think' it has something to do with an error in the strtotime function (I read about on php.net for php 5.3). The error sounded very similar to my problem, and my fix (comparing start day with calculated future day) seems to have solved it in my situation.

Thanks!

Member Avatar for diafol

JUst do a check - does it work with leap years. Does it help with daylight saving? These issues cropped up time and time again when I tried to increment with days or multipliers.

I cannot resist ardav's challenge for a 2-liner. This is not a 2-liner, but at least it has 2 lines less than ardav's solution (which is a fine start to begin with).
Just add 4 weeks to your start date. If this does not bring you into the next month, add another week.
Code not tested.

//GET THESE SENT FROM A FORM and possibly parsed/exploded
$start_year = 2011;
$start_month = 3; // (1 = Jan, etc, so 3 = March)
$num_months = 6;
$repeat_day = 3; //(0=sun,1=mon etc, so weds = 3)
$calendar_info = "this, that and the other"; //event details


$date = mktime(0,0,0,$start_month, $start_day, start_year );
for ($x = 0; $x < $num_months; $x++)
  $date_clauses[] = sprintf("'%s','%s'", date('Y-m-d', $date), $calendar_info );
  $date += 4 * 7 * 24 * 3600; // increment date by 4 weeks
  while (month($date) == ($start_month + $x) % 12) // if we are still in the same month
    $date += 7 * 24 * 3600; // add another week
  }
  $date_clauses[] = "('$date_event','$calendar_info')";
}
$date_clause = implode(",",$dateclauses);
$r = mysql_query("INSERT INTO events (`date`,`event_details`) VALUES $date_clause");
Member Avatar for diafol

Show off :)

//GET THESE SENT FROM A FORM and possibly parsed/exploded
$start_year = 2011;
$start_month = 3; // (1 = Jan, etc, so 3 = March)
$num_months = 6;
$repeat_day = 3; //(0=sun,1=mon etc, so weds = 3)
$calendar_info = "this, that and the other"; //event details
 

while($x=0;$x < $num_months;$x++;){
  $firstday = date("w", mktime(0, 0, 0, $start_month + $x, 1, $start_year));
  $daypart = ($firstday <= $repeat_day) ? $repeat_day - $first_day : 7 + $repeat_day - $first_day;
  $date_clauses[] =  "('" . date("Y-m-d", mktime(0, 0, 0, $start_month + $x, 1 + $daypart, $start_year) . "','$calendar_info')";
}
$date_clause = implode(",",$date_clauses);
$r = mysql_query("INSERT INTO events (`date`,`event_details`) VALUES $date_clause");

OK, reduced it. I'm not getting into a peeing competition,though. :)
@SM - tried your code as it looked a little more efficient than mine (fixed a few errors with } and the month function), but it gave a weird result on December 2011 (came up twice and skipped Jan 2012), it was perfect for everything else in a 36 month repeat.

Member Avatar for diafol

Ok, my code didn't work either - for instead of while. DOh! first_day vs. firstday.

Anyway, I tested the output with this and it seemed to work:

//GET THESE SENT FROM A FORM and possibly parsed/exploded
$start_year = 2011;
$start_month = 3; // (1 = Jan, etc, so 3 = March)
$num_months = 36;
$repeat_day = 3; //(0=sun,1=mon etc, so weds = 3)
$calendar_info = "this, that and the other"; //event details
 
$output = "";
for($x = 0; $x < $num_months; $x++){
  $first_day = date("w", mktime(0, 0, 0, $start_month + $x, 1, $start_year));
  $daypart = ($first_day <= $repeat_day) ? $repeat_day - $first_day : 7 + $repeat_day - $first_day;
  $output .=  date("l m", mktime(0, 0, 0, $start_month + $x, 1 + $daypart, $start_year)) . "<br />";
}
echo $output;

Did a 72 month loop with SM's solution and it gave a duplicate December and missing January on leap years. I've noticed this before with 'hour multipliers' where you have 'x * 3600'. Can't figure it out though. It also increments the day and month by 1 at the start. Regardless, I still think SM's solution is a more elegant way of thinking with just adding a extra week.

I've obviously got nothing better to do on a Saturday morning!

Thanks guys! I decided to try your code (removed the function all together) and it works just fine. It seems like these events calendar questions always arise when it comes to repeating events. I did a LOT of searching, but your idea seems to work the smoothest!

Final code:

elseif($month_repeat_type == 'WDAY') {
                    $month = date('m', strtotime($startdate));
                    $year = date('Y', strtotime($startdate));
                    $month_begin = date('Y-m-d', mktime(0,0,0,$month,1,$year));
                    $rep_week = $month_weeknumber;
                    $rep_day = $month_day;
                    for($i=1;$i<$occurences;$i++) {
                         $firstday = date('w', mktime(0,0,0, $month + $i, 1, $year));
                         $daypart = ($firstday <= $rep_day) ? $rep_day - $firstday : 7 + $rep_day - $firstday;
                         $data['event_date'] = date('Y-m-d', mktime(0,0,0, $month + $i, 1 + $daypart, $year));
                         $dba->query_insert(MOD_CAL_EVENTS, $data);                         
                    }
               }

I did have to change the for loop to start counting at 1 (the initial event is added to the DB long before since I have several other repeat event options to deal with). And I use a class for the database insert...but works great.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.