At first I suggested using the DateTime class that PHP provides, naively believing that it has some kind of thoughtful API that could be used, It turns out that it is not. Although it has very basic DateTime functionality, it is mostly unusable because for most operations it uses the DateInterval class. Combined, these classes are another masterpiece of poor API design.
The interval should be defined as follows:
An interval in Joda-Time is an interval of time from one millisecond moment to another moment. Both points are fully defined points in the datetime continuum, with a time zone.
In PHP, however, an interval is simply a duration:
A date interval stores a fixed amount of time (in years, months, days, hours, etc.) or a relative time string [for example, “2 days”].
Unfortunately, the PHP definition of DateInterval does not allow you to calculate the intersection / overlap (which is required by the OP), since PHP intervals do not have a specific position in the datetime continuum. So I implemented a (very rudimentary) class that adheres to the definition of a JodaTime interval. It does not pass rigorous testing, but it must do the job:
class ProperDateInterval { private $start = null; private $end = null; public function __construct(DateTime $start, DateTime $end) { $this->start = $start; $this->end = $end; } /** * Does this time interval overlap the specified time interval. */ public function overlaps(ProperDateInterval $other) { $start = $this->getStart()->getTimestamp(); $end = $this->getEnd()->getTimestamp(); $oStart = $other->getStart()->getTimestamp(); $oEnd = $other->getEnd()->getTimestamp(); return $start < $oEnd && $oStart < $end; } /** * Gets the overlap between this interval and another interval. */ public function overlap(ProperDateInterval $other) { if(!$this->overlaps($other)) { // I haven't decided what should happen here yet. // Returning "null" doesn't seem like a good solution. // Maybe ProperDateInterval::EMPTY? throw new Exception("No intersection."); } $start = $this->getStart()->getTimestamp(); $end = $this->getEnd()->getTimestamp(); $oStart = $other->getStart()->getTimestamp(); $oEnd = $other->getEnd()->getTimestamp(); $overlapStart = NULL; $overlapEnd = NULL; if($start === $oStart || $start > $oStart) { $overlapStart = $this->getStart(); } else { $overlapStart = $other->getStart(); } if($end === $oEnd || $end < $oEnd) { $overlapEnd = $this->getEnd(); } else { $overlapEnd = $other->getEnd(); } return new ProperDateInterval($overlapStart, $overlapEnd); } /** * @return long The duration of this interval in seconds. */ public function getDuration() { return $this->getEnd()->getTimestamp() - $this->getStart()->getTimestamp(); } public function getStart() { return $this->start; } public function getEnd() { return $this->end; } }
It can be used like this:
$seasonStart = DateTime::createFromFormat('jM-Y', '01-Apr-2012'); $seasonEnd = DateTime::createFromFormat('jM-Y', '30-Sep-2012'); $userStart = DateTime::createFromFormat('jM-Y', '01-Jan-2012'); $userEnd = DateTime::createFromFormat('jM-Y', '02-Apr-2012'); $i1 = new ProperDateInterval($seasonStart, $seasonEnd); $i2 = new ProperDateInterval($userStart, $userEnd); $overlap = $i1->overlap($i2); var_dump($overlap->getDuration());