Created
March 10, 2018 08:58
-
-
Save imbrish/fb09b3328c498873818b9ff0b56ea423 to your computer and use it in GitHub Desktop.
Extension of `Carbon\CarbonInterval`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Library\Utilities; | |
use DateTime; | |
use DatePeriod; | |
use Carbon\CarbonInterval; | |
use InvalidArgumentException; | |
use Illuminate\Support\Collection; | |
class DateInterval extends CarbonInterval | |
{ | |
/** | |
* Date period options. | |
*/ | |
const EXCLUDE_START = 1; | |
const EXCLUDE_END = 2; | |
/** | |
* Mapping of units and properties. | |
* | |
* @var array | |
*/ | |
protected static $units = [ | |
'Y' => 'years', | |
'M' => 'months', | |
'W' => 'weeks', | |
'D' => 'dayz', | |
'h' => 'hours', | |
'm' => 'minutes', | |
's' => 'seconds', | |
]; | |
/** | |
* Mapping of values after which properties should cascade. | |
* | |
* @var array | |
*/ | |
protected static $cascades = [ | |
'seconds' => 60, | |
'minutes' => 60, | |
'hours' => 24, | |
'dayz' => 30, | |
'months' => 12, | |
'years' => 0, | |
]; | |
/** | |
* Parse simplified interval specification. | |
* | |
* Understands any string with one or more parts in form of <number><unit>. | |
* Units above may be Y for years, M for months, D for days, W for weeks, | |
* h for hours, m for minutes or s for seconds. | |
* | |
* @param string $spec | |
* @return static | |
* | |
* @throws \InvalidArgumentException | |
*/ | |
public static function parse($spec) | |
{ | |
$pattern = '/([0-9]+)\s*(['.implode(array_keys(static::$units)).'])/'; | |
// Fail if parser regex leaves some non space characters. | |
if (trim(preg_replace($pattern, '', $spec))) { | |
throw new InvalidArgumentException("Invalid interval specification: $spec."); | |
} | |
$interval = new static(0, 0, 0, 0, 0, 0, 0); | |
preg_match_all($pattern, $spec, $parts, PREG_SET_ORDER); | |
foreach ($parts as $part) { | |
list($match, $amount, $type) = $part; | |
$property = static::$units[$type]; | |
if ($property == 'weeks') { | |
list($property, $amount) = ['dayz', $amount * 7]; | |
} | |
$interval->$property += $amount; | |
} | |
return $interval; | |
} | |
/** | |
* Cascade the values up the unit stack. | |
* | |
* @return string | |
*/ | |
public function cascade() | |
{ | |
$carry = 0; | |
array_walk(static::$cascades, function ($max, $property) use (&$carry) { | |
$value = $this->$property + $carry; | |
$this->$property = $max ? $value % $max : $value; | |
$carry = $max ? floor($value / $max) : 0; | |
}); | |
return $this; | |
} | |
/** | |
* Get the current interval in a human readable format in the current locale. | |
* | |
* @param bool $singleUnit | |
* @param bool $skipOne | |
* @return string | |
*/ | |
public function forHumans($singleUnit = false, $skipOne = false) | |
{ | |
$periods = [ | |
'year' => $this->years, | |
'month' => $this->months, | |
'week' => $this->weeks, | |
'day' => $this->daysExcludeWeeks, | |
'hour' => $this->hours, | |
'minute' => $this->minutes, | |
'second' => $this->seconds, | |
]; | |
$parts = []; | |
$trans = static::translator(); | |
foreach ($periods as $unit => $count) { | |
if ($count == 0) { | |
continue; | |
} | |
$part = $trans->transChoice($unit, $count, [':count' => $count]); | |
if ($skipOne && $count == 1) { | |
$part = substr($part, 2); | |
} | |
if ($singleUnit) { | |
return $part; | |
} | |
array_push($parts, $part); | |
} | |
return implode(' ', $parts); | |
} | |
/** | |
* Convert the current interval to a collection of DateTime objects in given period. | |
* | |
* @param \DateTime $start | |
* @param \DateTime|int $end | |
* @param int $options | |
* @return \Illuminate\Support\Collection | |
* | |
* @throws \InvalidArgumentException | |
*/ | |
public function toPeriod($start, $end, $options = 0) | |
{ | |
if ($this->spec() == 'PT0S') { | |
throw new InvalidArgumentException("Empty interval cannot be converted into period."); | |
} | |
if (! ($options & static::EXCLUDE_END) && $end instanceof DateTime) { | |
$end = tap(clone $end)->modify('+1 second'); | |
} | |
else if ($options & static::EXCLUDE_END && is_int($end)) { | |
$end--; | |
} | |
$period = new DatePeriod( | |
$start, $this, $end, | |
$options & static::EXCLUDE_START ? DatePeriod::EXCLUDE_START_DATE : 0 | |
); | |
return new Collection(iterator_to_array($period)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment