Skip to content

Instantly share code, notes, and snippets.

@imbrish
Created March 10, 2018 08:58
Show Gist options
  • Save imbrish/fb09b3328c498873818b9ff0b56ea423 to your computer and use it in GitHub Desktop.
Save imbrish/fb09b3328c498873818b9ff0b56ea423 to your computer and use it in GitHub Desktop.
Extension of `Carbon\CarbonInterval`
<?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