I want to create a DatePeriod object with a negative DateInterval.
This creates a DatePeriod with the year increasing from today to 2016.
$this->S
I had the same problem (and some other) and have created a class in order to be able to add and substact DateInterval. It supports also negative ISO8601 date interval ('P-2M1DT3H-56M21S' for example).
Here the code (suggestions are welcome, I'm a very beginner in PHP):
class MyDateInterval extends DateInterval
{
public function __construct($interval_spec)
{
$interval_spec = str_replace('+', '', $interval_spec);
$pos = strpos($interval_spec, '-');
if ($pos !== false) {
// if at least 1 negative part
$pattern = '/P(?P<ymd>(?P<years>-?\d+Y)?(?P<months>-?\d+M)?(?P<days>-?\d+D)?)?(?P<hms>T(?P<hours>-?\d+H)?(?P<minutes>-?\d+M)?(?P<seconds>-?\d+S)?)?/';
$match = preg_match($pattern, $interval_spec, $matches);
$group_names = array('years', 'months', 'days', 'hours', 'minutes', 'seconds');
$negative_parts = array();
$positive_parts = array();
$all_negative = true;
foreach ($matches as $k => $v) {
if (in_array($k, $group_names, true)) {
if (substr($v, 0, 1) == '-' and $v != '')
$negative_parts[$k] = $v;
if (substr($v, 0, 1) != '-' and $v != '')
$positive_parts[$k] = $v;
}
}
if (count($positive_parts) == 0) {
// only negative parts
$interval_spec = str_replace('-', '', $interval_spec);
parent::__construct($interval_spec);
$this->invert = 1;
} else {
// the negative and positive parts are to be sliced
$negative_interval_spec = 'P';
$positive_interval_spec = 'P';
if ($matches['ymd'] != '') {
foreach ($matches as $k => $v) {
if (in_array($k, array_slice($group_names, 0, 3))) {
$negative_interval_spec .= $negative_parts[$k];
$positive_interval_spec .= $positive_parts[$k];
}
}
}
if ($matches['hms'] != '') {
$negative_ok = false;
$positive_ok = false;
foreach ($matches as $k => $v) {
if (in_array($k, array_slice($group_names, 3, 3))) {
if ($negative_parts[$k] != '' and ! $negative_ok) {
$negative_interval_spec .= 'T';
$negative_ok = true;
}
$negative_interval_spec .= $negative_parts[$k];
if ($positive_parts[$k] != '' and ! $positive_ok) {
$positive_interval_spec .= 'T';
$positive_ok = true;
}
$positive_interval_spec .= $positive_parts[$k];
}
}
}
$negative_interval_spec = str_replace('-', '', $negative_interval_spec);
$from = new DateTime('2013-01-01');
$to = new DateTime('2013-01-01');
$to = $to->add(new DateInterval($positive_interval_spec));
$to = $to->sub(new DateInterval($negative_interval_spec));
$diff = $from->diff($to);
parent::__construct($diff->format('P%yY%mM%dDT%hH%iM%sS'));
$this->invert = $diff->invert;
}
} else {
// only positive parts
parent::__construct($interval_spec);
}
}
public static function fromDateInterval(DateInterval $from)
{
return new MyDateInterval($from->format('P%yY%mM%dDT%hH%iM%sS'));
}
public static function fromSeconds($from)
{
$invert = false;
if ($from < 0)
$invert = true;
$from = abs($from);
$years = floor($from / (365 * 30 * 24 * 60 * 60));
$from = $from % (365 * 30 * 24 * 60 * 60);
$months = floor($from / (30 * 24 * 60 * 60));
$from = $from % (30 * 24 * 60 * 60);
$days = floor($from / (24 * 60 * 60));
$from = $from % (24 * 60 * 60);
$hours = floor($from / (60 * 60));
$from = $from % (60 * 60);
$minutes = floor($from / 60);
$seconds = floor($from % 60);
if ($invert)
return new MyDateInterval(sprintf("P-%dY-%dM-%dDT-%dH-%dM-%dS", $years, $months, $days, $hours, $minutes, $seconds));
return new MyDateInterval(sprintf("P%dY%dM%dDT%dH%dM%dS", $years, $months, $days, $hours, $minutes, $seconds));
}
public function to_seconds()
{
$seconds = ($this->y * 365 * 24 * 60 * 60)
+ ($this->m * 30 * 24 * 60 * 60)
+ ($this->d * 24 * 60 * 60)
+ ($this->h * 60 * 60)
+ ($this->i * 60)
+ $this->s;
if ($this->invert == 1)
return $seconds * -1;
return $seconds;
}
public function to_hours()
{
$hours = round($this->to_seconds() / (60 * 60), 2);
return $hours;
}
public function add($interval)
{
$sum = $this->to_seconds() + $interval->to_seconds();
$new = MyDateInterval::fromSeconds($sum);
foreach ($new as $k => $v) $this->$k = $v;
return $this;
}
public function sub($interval)
{
$diff = $this->to_seconds() - $interval->to_seconds();
$new = MyDateInterval::fromSeconds($diff);
foreach ($new as $k => $v) $this->$k = $v;
return $this;
}
public function recalculate()
{
$seconds = $this->to_seconds();
$new = MyDateInterval::fromSeconds($seconds);
foreach ($new as $k => $v) $this->$k = $v;
return $this;
}
}