Page 4 of 4

Posted: Fri May 18, 2007 2:02 am
by MySchizoBuddy
Bug in ur code
try these variables

Code: Select all

$event_startDate = '2007-1-3';
$event_startTime = '08:00:00';
$event_endDate = '2007-4-3';
$event_endTime = '17:00:00';

$range_start = strtotime('2007-2-1');
$range_end = strtotime('2007-2-20');

$rrule = "FREQ=WEEKLY;INTERVAL=1;BYDAY=WE";
the result is
2007-01-31 Wrong outside range
2007-02-07 Correct
2007-02-14 Correct

Another example with out of range dates

Code: Select all

$event_startDate = '2007-1-1';
$event_startTime = '08:00:00';
$event_endDate = '2007-4-3';
$event_endTime = '17:00:00';

$range_start = strtotime('2007-2-1');
$range_end = strtotime('2007-2-20');

$rrule = "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,WE,FR";
can u explain how to deal with all day events like national holidays that repeat per year

Posted: Fri May 18, 2007 8:44 am
by inghamn
MySchizoBuddy wrote:If i set a reoccurring event in may that will reoccur every month till december. Now if my users are looking at November. how do i find out which of the numerous reoccurring events, will occur in November.
From the looks of it. I will have to parse all my reoccurring events, blow them up, and save only the ones that occur in November and discard the rest.
Yes, the solution I'm running with is essentially:
Store the Events with RRULE descriptions
Store the Exceptions
Generate the recurrences dynamically as needed.

For displaying events that happen in November I would start by selecting the Events that might have a recurrence in Novemeber. Then foreach over them, and generate the recurrences that happen in November. This is partly a result of the ActiveRecord style ORM I'm using for the database. The Calendar class creates a new EventList using a start and end datetime. Then it loops over those and gets all the recurrences.
Here's the relevent code from my Calendar class:

Code: Select all

public function getEvents($rangeStart=null,$rangeEnd=null)
{
	if ($this->id) { $search['calendar_id'] = $this->id; }
	if (isset($rangeStart))
	{
		if (is_int($rangeStart))
		{
			# $rangeStart is a timestamp
			$search['rangeStart'] = date('Y-m-d',$rangeStart);
			if (isset($rangeEnd)) { $search['rangeEnd'] = date('Y-m-d',$rangeEnd); }
		}
		else
		{
			# rangeStart is a date string
			$search['rangeStart'] = $rangeStart;
			if (isset($rangeEnd)) { $search['rangeEnd'] = $rangeEnd; }
		}
	}

	$eventList = new EventList();
	if (isset($search)) { $eventList->find($search); }
	else { $eventList->find(); }

	return $eventList;
}

public function getEventRecurrenceArray($rangeStart=null,$rangeEnd=null)
{
	$recurrenceArray = array();
	foreach($this->getEvents($rangeStart,$rangeEnd) as $event)
	{
		foreach($event->getRecurrences($rangeStart,$rangeEnd) as $recurrence)
		{
			$r = $this->dateStringToArray($recurrence->getStart());
			$recurrenceArray[$r['year']][$r['mon']][$r['mday']][] = $recurrence;
		}
	}
	return $recurrenceArray;
}
MySchizoBuddy wrote:WebCalendar reoccurring code is pretty thorough, and feature complete.
If u want you can have a look at it. get the 1.1 development version
http://www.k5n.us/webcalendar.php
Quite right. WebCalendar was one of the projects I studied this code from to begin with. I originally really like the idea of splitting the RRULE fields into database fields. That led me to trying my hand at the stored procedure I originally posted. WebCalendar still an excellent resource.

I ended up coming to the conclusion that the recurring event exploding just had to happen in PHP, instead of the database. It ended up being easiest to just store a valid RRULE string and let PHP's fabulous string parsing handle it, instead of trying to read in several string fields from the database. At that point, I started looking at actual iCalendar parsers and ended up using code from the phpicalendar project.

MySchizoBuddy wrote:Bug in ur code
try these variables

Code: Select all

$event_startDate = '2007-1-3';
$event_startTime = '08:00:00';
$event_endDate = '2007-4-3';
$event_endTime = '17:00:00';

$range_start = strtotime('2007-2-1');
$range_end = strtotime('2007-2-20');

$rrule = "FREQ=WEEKLY;INTERVAL=1;BYDAY=WE";
the result is
2007-01-31 Wrong outside range
2007-02-07 Correct
2007-02-14 Correct
Doh! You are correct sir. I've gone back and added one last sanity check before actually adding $next_date_time to $recur_data. Making sure that it falls withing the range asked for.

The bug came from how weekly's were calcuated. The code starts at the start_range given, goes back to that week's Sunday, and thn generates recurrences for the whole week. Hence the need for the sanity check before adding it to $recur_data. My bad.

MySchizoBuddy wrote:can u explain how to deal with all day events like national holidays that repeat per year
True, the code I've got, I've left out YEARLY freq checking. In my project I'm about to add it back in...involving mostly a copy paste from the phpicalendar's code.

In any event, aside from the RRULE parsing, the rest of my Event code deviates from the iCalendar RFC. I allow events to span multiple days, with or without time portions. An all day event is simply an event with no time portion. In the database I've set aside a flag for allDayEvent used when displaying events. The recurring event exploding works just as well without time portions, since strtotime generates timestamps as if the time portion was 00:00:00.

Posted: Fri May 18, 2007 11:28 am
by MySchizoBuddy
Thanks inghamn
once my calendar is done I'll post it here, :)

Posted: Fri May 18, 2007 9:07 pm
by MySchizoBuddy
Just for reference
Pear iCal library is in the works, it fate unknown.
Here is the Reoccurrence file
http://www.akbkhome.com/svn/akpear/File ... rrence.php

PHP iCalendar is now going all OO with v3
http://phpicalendar.cvs.sourceforge.net ... functions/

iCal4J port

Posted: Fri Oct 26, 2007 8:14 am
by inghamn
After months of working on other things, I was forced to deal with a problem with the recurring events in my content manager. Partly a design flaw (I chose to deviate from the iCalendar spec - bad choice) and partly from discovered bugs in the phpicalendar code for recurring event parsing.

Either way, I've had to go back to the recurring event exploding and make sure it's rock solid (I've been infected over past months, you see)

I looked around for an existing PHP library that could handle exploding of recurring events from an rrule.

MySchizoBuddy: I know you've recommended http://www.k5n.us/webcalendar.php, I just couldn't figure out how to rip code fom it to use to generate recurring events (And that probably speaks more to my skills than anything)

phpicalendar doesn't seem to show much progress yet on their version 3 rewrite into OO.
libical seems to be missing code for recurring event parsing

I even looked at Bennu, but it hasn't been touched in years.

If anyone has seen something more, let me know

However, there is a rock solid library out there, just not for PHP. iCal4J - everybody seems to use it in Javaland. And it comes with tests!
The PHP - Java bridge proved really difficult to get going in PHP 5 than I anticipated, so now, for my project, I've been porting code from iCal4J. In this case, I really am only needing the RRule parsing and exploding, so I'm not needing to port the entire library, yet - just one class from it. (And it's got a ton of classes, like all good java stuff)

I'm amost done with the port of Recur.class from iCal4J. Since I've been infected, I went ahead and ported their tests across first. When they all pass using my PHP port, I can let folks know. This was started yesterday, and I should finish up sometime today and see it work in PHP. But I must say, I love PHP and all, but the Java from iCal4J is some really nice work.

Posted: Fri Oct 26, 2007 11:48 am
by ev0l
I think maybe you all are making this too complicated.

strtotime handles human readable times for example

Code: Select all

strtotime("third friday");
Will return the date Fri Nov 9 00:00:00 EST 2007.

You can store the string in the database and just use strtotime passing a date context as the second argument.

Recur class

Posted: Fri Oct 26, 2007 9:24 pm
by inghamn
Ugh - why do these things always end up being harder than it sounds? Anyway, after a lot of hard work (at least it was for me) I've got my test cases passing and all the code from the iCal4J Recur.class has been ported. It looks like it works. I only ported 12 of the original test cases from iCal4J (out of 30 or so), but they're passing. Come monday, maybe I'll port the rest of the original test cases ( I don't feel like crying right now when one of them fails :( )

The file size went from 1100 loc in java to 550 loc in PHP.

Code: Select all

<?php
/**
 * This code is a port of the Recur class from iCal4J
 * available from http://ical4j.sourceforge.net/
 * iCal4J is written by Ben Fortuna and is available
 * under his hown licencse
 *
 * This port is being distributed by me under the GPL
 * @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.txt
 * @author Cliff Ingham <inghamn@bloomington.in.gov>
 */
require_once 'WeekDay.inc';

class Recur
{
	private $frequency;
	private $until;
	private $count;
	private $interval = 1;

	/**
	 * These should be arrays of Integers
	 */
	private $secondList = array();
    private $minuteList = array();
    private $hourList = array();
    private $monthDayList = array();
    private $yearDayList = array();
    private $weekNoList = array();
    private $monthList = array();
    private $setPosList = array();

	/**
	 * This should be an array of WeekDay objects
	 * WeekDays represent SU,MO,TU,WE,TH,FR,SA
	 * Plus they have an offset
	 */
	private $dayList = array();

	private $weekStartDay = 'Sunday';
	private $experimentalValues = array();
	private $calIncField;


	/**
	 * @param String $rrule A valid iCal RRULE string
	 */
	public function __construct($rrule=null)
	{
		if ($rrule)
		{
			$rules = explode(';',$rrule);
			foreach($rules as $rule)
			{
				list($field,$value) = explode('=',$rule);
				switch ($field)
				{
					case 'FREQ': $this->setFrequency($value); break;
					case 'UNTIL': $this->setUntil($value); break;
					case 'COUNT': $this->setCount($value); break;
					case 'INTERVAL': $this->setInterval($value); break;
					case 'BYSECOND': $this->setBySecond($value); break;
					case 'BYMINUTE': $this->setByMinute($value); break;
					case 'BYHOUR': $this->setByHour($value); break;
					case 'BYDAY': $this->setByDay($value); break;
					case 'BYMONTHDAY': $this->setByMonthDay($value); break;
					case 'BYYEARDAY': $this->setByYearDay($value); break;
					case 'BYWEEKNO': $this->setByWeekNo($value); break;
					case 'BYMONTH': $this->setByMonth($value); break;
					case 'BYSETPOS': $this->setBySetPos($value); break;
					case 'WKST': $this->setWeekStartDay($value); break;

					// assume experimental value..
					default : $this->experimentalValues[$field] = $value;
				}
			}
			$this->validateFrequency();
		}
	}


    /**
     * Returns a list of start dates in the specified period represented by this recur. This method includes a base date
     * argument, which indicates the start of the fist occurrence of this recurrence. The base date is used to inject
     * default values to return a set of dates in the correct format. For example, if the search start date (start) is
     * Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, the start dates returned should all be at
     * 9:00AM, and not 12:19PM.
     * @return a list of timestamps represented by this recur instance
     * @param timestamp $periodStart the starting timestamp of the period
     * @param timestamp $periodEnd the ending timestamp of the period
     * @param timestamp $seed the timestamp of this Recurrence's first instance
     * @param int $maxCount limits the number of instances returned. Up to one years
     *       worth extra may be returned. Less than 0 means no limit
     */
	public function getDates($periodStart,$periodEnd=null,$seed=null)
	{
		if (!$periodEnd) { $periodEnd = strtotime('+1 year',$periodStart); }
		if (!$seed) { $seed = $periodStart; }
		if ($this->until && $this->until < $periodEnd) { $periodEnd = $this->until; }

		$dates = array();
		$cal = $seed;

		$invalidCandidateCount = 0;
		while((!$this->count) || ($this->count > count($dates)))
		{
			if ($this->until && $cal > $this->until) { break; }
			if ($cal > $periodEnd) { break; }
			if ($this->count && (count($dates) + $invalidCandidateCount) >= $this->count) { break; }
			
			$candidates = $this->getCandidates($cal);
			foreach($candidates as $candidate)
			{
                // don't count candidates that occur before the seed date..
                if ($candidate >= $seed)
                {
                	if ($candidate < $periodStart || $candidate > $periodEnd) { $invalidCandidateCount++; }
                	elseif ($this->count && (count($dates) + $invalidCandidateCount)>=$this->count) { break; }
                	elseif (!($this->until && $candidate > $this->until)) { $dates[] = $candidate; }
                }
			}
			# We went through all the candidates, and still need more
			$cal = strtotime("+{$this->interval} {$this->calIncField}",$cal);
		}
		return $dates;
	}

    /**
     * Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed.
     * @param date the seed date
     * @return a DateList
     */
	public function getCandidates($date)
	{
		$dates = array($date);
		$dates = $this->getMonthVariants($dates);
		$dates = $this->getWeekNoVariants($dates);
		$dates = $this->getYearDayVariants($dates);
		$dates = $this->getMonthDayVariants($dates);
		$dates = $this->getDayVariants($dates);
		$dates = $this->getHourVariants($dates);
		$dates = $this->getMinuteVariants($dates);
		$dates = $this->getSecondVariants($dates);

		return $dates;
	}

    /**
     * Applies BYMONTH rules specified in this Recur instance to the specified date list. If no BYMONTH rules are
     * specified the date list is returned unmodified.
     * @param array $dates An array of timestamps
     * @return
     */
    private function getMonthVariants(array $dates)
    {
    	if (!count($this->monthList)) { return $dates; }

    	$monthlyDates = array();
    	foreach($dates as $date)
    	{
    		$currentMonth = date('n',$date);
			foreach($this->monthList as $targetMonth)
			{
				if ($targetMonth < $currentMonth) { $targetMonth+=12; }
				$distance = $targetMonth - $currentMonth;
				$monthlyDates[] = strtotime("+$distance months",$date);
			}
    	}
        return $monthlyDates;
    }

    /**
     * Applies BYSECOND rules specified in this Recur instance to the specified date list. If no BYSECOND rules are
     * specified the date list is returned unmodified.
     * @param array $dates an array of timestamps
     */
    private function getSecondVariants(array $dates)
    {
    	if (!count($this->secondList)) { return $dates; }

    	$secondlyDates = array();
    	foreach($dates as $date)
    	{
			$cal = getdate($date);
			foreach($this->secondList as $second)
			{
				$secondlyDates[] = mktime($cal['hours'],$cal['minutes'],$second,$cal['mon'],$cal['mday'],$cal['year']);
			}
    	}
        return $secondlyDates;
    }

    /**
     * Applies BYWEEKNO rules specified in this Recur instance to the specified date list. If no BYWEEKNO rules are
     * specified the date list is returned unmodified.
     * @param array $dates an array of timestamps
     * @return
     */
    private function getWeekNoVariants(array $dates)
    {
    	if (!count($this->weekNoList)) { return $dates; }

    	$weekNoDates = array();
    	foreach($dates as $date)
    	{
    		$cal = getdate($date);
    		$year = $cal['year'];
			$currentWeek = date('W',$date);
			$currentWeekDay = date('N',$date) - 1;
			foreach($this->weekNoList as $targetWeek)
			{
				$y = ($targetWeek < $currentWeek) ? $year+1 : $year;
				$week = $this->getWeek($targetWeek,$y);
				$target = getdate($week[$currentWeekDay]);
				$weekNoDates[] = mktime($cal['hours'],$cal['minutes'],$cal['seconds'],$target['mon'],$target['mday'],$target['year']);
			}
    	}
    	return $weekNoDates;
    }

	/**
	 * Returns an array of timestamps for the given week,year
	 * Returns timestamps in an array Mon-Sun, 0-6
	 * @param $weekNumber
	 * @param $year
	 */
	private function getWeek ($weekNumber, $year)
	{
		// Count from '0104' because January 4th is always in week 1
		// (according to ISO 8601).
		$time = strtotime($year.'0104 +'.($weekNumber - 1).' weeks');
		
		// Get the time of the first day of the week
		$mondayTime = strtotime('-'.(date('w', $time) - 1).' days',$time);
		
		// Get the times of days 0 -> 6
		$dayTimes = array ();
		for ($i = 0; $i < 7; ++$i) { $dayTimes[] = strtotime('+' . $i . ' days', $mondayTime); }
		
		// Return timestamps for mon-sun.
		return $dayTimes;
	}

    /**
     * Applies BYYEARDAY rules specified in this Recur instance to the specified date list. If no BYYEARDAY rules are
     * specified the date list is returned unmodified.
     * @param array $dates An array of timestamps
     */
    private function getYearDayVariants(array $dates)
    {
    	if (!count($this->yearDayList)) { return $dates; }

        $yearDayDates = array();
        foreach($dates as $date)
        {
        	# PHP's year days start counting at 0
        	# iCalendar starts counding at 1
        	$currentYearDay = date('z',$date) + 1;
        	$year = date('Y',$date);
        	foreach($this->yearDayList as $targetYearDay)
        	{
        		if ($targetYearDay < $currentYearDay)
        		{
        			$numDays = date('z',mktime(0,0,0,12,31,$year))+1;
        			$targetYearDay+=$numDays;
        		}
        		$distance = $targetYearDay - $currentYearDay;
        		$yearDayDates[] = strtotime("+$distance days",$date);
        	}

        }
        return $yearDayDates;
    }

    /**
     * Applies BYMONTHDAY rules specified in this Recur instance to the specified date list. If no BYMONTHDAY rules are
     * specified the date list is returned unmodified.
     * @param array $dates an array of timestamps
     */
    private function getMonthDayVariants(array $dates)
    {
    	if (!count($this->monthDayList)) { return $dates; }

    	$monthDayDates = array();
    	foreach($dates as $date)
    	{
    		$cal = getdate($date);
    		foreach($this->monthDayList as $monthDay)
    		{
    			$monthDayDates[] = mktime($cal['hours'],$cal['minutes'],$cal['seconds'],$cal['mon'],$monthDay,$cal['year']);
    		}
    	}
        return $monthDayDates;
    }

    /**
     * Applies BYDAY rules specified in this Recur instance to the specified date list. If no BYDAY rules are specified
     * the date list is returned unmodified.
     * @param array $dates an array of timestamps
     */
    private function getDayVariants(array $dates)
    {
    	if (!count($this->dayList)) { return $dates; }

    	$weekDayDates = array();
    	foreach($dates as $date)
    	{
			foreach($this->dayList as $weekDay)
			{
                // if BYYEARDAY or BYMONTHDAY is specified filter existing
                // list..
                if (count($this->yearDayList) || count($this->monthDayList))
                {
                	$currentDayOfWeek = date('l',$date);
                	if ($weekDay->getDayName() == $currentDayOfWeek)
                	{
                		$weekDayDates[] = $date;
                	}
                }
                else
                {
                	$absDays = $this->getAbsWeekDays($date,$weekDay);
					$weekDayDates = array_merge($weekDayDates,$absDays);
					
                }
			}
    	}
    	
        return $weekDayDates;
    }

    /**
     * Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency
     * specified by this recurrence rule.
     * @param int $date timestamp
     * @param WeekDay $weekDay
     */
    private function getAbsWeekDays($date,WeekDay $weekDay)
    {
    	$cal = $date;
    	$days = array();
    	$calDay = $weekDay->getDayName();
    	
    	if ($this->frequency == 'DAILY')
    	{
    		$current = getdate($cal);
    		if ($current['weekday'] == $calDay) { $days[] = $cal; }
    	}
    	elseif ($this->frequency == 'WEEKLY' || count($this->weekNoList))
    	{
    		# Find the target day in the current week
    		$t = $cal;
    		
    		# Back up to Sunday
    		$current = getdate($t);
    		if ($current['weekday']!='Sunday')
    		{
    			$sunday = getdate(strtotime('-1 Sunday',$cal));
    			$t = mktime($current['hours'],$current['minutes'],$current['seconds'],$sunday['mon'],$sunday['mday'],$sunday['year']);
    		}
    		# Move head to the target day
    		if  ($weekDay->getDayName() != 'Sunday')
    		{
    			$target = getdate(strtotime("+1 {$weekDay->getDayName()}",$t));
    			$t = mktime($current['hours'],$current['minutes'],$current['seconds'],$target['mon'],$target['mday'],$target['year']);
    		}
    		$days[] = $t;
    	}
    	elseif($this->frequency == 'MONTHLY' || count($this->monthList))
    	{
    		# Add all of this weekDay's dates for the current month
    		$currentMonth = date('n',$cal);

    		$t = getdate($cal);
    		$cal = mktime($t['hours'],$t['minutes'],$t['seconds'],$t['mon'],1,$t['year']);
    		if (date('l',$cal) != $weekDay->getDayName())
    		{
    			# If the first day of the month is not valid,
    			# jump ahead to the first valid day
    			$target = getdate(strtotime("+1 {$weekDay->getDayName()}",$cal));
    			$cal = mktime($t['hours'],$t['minutes'],$t['seconds'],$target['mon'],$target['mday'],$target['year']);
    		}
			while(date('n',$cal)==$currentMonth)
    		{
    			$days[] = $cal;
    			$target = getdate(strtotime('+1 week',$cal));
    			$cal = mktime($t['hours'],$t['minutes'],$t['seconds'],$target['mon'],$target['mday'],$target['year']);
    		}
    		
    	}
    	elseif($this->frequency == 'YEARLY')
    	{
			# Add all of this weekDays dates for the current year
			$current = getdate($cal);
			
			# Go to the first day of the year
			$cal = mktime($current['hours'],$current['minutes'],$current['seconds'],1,1,$current['year']);
			if (!date('l',$cal) == $weekDay->getDayName())
			{
				$target = getdate(strtotime("+1 {$weekDay->getDayName()}",$cal));
				$cal = mktime($current['hours'],$current['minutes'],$current['seconds'],$target['mon'],$target['mday'],$target['year']);
			}
			while(date('Y',$cal) == $current['year'])
			{
				$days[] = $cal;
				$cal = strtotime('+1 week',$cal);
			}
    	}
        return $this->getOffsetDates($days, $weekDay->getOffset());
    }

    /**
     * Returns a single-element sublist containing the element of <code>list</code> at <code>offset</code>. Valid
     * offsets are from 1 to the size of the list. If an invalid offset is supplied, all elements from <code>list</code>
     * are added to <code>sublist</code>.
     * @param array $dates an array of timestamps
     * @param int $offset
     */
    private function getOffsetDates(array $dates,$offset)
    {
    	if ($offset == 0) { return $dates; }

    	$offsetDates = array();
    	$size = count($dates);

    	if ($offset < 0 && $offset >= -$size) { $offsetDates[] = $dates[$size + $offset]; }
    	elseif ($offset > 0 && $offset <= $size) { $offsetDates[] = $dates[$offset - 1]; }
    	return $offsetDates;
    }

    /**
     * Applies BYHOUR rules specified in this Recur instance to the specified date list. If no BYHOUR rules are
     * specified the date list is returned unmodified.
     * @param array $dates an array of timestamps
     */
    private function getHourVariants(array $dates)
    {
    	if (!count($this->hourList)) { return $dates; }

    	$hourlyDates = array();
    	foreach($dates as $date)
    	{
    		$cal = getdate($date);
    		foreach($this->hourList as $hour)
    		{
				$hourlyDates[] = mktime($hour,$cal['minutes'],$cal['seconds'],$cal['mon'],$cal['mday'],$cal['year']);
    		}
    	}
        return $hourlyDates;
    }

    /**
     * Applies BYMINUTE rules specified in this Recur instance to the specified date list. If no BYMINUTE rules are
     * specified the date list is returned unmodified.
     * @param array $dates an array of timestamps
     */
    private function getMinuteVariants(array $dates)
    {
    	if (!count($this->minuteList)) { return $dates; }

    	$minutelyDates = array();
    	foreach($dates as $date)
    	{
			$cal = getdate($date);
			foreach($this->minuteList as $minute)
			{
				$minutelyDates[] = mktime($cal['hours'],$minute,$cal['seconds'],$cal['mon'],$cal['mday'],$cal['year']);
			}
    	}
        return $minutelyDates;
    }


    private function validateFrequency()
    {
        if ($this->frequency == null) { throw new Exception('A recurrence rule MUST contain a FREQ rule part.'); }
        switch ($this->getFrequency())
        {
			case 'SECONDLY':	$this->calIncField = 'seconds';	break;
			case 'MINUTELY':	$this->calIncField = 'minutes'; break;
			case 'HOURLY':		$this->calIncField = 'hours';	break;
			case 'DAILY':		$this->calIncField = 'days';	break;
			case 'WEEKLY':		$this->calIncField = 'weeks';	break;
			case 'MONTHLY':		$this->calIncField = 'months';	break;
			case 'YEARLY':		$this->calIncField = 'years';	break;
			default:
				throw new Exception("Invalid FREQ rule part '{$this->frequency}' in recurrence rule");
        }
    }


	public function __toString()
	{
		$b = "FREQ={$this->frequency}";
		if ($this->interval >= 1) { $b.=";INTERVAL={$this->interval}"; }
		if ($this->until != null) { $b.=";UNTIL={$this->until}"; }
		if ($this->count >= 1) { $b.=";COUNT={$this->count}"; }
		if (count($this->getMonthList())) { $b.=";BYMONTH=".implode(',',$this->monthList); }
		if (count($this->getWeekNoList())) { $b.=";BYWEEKNO=".implode(',',$this->weekNoList); }
		if (count($this->getYearDayList())) { $b.=";BYYEARDAY=".implode(',',$this->yearDayList); }
		if (count($this->getMonthDayList())) { $b.=";BYMONTHDAY=".implode(',',$this->monthDayList); }
		if (count($this->getDayList())) { $b.=";BYDAY=".implode(',',$this->dayList); }
		if (count($this->getHourList())) { $b.=";BYHOUR=".implode(',',$this->hourList); }
		if (count($this->getMinuteList())) { $b.=";BYMINUTE=".implode(',',$this->minuteList); }
		if (count($this->getSecondList())) { $b.=";BYSECOND=".implode(',',$this->secondList); }
		if (count($this->getSetPosList())) { $b.=";BYSETPOS=".implode(',',$this->setPosList); }
		return $b;
	}

	/**
	 * Generic Getters
	 */
	public function getDayList() { return $this->dayList; }
	public function getHourList() { return $this->hourList; }
	public function getMinuteList() { return $this->minuteList; }
	public function getMonthDayList() { return $this->monthDayList; }
	public function getMonthList() { return $this->monthList; }
	public function getSecondList() { return $this->secondList; }
	public function getSetPosList() { return $this->setPosList; }
	public function getWeekNoList() { return $this->weekNoList; }
	public function getYearDayList() { return $this->yearDayList; }

	public function getCount() { return $this->count; }
	public function getExperimentalValues() { return $this->experimentalValues; }
	public function getFrequency() { return $this->frequency; }
	public function getInterval() { return $this->interval; }
	public function getUntil() { return $this->until; }

	public function getWeekStartDay() { return $this->weekStartDay; }


	/**
	 * Generic Setters
	 */
	public function setFrequency($string) { $this->frequency = $string; }
	public function setCount($int) { $this->count = (int)$int; }
	public function setInterval($int) { $this->interval = (int)$int; }
	public function setBySecond($string) { foreach(explode(',',$string) as $number) { $this->secondList[] = (int) $number; } }
	public function setByMinute($string) { foreach(explode(',',$string) as $number) { $this->minuteList[] = (int) $number; } }
	public function setByHour($string) { foreach(explode(',',$string) as $number) { $this->hourList[] = (int) $number; } }
	public function setByDay($string) { foreach(explode(',',$string) as $day) { $this->dayList[] = new WeekDay($day); } }
	public function setByMonthDay($string) { foreach(explode(',',$string) as $number) { $this->monthDayList[] = (int) $number; } }
	public function setByYearDay($string) { foreach(explode(',',$string) as $number) { $this->yearDayList[] = (int) $number; } }
	public function setByWeekNo($string) { foreach(explode(',',$string) as $number) { $this->weekNoList[] = (int) $number; } }
	public function setByMonth($string) { foreach(explode(',',$string) as $number) { $this->monthList[] = (int) $number; } }
	public function setBySetPos($string) { foreach(explode(',',$string) as $number) { $this->setPosList[] = (int) $number; } }
	public function setWeekStartDay($string) { $this->weekStartDay = $string; }
	public function setUntil($string)
	{
		$until = $string;
		if ($until && strpos($string,'T'))
		{
			preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})/',$until,$regs);
			$this->until = mktime($regs[4],$regs[5],$regs[6],$regs[2],$regs[3],$regs[1]);
		}
		else
		{
			preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})/',$until,$regs);
			$this->until = mktime(0,0,0,$regs[2],$regs[3],$regs[1]);
		}
	}
}