Отрицательный 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
)