# 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 ?>