From ae2d61682f9d498829a8b19e8f1b4e6ad289a908 Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 2 Apr 2014 10:53:39 +0200 Subject: [PATCH] Fix how the timeline processes dates refs #4190 --- library/Icinga/Util/DateTimeFactory.php | 39 +++++++++++++- .../controllers/TimelineController.php | 30 +++++++---- .../library/Monitoring/Timeline/TimeRange.php | 52 ++++++++++++++----- 3 files changed, 96 insertions(+), 25 deletions(-) diff --git a/library/Icinga/Util/DateTimeFactory.php b/library/Icinga/Util/DateTimeFactory.php index f5e049b3a..17aa72948 100644 --- a/library/Icinga/Util/DateTimeFactory.php +++ b/library/Icinga/Util/DateTimeFactory.php @@ -31,8 +31,8 @@ namespace Icinga\Util; use \DateTime; use \DateTimeZone; -use \Icinga\Util\ConfigAwareFactory; -use \Icinga\Exception\ConfigurationError; +use Icinga\Util\ConfigAwareFactory; +use Icinga\Exception\ConfigurationError; /** * Factory for time zone aware DateTime objects @@ -91,4 +91,39 @@ class DateTimeFactory implements ConfigAwareFactory { return new DateTime($time, $timeZone !== null ? $timeZone : self::$timeZone); } + + /** + * Return the amount of seconds based on the given month + * + * @param DateTime $dateTime The date and time to use + * + * @return int + */ + public static function getSecondsByMonth(DateTime $dateTime) + { + return (int) $dateTime->format('t') * 24 * 3600; + } + + /** + * Return the amount of seconds based on the given year + * + * @param DateTime $dateTime The date and time to use + * + * @return int + */ + public static function getSecondsByYear(DateTime $dateTime) + { + return (self::isLeapYear($dateTime) ? 366 : 365) * 24 * 3600; + } + + /** + * Return whether the given year is a leap year + * + * @param DateTime $dateTime The date and time to check + * @return bool + */ + public static function isLeapYear(DateTime $dateTime) + { + return $dateTime->format('L') == 1; + } } diff --git a/modules/monitoring/application/controllers/TimelineController.php b/modules/monitoring/application/controllers/TimelineController.php index e276e517d..47917995d 100644 --- a/modules/monitoring/application/controllers/TimelineController.php +++ b/modules/monitoring/application/controllers/TimelineController.php @@ -7,6 +7,7 @@ use \DateInterval; use \Zend_Config; use Icinga\Web\Url; use Icinga\Application\Config; +use Icinga\Util\DateTimeFactory; use Icinga\Web\Controller\ActionController; use Icinga\Module\Monitoring\Timeline\TimeLine; use Icinga\Module\Monitoring\Timeline\TimeRange; @@ -152,11 +153,13 @@ class Monitoring_TimelineController extends ActionController } /** - * Return a preload interval based on the chosen timeline interval + * Return a preload interval based on the chosen timeline interval and the given date and time * - * @return DateInterval The interval to pre-load + * @param DateTime $dateTime The date and time to use + * + * @return DateInterval The interval to pre-load */ - private function getPreloadInterval() + private function getPreloadInterval(DateTime $dateTime) { switch ($this->view->intervalBox->getInterval()) { @@ -165,9 +168,17 @@ class Monitoring_TimelineController extends ActionController case '1w': return DateInterval::createFromDateString('8 weeks -1 second'); case '1m': - return DateInterval::createFromDateString('6 months -1 second'); + $dateCopy = clone $dateTime; + for ($i = 0; $i < 6; $i++) { + $dateCopy->sub(new DateInterval('PT' . DateTimeFactory::getSecondsByMonth($dateCopy) . 'S')); + } + return $dateCopy->add(new DateInterval('PT1S'))->diff($dateTime); case '1y': - return DateInterval::createFromDateString('4 years -1 second'); + $dateCopy = clone $dateTime; + for ($i = 0; $i < 4; $i++) { + $dateCopy->sub(new DateInterval('PT' . DateTimeFactory::getSecondsByYear($dateCopy) . 'S')); + } + return $dateCopy->add(new DateInterval('PT1S'))->diff($dateTime); default: return DateInterval::createFromDateString('1 day -1 second'); } @@ -223,13 +234,14 @@ class Monitoring_TimelineController extends ActionController */ private function buildTimeRanges() { - $startTime = new DateTime(); + $startTime = DateTimeFactory::create(); $startParam = $this->_request->getParam('start'); $startTimestamp = is_numeric($startParam) ? intval($startParam) : strtotime($startParam); if ($startTimestamp !== false) { $startTime->setTimestamp($startTimestamp); + } else { + $this->extrapolateDateTime($startTime); } - $this->extrapolateDateTime($startTime); $endTime = clone $startTime; $endParam = $this->_request->getParam('end'); @@ -237,13 +249,13 @@ class Monitoring_TimelineController extends ActionController if ($endTimestamp !== false) { $endTime->setTimestamp($endTimestamp); } else { - $endTime->sub($this->getPreloadInterval()); + $endTime->sub($this->getPreloadInterval($startTime)); } $forecastStart = clone $endTime; $forecastStart->sub(new DateInterval('PT1S')); $forecastEnd = clone $forecastStart; - $forecastEnd->sub($endTime->diff($startTime)); + $forecastEnd->sub($this->getPreloadInterval($forecastStart)); $timelineInterval = $this->getTimelineInterval(); return array( diff --git a/modules/monitoring/library/Monitoring/Timeline/TimeRange.php b/modules/monitoring/library/Monitoring/Timeline/TimeRange.php index 85be826e6..93c81f98f 100644 --- a/modules/monitoring/library/Monitoring/Timeline/TimeRange.php +++ b/modules/monitoring/library/Monitoring/Timeline/TimeRange.php @@ -8,6 +8,7 @@ use \StdClass; use \Iterator; use \DateTime; use \DateInterval; +use Icinga\Util\DateTimeFactory; /** * A range of time split into a specific interval @@ -149,17 +150,44 @@ class TimeRange implements Iterator $startTime->setTimestamp($time); } - $endTime = clone $startTime; + return $this->buildTimeframe($startTime, $this->applyInterval(clone $startTime, 1)); + } - if ($this->negative) { - $endTime->sub($this->interval); - $endTime->add(new DateInterval('PT1S')); - } else { - $endTime->add($this->interval); - $endTime->sub(new DateInterval('PT1S')); + /** + * Apply the current interval to the given date and time + * + * @param DateTime $dateTime The date and time to apply the interval to + * @param int $adjustBy By how much seconds the resulting date and time should be adjusted + * + * @return DateTime + */ + protected function applyInterval(DateTime $dateTime, $adjustBy) + { + if (!$this->interval->y && !$this->interval->m) { + if ($this->negative) { + return $dateTime->sub($this->interval)->add(new DateInterval('PT' . $adjustBy . 'S')); + } else { + return $dateTime->add($this->interval)->sub(new DateInterval('PT' . $adjustBy . 'S')); + } + } elseif ($this->interval->m) { + for ($i = 0; $i < $this->interval->m; $i++) { + if ($this->negative) { + $dateTime->sub(new DateInterval('PT' . DateTimeFactory::getSecondsByMonth($dateTime) . 'S')); + } else { + $dateTime->add(new DateInterval('PT' . DateTimeFactory::getSecondsByMonth($dateTime) . 'S')); + } + } + } elseif ($this->interval->y) { + for ($i = 0; $i < $this->interval->y; $i++) { + if ($this->negative) { + $dateTime->sub(new DateInterval('PT' . DateTimeFactory::getSecondsByYear($dateTime) . 'S')); + } else { + $dateTime->add(new DateInterval('PT' . DateTimeFactory::getSecondsByYear($dateTime) . 'S')); + } + } } - - return $this->buildTimeframe($startTime, $endTime); + $adjustment = new DateInterval('PT' . $adjustBy . 'S'); + return $this->negative ? $dateTime->add($adjustment) : $dateTime->sub($adjustment); } /** @@ -224,10 +252,6 @@ class TimeRange implements Iterator */ public function next() { - if ($this->negative) { - $this->current->sub($this->interval); - } else { - $this->current->add($this->interval); - } + $this->applyInterval($this->current, 0); } }