00001 <?php
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00021 class DateRange implements Stringable, SingleRange
00022 {
00023 private $start = null;
00024 private $end = null;
00025
00026 private $dayStartStamp = null;
00027 private $dayEndStamp = null;
00028
00032 public static function create($start = null, $end = null)
00033 {
00034 return new self($start, $end);
00035 }
00036
00037 public function __construct($start = null, $end = null)
00038 {
00039 if ($start)
00040 $this->setStart($start);
00041
00042 if ($end)
00043 $this->setEnd($end);
00044 }
00045
00046 public function __clone()
00047 {
00048 if ($this->start)
00049 $this->start = clone $this->start;
00050
00051 if ($this->end)
00052 $this->end = clone $this->end;
00053 }
00054
00059 public function setStart( $start)
00060 {
00061 $this->checkType($start);
00062
00063 if ($this->end && $this->end->toStamp() < $start->toStamp())
00064 throw new WrongArgumentException(
00065 'start must be lower than end'
00066 );
00067
00068 $this->start = $start;
00069 $this->dayStartStamp = null;
00070
00071 return $this;
00072 }
00073
00078 public function setEnd( $end)
00079 {
00080 $this->checkType($end);
00081
00082 if ($this->start && $this->start->toStamp() > $end->toStamp())
00083 throw new WrongArgumentException(
00084 'end must be higher than start'
00085 );
00086
00087 $this->end = $end;
00088 $this->dayEndStamp = null;
00089 return $this;
00090 }
00091
00095 public function lazySet($start = null, $end = null)
00096 {
00097 if ($start)
00098 $this->checkType($start);
00099
00100 if ($end)
00101 $this->checkType($end);
00102
00103 if ($start && $end) {
00104 if ($start->toStamp() >= $end->toStamp())
00105 $this->setEnd($start)->setStart($end);
00106 else
00107 $this->setStart($start)->setEnd($end);
00108 } elseif ($start)
00109 $this->setStart($start);
00110 elseif ($end)
00111 $this->setEnd($end);
00112
00113 return $this;
00114 }
00115
00119 public function dropStart()
00120 {
00121 $this->start = null;
00122 $this->dayStartStamp = null;
00123 return $this;
00124 }
00125
00129 public function dropEnd()
00130 {
00131 $this->end = null;
00132 $this->dayEndStamp = null;
00133 return $this;
00134 }
00135
00136 public function isEmpty()
00137 {
00138 return
00139 ($this->start === null)
00140 && ($this->end === null);
00141 }
00142
00146 public function getStart()
00147 {
00148 return $this->start;
00149 }
00150
00154 public function getEnd()
00155 {
00156 return $this->end;
00157 }
00158
00159 public function toDateString(
00160 $internalDelimiter = '-',
00161 $dateDelimiter = ' - '
00162 )
00163 {
00164 if ($this->start && $this->end)
00165 return
00166 "{$this->start->toDate($internalDelimiter)}"
00167 .$dateDelimiter
00168 ."{$this->end->toDate($internalDelimiter)}";
00169 elseif ($this->start)
00170 return $this->start->toDate($internalDelimiter);
00171 elseif ($this->end)
00172 return $this->end->toDate($internalDelimiter);
00173
00174 return null;
00175 }
00176
00177 public function toString($delimiter = ' - ')
00178 {
00179 if ($this->start && $this->end)
00180 return
00181 $this->start->toString()
00182 .$delimiter
00183 .$this->end->toString();
00184 elseif ($this->start)
00185 return $this->start->toString();
00186 elseif ($this->end)
00187 return $this->end->toString();
00188
00189 return null;
00190 }
00191
00192 public function overlaps(DateRange $range)
00193 {
00194 if ($this->isEmpty() || $range->isEmpty())
00195 return true;
00196
00197 $left = $this->getStartStamp();
00198 $right = $this->getEndStamp();
00199 $min = $range->getStartStamp();
00200 $max = $range->getEndStamp();
00201
00202 return (
00203 (
00204 $min
00205 && $max
00206 && (
00207 (
00208 $left
00209 && $right
00210 && (
00211 (($left <= $min) && ($min <= $right))
00212 || (($min <= $left) && ($left <= $max))
00213 )
00214 ) || (
00215 !$left
00216 && ($min <= $right)
00217 ) || (
00218 !$right
00219 && ($left <= $max)
00220 )
00221 )
00222 ) || (
00223 $min
00224 && !$max
00225 && (
00226 !$right
00227 || (
00228 $right
00229 && ($min <= $right)
00230 )
00231 )
00232 ) || (
00233 !$min
00234 && $max
00235 && (
00236 !$left
00237 || (
00238 $left
00239 && ($left <= $max)
00240 )
00241 )
00242 )
00243 );
00244 }
00245
00246 public function contains( $date)
00247 {
00248 $this->checkType($date);
00249
00250 $start = $this->getStartStamp();
00251 $end = $this->getEndStamp();
00252 $probe = $date->toStamp();
00253
00254 if (
00255 (!$start && !$end)
00256 || (!$start && $end >= $probe)
00257 || (!$end && $start <= $probe)
00258 || ($start <= $probe && $end >= $probe)
00259 )
00260 return true;
00261
00262 return false;
00263 }
00264
00265 public function split()
00266 {
00267 Assert::isFalse(
00268 $this->isOpen(),
00269 "open range can't be splitted"
00270 );
00271
00272 $dates = array();
00273
00274 $start = new Date($this->start->getDayStartStamp());
00275
00276 $endStamp = $this->end->getDayEndStamp();
00277
00278 for (
00279 $current = $start;
00280 $current->toStamp() < $endStamp;
00281 $current->modify('+1 day')
00282 ) {
00283 $dates[] = new Date($current->getDayStartStamp());
00284 }
00285
00286 return $dates;
00287 }
00288
00289 public static function merge($array )
00290 {
00291 $out = array();
00292
00293 foreach ($array as $range) {
00294 $accepted = false;
00295
00296 foreach ($out as $outRange)
00297 if ($outRange->isNeighbour($range)) {
00298 $outRange->enlarge($range);
00299 $accepted = true;
00300 }
00301
00302 if (!$accepted)
00303 $out[] = clone $range;
00304 }
00305
00306 return $out;
00307 }
00308
00309 public function isNeighbour(DateRange $range)
00310 {
00311 Assert::isTrue(!$this->isOpen() && !$range->isOpen());
00312
00313 if (
00314 $this->overlaps($range)
00315 || (
00316 $this->start->spawn('-1 day')->getDayStartStamp()
00317 == $range->end->getDayStartStamp()
00318 ) || (
00319 $this->end->spawn('+1 day')->getDayStartStamp()
00320 == $range->start->getDayStartStamp()
00321 )
00322 )
00323 return true;
00324
00325 return false;
00326 }
00327
00328 public function isOpen()
00329 {
00330 return !$this->start || !$this->end;
00331 }
00332
00338 public function enlarge(DateRange $range)
00339 {
00340 if (!$range->start)
00341 $this->start = null;
00342 elseif (
00343 $this->start
00344 && $this->start->toStamp() > $range->start->toStamp()
00345 )
00346 $this->start = clone $range->start;
00347
00348 if (!$range->end)
00349 $this->end = null;
00350 elseif (
00351 $this->end
00352 && $this->end->toStamp() < $range->end->toStamp()
00353 )
00354 $this->end = clone $range->end;
00355
00356 return $this;
00357 }
00358
00364 public function clip(DateRange $range)
00365 {
00366 Assert::isTrue($this->overlaps($range));
00367
00368 if (
00369 $range->start
00370 && (
00371 $this->start
00372 && $range->start->toStamp() > $this->start->toStamp()
00373 || !$this->start
00374 )
00375 )
00376 $this->start = clone $range->start;
00377
00378 if (
00379 $range->end
00380 && (
00381 $this->end
00382 && $range->end->toStamp() < $this->end->toStamp()
00383 || !$this->end
00384 )
00385 )
00386 $this->end = clone $range->end;
00387
00388 return $this;
00389 }
00390
00396 public function lightCopyOnClip(DateRange $range)
00397 {
00398 $copy = DateRange::create();
00399
00400 if (
00401 $range->start
00402 && (
00403 $this->start
00404 && $range->start->toStamp() > $this->start->toStamp()
00405 || !$this->start
00406 )
00407 )
00408 $copy->start = $range->start;
00409 else
00410 $copy->start = $this->start;
00411
00412 if (
00413 $range->end
00414 && (
00415 $this->end
00416 && $range->end->toStamp() < $this->end->toStamp()
00417 || !$this->end
00418 )
00419 )
00420 $copy->end = $range->end;
00421 else
00422 $copy->end = $this->end;
00423
00424 return $copy;
00425 }
00426
00427 public function getStartStamp()
00428 {
00429 if ($this->start) {
00430 if (!$this->dayStartStamp) {
00431 $this->dayStartStamp = $this->start->getDayStartStamp();
00432 }
00433
00434 return $this->dayStartStamp;
00435 }
00436
00437 return null;
00438 }
00439
00440 public function getEndStamp()
00441 {
00442 if ($this->end) {
00443 if (!$this->dayEndStamp) {
00444 $this->dayEndStamp = $this->end->getDayEndStamp();
00445 }
00446
00447 return $this->dayEndStamp;
00448 }
00449
00450 return null;
00451 }
00452
00453 public static function compare(DateRange $left, DateRange $right)
00454 {
00455 if ($left->isEmpty() && $right->isEmpty())
00456 return 0;
00457 elseif ($left->isEmpty())
00458 return 1;
00459 elseif ($right->isEmpty())
00460 return -1;
00461
00462 $leftStart = $left->getStartStamp();
00463 $leftEnd = $left->getEndStamp();
00464
00465 $rightStart = $right->getStartStamp();
00466 $rightEnd = $right->getEndStamp();
00467
00468 if (
00469 !$leftStart && !$rightStart
00470 || $leftStart && $rightStart && ($leftStart == $rightStart)
00471 ) {
00472 if (
00473 !$leftEnd && !$rightEnd
00474 || $leftEnd && $rightEnd && ($leftEnd == $rightEnd)
00475 )
00476 return 0;
00477 elseif (!$leftEnd && $rightEnd)
00478 return 1;
00479 elseif ($leftEnd && !$rightEnd)
00480 return -1;
00481 elseif ($leftEnd < $rightEnd)
00482 return -1;
00483 else
00484 return 1;
00485 } elseif (!$leftStart && $rightStart)
00486 return -1;
00487 elseif ($leftStart && !$rightStart)
00488 return 1;
00489 elseif ($leftStart < $rightStart)
00490 return -1;
00491 else
00492 return 1;
00493 }
00494
00495 public function isOneDay()
00496 {
00497 return (!$this->isOpen())
00498 && ($this->start->toDate() == $this->end->toDate());
00499 }
00500
00501 protected function checkType($value)
00502 {
00503 Assert::isTrue(
00504 ClassUtils::isInstanceOf($value, $this->getObjectName())
00505 );
00506 }
00507
00508 protected function getObjectName()
00509 {
00510 return 'Date';
00511 }
00512 }
00513 ?>