DateRange.class.php

Go to the documentation of this file.
00001 <?php
00002 /***************************************************************************
00003  *   Copyright (C) 2004-2007 by Anton E. Lebedevich                        *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU Lesser General Public License as        *
00007  *   published by the Free Software Foundation; either version 3 of the    *
00008  *   License, or (at your option) any later version.                       *
00009  *                                                                         *
00010  ***************************************************************************/
00011 
00020     class DateRange implements Stringable, SingleRange
00021     {
00022         private $start  = null;
00023         private $end    = null;
00024         
00025         private $dayStartStamp  = null;
00026         private $dayEndStamp    = null;
00027         
00031         public static function create($start = null, $end = null)
00032         {
00033             return new self($start, $end);
00034         }
00035         
00036         public function __construct($start = null, $end = null)
00037         {
00038             if ($start)
00039                 $this->setStart($start);
00040             
00041             if ($end)
00042                 $this->setEnd($end);
00043         }
00044         
00045         public function __clone()
00046         {
00047             if ($this->start)
00048                 $this->start = clone $this->start;
00049             
00050             if ($this->end)
00051                 $this->end = clone $this->end;
00052         }
00053         
00058         public function setStart(/* Date */ $start)
00059         {
00060             $this->checkType($start);
00061             
00062             if ($this->end && $this->end->toStamp() < $start->toStamp())
00063                 throw new WrongArgumentException(
00064                     'start must be lower than end'
00065                 );
00066             
00067             $this->start = $start;
00068             $this->dayStartStamp = null;
00069             
00070             return $this;
00071         }
00072         
00076         public function safeSetStart(/* Date */ $start)
00077         {
00078             if (
00079                 !$this->getEnd()
00080                 || Timestamp::compare(
00081                     $start, $this->getEnd()
00082                 ) < 0
00083             )
00084                 $this->setStart($start);
00085             
00086             elseif ($this->getEnd())
00087                 $this->setStart($this->getEnd());
00088             
00089             return $this;
00090         }
00091         
00095         public function safeSetEnd(/* Date */ $end)
00096         {
00097             if (
00098                 !$this->getStart()
00099                 || Timestamp::compare(
00100                     $end, $this->getStart()
00101                 ) > 0
00102             )
00103                 $this->setEnd($end);
00104             
00105             elseif ($this->getStart())
00106                 $this->setEnd($this->getStart());
00107             
00108             return $this;
00109         }
00110         
00115         public function setEnd(/* Date */ $end)
00116         {
00117             $this->checkType($end);
00118             
00119             if ($this->start && $this->start->toStamp() > $end->toStamp())
00120                 throw new WrongArgumentException(
00121                     'end must be higher than start'
00122                 );
00123             
00124             $this->end = $end;
00125             $this->dayEndStamp = null;
00126             return $this;
00127         }
00128         
00132         public function lazySet($start = null, $end = null)
00133         {
00134             if ($start)
00135                 $this->checkType($start);
00136             
00137             if ($end)
00138                 $this->checkType($end);
00139             
00140             if ($start && $end) {
00141                 if ($start->toStamp() >= $end->toStamp())
00142                     $this->setEnd($start)->setStart($end);
00143                 else
00144                     $this->setStart($start)->setEnd($end);
00145             } elseif ($start)
00146                 $this->setStart($start);
00147             elseif ($end)
00148                 $this->setEnd($end);
00149             
00150             return $this;
00151         }
00152         
00156         public function dropStart()
00157         {
00158             $this->start = null;
00159             $this->dayStartStamp = null;
00160             return $this;
00161         }
00162         
00166         public function dropEnd()
00167         {
00168             $this->end = null;
00169             $this->dayEndStamp = null;
00170             return $this;
00171         }
00172         
00173         public function isEmpty()
00174         {
00175             return
00176                 ($this->start === null)
00177                 && ($this->end === null);
00178         }
00179         
00183         public function getStart()
00184         {
00185             return $this->start;
00186         }
00187         
00191         public function getEnd()
00192         {
00193             return $this->end;
00194         }
00195         
00196         public function toDateString(
00197             $internalDelimiter = '-',
00198             $dateDelimiter = ' - '
00199         )
00200         {
00201             if ($this->start && $this->end)
00202                 return
00203                     "{$this->start->toDate($internalDelimiter)}"
00204                     .$dateDelimiter
00205                     ."{$this->end->toDate($internalDelimiter)}";
00206             elseif ($this->start)
00207                 return $this->start->toDate($internalDelimiter);
00208             elseif ($this->end)
00209                 return $this->end->toDate($internalDelimiter);
00210             
00211             return null;
00212         }
00213         
00214         public function toString($delimiter = ' - ')
00215         {
00216             if ($this->start && $this->end)
00217                 return
00218                     $this->start->toString()
00219                     .$delimiter
00220                     .$this->end->toString();
00221             elseif ($this->start)
00222                 return $this->start->toString();
00223             elseif ($this->end)
00224                 return $this->end->toString();
00225             
00226             return null;
00227         }
00228         
00229         public function overlaps(DateRange $range)
00230         {
00231             if ($this->isEmpty() || $range->isEmpty())
00232                 return true;
00233             
00234             $left = $this->getStartStamp();
00235             $right = $this->getEndStamp();
00236             $min = $range->getStartStamp();
00237             $max = $range->getEndStamp();
00238             
00239             return (
00240                 (
00241                     $min
00242                     && $max
00243                     && (
00244                         (
00245                             $left
00246                             && $right
00247                             && (
00248                                 (($left <= $min) && ($min <= $right))
00249                                 || (($min <= $left) && ($left <= $max))
00250                             )
00251                         ) || (
00252                             !$left
00253                             && ($min <= $right)
00254                         ) || (
00255                             !$right
00256                             && ($left <= $max)
00257                         )
00258                     )
00259                 ) || (
00260                     $min
00261                     && !$max
00262                     && (
00263                         !$right
00264                         || (
00265                             $right
00266                             && ($min <= $right)
00267                         )
00268                     )
00269                 ) || (
00270                     !$min
00271                     && $max
00272                     && (
00273                         !$left
00274                         || (
00275                             $left
00276                             && ($left <= $max)
00277                         )
00278                     )
00279                 )
00280             );
00281         }
00282         
00283         public function contains(/* Timestamp */ $date)
00284         {
00285             $this->checkType($date);
00286             
00287             $start = $this->getStartStamp();
00288             $end = $this->getEndStamp();
00289             $probe = $date->toStamp();
00290             
00291             if (
00292                 (!$start && !$end)
00293                 || (!$start && $end >= $probe)
00294                 || (!$end && $start <= $probe)
00295                 || ($start <= $probe && $end >= $probe)
00296             )
00297                 return true;
00298             
00299             return false;
00300         }
00301 
00302         public function split()
00303         {
00304             Assert::isFalse(
00305                 $this->isOpen(),
00306                 "open range can't be splitted"
00307             );
00308             
00309             $dates = array();
00310             
00311             $start = new Date($this->start->getDayStartStamp());
00312             
00313             $endStamp = $this->end->getDayEndStamp();
00314             
00315             for (
00316                 $current = $start;
00317                 $current->toStamp() < $endStamp;
00318                 $current->modify('+1 day')
00319             ) {
00320                 $dates[] = new Date($current->getDayStartStamp());
00321             }
00322             
00323             return $dates;
00324         }
00325         
00326         public static function merge($array /* of DateRanges */)
00327         {
00328             $out = array();
00329             
00330             foreach ($array as $range) {
00331                 $accepted = false;
00332                 
00333                 foreach ($out as $outRange)
00334                     if ($outRange->isNeighbour($range)) {
00335                         $outRange->enlarge($range);
00336                         $accepted = true;
00337                     }
00338                 
00339                 if (!$accepted)
00340                     $out[] = clone $range;
00341             }
00342             
00343             return $out;
00344         }
00345         
00346         public function isNeighbour(DateRange $range)
00347         {
00348             Assert::isTrue(!$this->isOpen() && !$range->isOpen());
00349             
00350             if (
00351                 $this->overlaps($range)
00352                 || (
00353                     $this->start->spawn('-1 day')->getDayStartStamp()
00354                     == $range->end->getDayStartStamp()
00355                 ) || (
00356                     $this->end->spawn('+1 day')->getDayStartStamp()
00357                     == $range->start->getDayStartStamp()
00358                 )
00359             )
00360                 return true;
00361             
00362             return false;
00363         }
00364         
00365         public function isOpen()
00366         {
00367             return !$this->start || !$this->end;
00368         }
00369         
00375         public function enlarge(DateRange $range)
00376         {
00377             if (!$range->start)
00378                 $this->start = null;
00379             elseif (
00380                 $this->start
00381                 && $this->start->toStamp() > $range->start->toStamp()
00382             )
00383                 $this->start = clone $range->start;
00384             
00385             if (!$range->end)
00386                 $this->end = null;
00387             elseif (
00388                 $this->end
00389                 && $this->end->toStamp() < $range->end->toStamp()
00390             )
00391                 $this->end = clone $range->end;
00392             
00393             return $this;
00394         }
00395         
00401         public function clip(DateRange $range)
00402         {
00403             Assert::isTrue($this->overlaps($range));
00404 
00405             if (
00406                 $range->start
00407                 && (
00408                     $this->start
00409                     && $range->start->toStamp() > $this->start->toStamp()
00410                     || !$this->start
00411                 )
00412             )
00413                 $this->start = clone $range->start;
00414             
00415             if (
00416                 $range->end
00417                 && (
00418                     $this->end
00419                     && $range->end->toStamp() < $this->end->toStamp()
00420                     || !$this->end
00421                 )
00422             )
00423                 $this->end = clone $range->end;
00424 
00425             return $this;
00426         }
00427 
00433         public function lightCopyOnClip(DateRange $range)
00434         {
00435             $copy = DateRange::create();
00436             
00437             if (
00438                 $range->start
00439                 && (
00440                     $this->start
00441                     && $range->start->toStamp() > $this->start->toStamp()
00442                     || !$this->start
00443                 )
00444             )
00445                 $copy->start = $range->start;
00446             else
00447                 $copy->start = $this->start;
00448             
00449             if (
00450                 $range->end
00451                 && (
00452                     $this->end
00453                     && $range->end->toStamp() < $this->end->toStamp()
00454                     || !$this->end
00455                 )
00456             )
00457                 $copy->end = $range->end;
00458             else
00459                 $copy->end = $this->end;
00460             
00461             return $copy;
00462         }
00463 
00464         public function getStartStamp() // null if start is null
00465         {
00466             if ($this->start) {
00467                 if (!$this->dayStartStamp) {
00468                     $this->dayStartStamp = $this->start->getDayStartStamp();
00469                 }
00470                 
00471                 return $this->dayStartStamp;
00472             }
00473             
00474             return null;
00475         }
00476         
00477         public function getEndStamp() // null if end is null
00478         {
00479             if ($this->end) {
00480                 if (!$this->dayEndStamp) {
00481                     $this->dayEndStamp = $this->end->getDayEndStamp();
00482                 }
00483                 
00484                 return $this->dayEndStamp;
00485             }
00486             
00487             return null;
00488         }
00489         
00490         public static function compare(DateRange $left, DateRange $right)
00491         {
00492             if ($left->isEmpty() && $right->isEmpty())
00493                 return 0;
00494             elseif ($left->isEmpty())
00495                 return 1;
00496             elseif ($right->isEmpty())
00497                 return -1;
00498             
00499             $leftStart = $left->getStartStamp();
00500             $leftEnd = $left->getEndStamp();
00501             
00502             $rightStart = $right->getStartStamp();
00503             $rightEnd = $right->getEndStamp();
00504             
00505             if (
00506                 !$leftStart && !$rightStart
00507                 || $leftStart && $rightStart && ($leftStart == $rightStart)
00508             ) {
00509                 if (
00510                     !$leftEnd && !$rightEnd
00511                     || $leftEnd && $rightEnd && ($leftEnd == $rightEnd)
00512                 )
00513                     return 0;
00514                 elseif (!$leftEnd && $rightEnd)
00515                     return 1;
00516                 elseif ($leftEnd && !$rightEnd)
00517                     return -1;
00518                 elseif ($leftEnd < $rightEnd)
00519                     return -1;
00520                 else
00521                     return 1;
00522             } elseif (!$leftStart && $rightStart)
00523                 return -1;
00524             elseif ($leftStart && !$rightStart)
00525                 return 1;
00526             elseif ($leftStart < $rightStart)
00527                 return -1;
00528             else
00529                 return 1;
00530         }
00531         
00532         public function isOneDay()
00533         {
00534             return (!$this->isOpen())
00535                 && ($this->start->toDate() == $this->end->toDate());
00536         }
00537         
00541         public function toTimestampRange()
00542         {
00543             return
00544                 TimestampRange::create(
00545                     $this->getStart()->toTimestamp(),
00546                     $this->getEnd()->toTimestamp()
00547                 );
00548         }
00549         
00550         protected function checkType($value)
00551         {
00552             Assert::isTrue(
00553                 ClassUtils::isInstanceOf($value, $this->getObjectName())
00554             );
00555         }
00556         
00557         protected function getObjectName()
00558         {
00559             return 'Date';
00560         }
00561     }
00562 ?>