<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Stoat - Where? &#187; Temporal Expressions</title>
	<atom:link href="http://jamietalbot.com/tag/temporal-expressions/feed/" rel="self" type="application/rss+xml" />
	<link>http://jamietalbot.com</link>
	<description>Adventures in Engrish</description>
	<lastBuildDate>Thu, 26 Aug 2010 23:02:40 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Modelling Recurring Events in PHP</title>
		<link>http://jamietalbot.com/2010/02/07/modelling-recurring-events-in-php/</link>
		<comments>http://jamietalbot.com/2010/02/07/modelling-recurring-events-in-php/#comments</comments>
		<pubDate>Sun, 07 Feb 2010 14:23:14 +0000</pubDate>
		<dc:creator>Jamie Talbot</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Celsus]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Recurring Events]]></category>
		<category><![CDATA[Temporal Expressions]]></category>

		<guid isPermaLink="false">http://jamietalbot.com/?p=52</guid>
		<description><![CDATA[In a previous article, I described how set operations could be modelled in PHP. With that foundation, we can begin to generate complex date criteria suitable for modelling recurring events. There are a number of different kinds of date condition, which Martin Fowler terms &#8220;Temporal Expressions&#8221;. Typical temporal expressions include &#8220;Last Day in the Month&#8221;,]]></description>
			<content:encoded><![CDATA[<p>In a <a href="http://jamietalbot.com/2010/02/04/set-operations-in-php/">previous article</a>, I described how set operations could be modelled in PHP.  With that foundation, we can begin to generate complex date criteria suitable for modelling recurring events.</p>
<p>There are a number of different kinds of date condition, which Martin Fowler terms &#8220;Temporal Expressions&#8221;.  Typical temporal expressions include &#8220;Last Day in the Month&#8221;, &#8220;Nth Day of the Week&#8221; and &#8220;Repeats Yearly&#8221;.  We can model each of these as a separate class and then combine them as necessary to provide a flexible architecture.</p>
<p>In order to take advantage of the set operations we defined previously, all of the classes must implement the same trivial interface:</p>
<pre class="brush: php;">
interface Celsus_Temporal_Expression_Interface {
	/**
	 * Determines whether the date specified is included in this temporal expression.
	 *
	 * @param string $date
	 */
	public function includes($date);
}
</pre>
<p>For our first example, let&#8217;s model conditions like &#8220;11th day of the month&#8221;.  By using negative numbers, we can also use the same class to model &#8220;11th day from the end of the month&#8221;:</p>
<pre class="brush: php;">
/**
 * Handles scheduling rules like &quot;11th day of the month&quot;.
 */
class Celsus_Temporal_Expression_DayOfMonth implements Celsus_Temporal_Expression_Interface {

	/**
	 * The day of the month we are interested in.  If the day is less than zero,
	 * it is interpreted as being from the end of the month.
	 *
	 * @var int
	 */
	private $_day;

	public function __construct($day) {
		$this-&gt;_day = $day;
	}

	public function includes($date) {
		return $this-&gt;_day &gt; 0 ? $this-&gt;_fromStartOfMonth($date) : $this-&gt;_fromEndOfMonth($date);
	}

	private function _fromStartOfMonth($date) {
		return $this-&gt;_day == date('j', strtotime($date));
	}

	private function _fromEndOfMonth($date) {
		$timestamp = strtotime($date);
		return ((date('t', $timestamp) - date('j', $timestamp)) + 1) == abs($this-&gt;_day);
	}
}
</pre>
<p>Usage is straightforward:</p>
<pre class="brush: php;">
$tenth_of_the_month = new Celsus_Temporal_Expression_DayOfMonth(10);
var_dump($tenth_of_the_month-&gt;includes('2010-01-10')); // True
var_dump($tenth_of_the_month-&gt;includes('2010-01-07')); // False
$three_days_before_end_of_the_month = new Celsus_Temporal_Expression_DayOfMonth(-3);
var_dump($three_days_before_end_of_the_month-&gt;includes('2010-01-10')); // False
var_dump($three_days_before_end_of_the_month-&gt;includes('2010-01-28')); // True
</pre>
<p>A second type of query is of the form &#8220;every 3 months&#8221;.  This is slightly more involved as it needs to also use a specified date as the base from which to start counting:</p>
<pre class="brush: php;">
/**
 * Handles scheduling rules like &quot;every 3 months&quot;
 */
class Celsus_Temporal_Expression_MonthsFromStart implements Celsus_Temporal_Expression_Interface {

	/**
	 * The count specifying the interval of months we are interested in.
	 *
	 * @var int
	 */
	private $_count;

	/**
	 * The start date of the sequence.  The number is stored in ISO
	 * format, i.e 2000-12-31.
	 *
	 * @var string
	 */
	private $_start;

	public function __construct($start, $count) {
		$this-&gt;_start = $start;
		$this-&gt;_count = $count;
	}

	public function includes($date) {
		// Take the specified month, minus the start month, mod the interval.  If it is
		// zero then this date should be included.
		return (0 == ((date('n', strtotime($date)) - date('n', strtotime($this-&gt;_start))) % $this-&gt;_count));
	}

}
</pre>
<p>Again, usage is similar:</p>
<pre class="brush: php;">
$every_two_months = new Celsus_Temporal_Expression_MonthsFromStart('2010-01-01', 2);
var_dump($every_two_months-&gt;includes('2010-03-01')); // True
var_dump($every_two_months-&gt;includes('2010-04-01')); // False
</pre>
<p>Now, making use of our set operations previously defined, we can combine them for more complex rules such as &#8220;the last day of every quarter&#8221;:</p>
<pre class="brush: php;">
$last_day_of_month = new Celsus_Temporal_Expression_DayOfMonth(-1);
$every_three_months = new Celsus_Temporal_Expression_MonthsFromStart('2010-01-01', 3);
$last_day_of_quarter = new Celsus_Set_Operation_Intersection('Celsus_Temporal_Expression_Interface');
$last_day_of_quarter-&gt;addElements(array($every_three_months, $last_day_of_month));
var_dump($last_day_of_quarter-&gt;includes('2010-01-31')); // False;
var_dump($last_day_of_quarter-&gt;includes('2010-06-30')); // True;
</pre>
<p>In general, intersections are going to be the most useful set operation for this kind of use, but are not the only possibility.  If we modify this to use a Union, we can test to see if dates are either the last day of the month, or within a month every 3 months from the start:</p>
<pre class="brush: php;">
$last_day_of_month = new Celsus_Temporal_Expression_DayOfMonth(-1);
$every_three_months = new Celsus_Temporal_Expression_MonthsFromStart('2010-01-01', 3);
$last_of_month_or_every_three_months = new Celsus_Set_Operation_Union('Celsus_Temporal_Expression_Interface');
$last_day_of_quarter-&gt;addElements(array($every_three_months, $last_day_of_month));
var_dump($last_of_month_or_every_three_months-&gt;includes('2010-02-28')); // True;
var_dump($last_of_month_or_every_three_months-&gt;includes('2010-06-05')); // True;
</pre>
<p>A handful of temporal expressions can be downloaded <a href="http://jamietalbot.com/projects/celsus/files/temporal_expressions.tar.gz">here</a>.  Further ones are left as an exercise.</p>
<p>With an appropriate database schema for persistence of rules, such as Apple&#8217;s <a href="http://developer.apple.com/mac/library/DOCUMENTATION/AppleApplications/Reference/SyncServicesSchemaRef/Articles/Calendars.html">iCal</a> reference, this provides a comprehensive method for defining complex date ranges and testing them quickly.</p>
]]></content:encoded>
			<wfw:commentRss>http://jamietalbot.com/2010/02/07/modelling-recurring-events-in-php/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
