Отрицательный DateInterval

Я хочу создать объект DatePeriod с отрицательным DateInterval.

Это создает DatePeriod с годом, увеличивающимся с сегодняшнего дня до 2016 года.

$this->Set('dates', new DatePeriod(new DateTime(), new DateInterval('P1Y'), new DateTime('2016-06-06')));

Я хочу начать с 2016 года и использовать отрицательный переход DateInterval к ​​текущему году

Что-то вроде этого может проиллюстрировать мое желание

$this->Set('dates', new DatePeriod(new DateTime('2016-06-06'), new DateInterval('-P1Y'), new DateTime()));

Я просто не могу найти какую-либо расширенную информацию о DatePeriod или DateInterval о том, как это сделать. Все, что я нахожу, это то, что DateInterval можно инвертировать.

Ответ 1

Согласно комментарий kevinpeno на 17 марта 2011 года 07:47 на странице php.net о DateInterval:: __ construct(), вы не может напрямую создавать отрицательные DateIntervals через конструктор:

new DateInterval('-P1Y'); // Exception "Unknown or bad format (-P1Y)"

Вместо этого вам необходимо создать положительный интервал и явно установить его invert в свойство 1:

$di = new DateInterval('P1Y');
$di->invert = 1; // Proper negative date interval

Просто проверил вышеуказанный код сам, он работает именно таким образом.

Ответ 2

Это заняло немного рытья. Единственным способом, которым я смог получить отрицательный DateInterval, было следующее:

$interval = DateInterval::createFromDateString('-1 day');

Однако есть улов. DatePeriod, похоже, не работает для отрицательных интервалов. Если вы устанавливаете дату начала до даты окончания, то она не содержит никаких дат вообще, и если вы переворачиваете так, чтобы дата начала была после даты окончания, она выглядела бы назад бесконечно.

Вероятно, вам придется перестроить свой код, чтобы перебрать даты с помощью DateTime::sub с положительным DateInterval или DateTime::add с отрицательным.

Ответ 3

Я пробовал это сам, и это невозможно с DatePeriod в одиночку, но я думаю, что это имеет смысл: он просто отражает периоды, которые обычно не имеют какого-либо определенного порядка и поэтому не могут быть переупорядочены (это может быть рассматривается как набор).

Единственный способ получить даты и отсортировать их в обратном порядке, насколько я вижу, - это что-то вроде этого

$result = array();
forech ($dateperiod as $date) {
  array_push ($result, $data);
}

Update

$date = new DateTime('2016-06-06');
$i = new DateInterval('P1Y');
$now = new DateTime;
while ($date >= $now) {
  echo $date->format('c') . PHP_EOL;
  $date = $date->sub($i);
}

Ответ 4

Этот отрывок работал у меня:

    $iDate = $endDate;
    while($iDate >= $startDate) {
        $dates[] = new DateTime($iDate->format('Y-m-d'));
        $iDate->sub(new DateInterval("P1D"));
    }

Ответ 5

У меня была та же проблема (и некоторые другие), и я создал класс, чтобы иметь возможность добавлять и подставлять DateInterval. Он также поддерживает отрицательный интервал даты ISO8601 (например, "P-2M1DT3H-56M21S" ).

Здесь код (предложения приветствуются, я очень новичок в 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;
    }
}

Ответ 6

Вы можете использовать sub http://php.net/manual/en/datetime.sub.php

Вот пример

$startDate = new \DateTime('2018-01-08 13:54:06');
$startDate->sub(new \DateInterval('P1D'));

Ответ 7

EDIT: обратите внимание, что это было вдохновлено кодом khany выше.

Здесь полностью работает script для моего прецедента, который должен отображать строки года + месяца с текущего месяца, возвращая N число месяцев. Он должен работать с днями или годами как интервалы и проверяется с помощью PHP версии 5.3.3.

<?php

date_default_timezone_set('America/Los_Angeles');

$monthsBack=16;

$monthDateList = array();
$previousMonthDate = new DateTime();
for($monthInterval = 0; $monthInterval < $monthsBack; $monthInterval++) {
    array_push($monthDateList, $previousMonthDate->format('Ym'));
    $previousMonthDate->sub(new DateInterval("P1M"));
}

print_r($monthDateList) . "\n";

?>

Вывод:

Array
(
    [0] => 201705
    [1] => 201704
    [2] => 201703
    [3] => 201702
    [4] => 201701
    [5] => 201612
    [6] => 201611
    [7] => 201610
    [8] => 201609
    [9] => 201608
    [10] => 201607
    [11] => 201606
    [12] => 201605
    [13] => 201604
    [14] => 201603
    [15] => 201602
)