date = clone $date; $this->translator = StaticContainer::get('Piwik\Translation\Translator'); } public function __sleep() { return [ 'date', ]; } public function __wakeup() { $this->translator = StaticContainer::get('Piwik\Translation\Translator'); } /** * Returns true if `$dateString` and `$period` represent multiple periods. * * Will return true for date/period combinations where date references multiple * dates and period is not `'range'`. For example, will return true for: * * - **date** = `2012-01-01,2012-02-01` and **period** = `'day'` * - **date** = `2012-01-01,2012-02-01` and **period** = `'week'` * - **date** = `last7` and **period** = `'month'` * * etc. * * @static * @param $dateString string The **date** query parameter value. * @param $period string The **period** query parameter value. * @return boolean */ public static function isMultiplePeriod($dateString, $period) { return is_string($dateString) && (preg_match('/^(last|previous){1}([0-9]*)$/D', $dateString, $regs) || Range::parseDateRange($dateString)) && $period != 'range'; } /** * Checks the given date format whether it is a correct date format and if not, throw an exception. * * For valid date formats have a look at the {@link \Piwik\Date::factory()} method and * {@link isMultiplePeriod()} method. * * @param string $dateString * @throws \Exception If `$dateString` is in an invalid format or if the time is before * Tue, 06 Aug 1991. */ public static function checkDateFormat($dateString) { if (self::isMultiplePeriod($dateString, 'day')) { return; } Date::factory($dateString); } /** * Returns the first day of the period. * * @return Date */ public function getDateStart() { $this->generate(); if (count($this->subperiods) == 0) { return $this->getDate(); } $periods = $this->getSubperiods(); /** @var $currentPeriod Period */ $currentPeriod = $periods[0]; while ($currentPeriod->getNumberOfSubperiods() > 0) { $periods = $currentPeriod->getSubperiods(); $currentPeriod = $periods[0]; } return $currentPeriod->getDate(); } /** * Returns the start date & time of this period. * * @return Date */ public function getDateTimeStart() { return $this->getDateStart()->getStartOfDay(); } /** * Returns the end date & time of this period. * * @return Date */ public function getDateTimeEnd() { return $this->getDateEnd()->getEndOfDay(); } /** * Returns the last day of the period. * * @return Date */ public function getDateEnd() { $this->generate(); if (count($this->subperiods) == 0) { return $this->getDate(); } $periods = $this->getSubperiods(); /** @var $currentPeriod Period */ $currentPeriod = $periods[count($periods) - 1]; while ($currentPeriod->getNumberOfSubperiods() > 0) { $periods = $currentPeriod->getSubperiods(); $currentPeriod = $periods[count($periods) - 1]; } return $currentPeriod->getDate(); } /** * Returns the period ID. * * @return int A unique integer for this type of period. */ public function getId() { return Piwik::$idPeriods[$this->getLabel()]; } /** * Returns the label for the current period. * * @return string `"day"`, `"week"`, `"month"`, `"year"`, `"range"` */ public function getLabel() { return $this->label; } /** * @return Date */ protected function getDate() { return $this->date; } protected function generate() { $this->subperiodsProcessed = true; } /** * Returns the number of available subperiods. * * @return int */ public function getNumberOfSubperiods() { $this->generate(); return count($this->subperiods); } /** * Returns the set of Period instances that together make up this period. For a year, * this would be 12 months. For a month this would be 28-31 days. Etc. * * @return Period[] */ public function getSubperiods() { $this->generate(); return $this->subperiods; } /** * Returns whether the date `$date` is within the current period or not. * * Note: the time component of the period's dates and `$date` is ignored. * * @param Date $today * @return bool */ public function isDateInPeriod(Date $date) { $ts = $date->getStartOfDay()->getTimestamp(); return $ts >= $this->getDateStart()->getStartOfDay()->getTimestamp() && $ts < $this->getDateEnd()->addDay(1)->getStartOfDay()->getTimestamp(); } /** * Returns whether the given period date range intersects with this one. * * @param Period $other * @return bool */ public function isPeriodIntersectingWith(Period $other) { return !($this->getDateEnd()->getTimestamp() < $other->getDateStart()->getTimestamp() || $this->getDateStart()->getTimestamp() > $other->getDateEnd()->getTimestamp()); } /** * Returns the start day and day after the end day for this period in the given timezone. * * @param Date[] $timezone */ public function getBoundsInTimezone(string $timezone) { $date1 = $this->getDateTimeStart()->setTimezone($timezone); $date2 = $this->getDateTimeEnd()->setTimezone($timezone); return [$date1, $date2]; } /** * Add a date to the period. * * Protected because adding periods after initialization is not supported. * * @param \Piwik\Period $period Valid Period object * @ignore */ protected function addSubperiod($period) { $this->subperiods[] = $period; } /** * Returns a list of strings representing the current period. * * @param string $format The format of each individual day. * @return array|string An array of string dates that this period consists of. */ public function toString($format = "Y-m-d") { $this->generate(); $dateString = array(); foreach ($this->subperiods as $period) { $childPeriodStr = $period->toString($format); if (is_array($childPeriodStr)) { $childPeriodStr = implode(",", $childPeriodStr); } $dateString[] = $childPeriodStr; } return $dateString; } /** * See {@link toString()}. * * @return string */ public function __toString() { return implode(",", $this->toString()); } /** * Returns a pretty string describing this period. * * @return string */ abstract public function getPrettyString(); /** * Returns a short string description of this period that is localized with the currently used * language. * * @return string */ abstract public function getLocalizedShortString(); /** * Returns a long string description of this period that is localized with the currently used * language. * * @return string */ abstract public function getLocalizedLongString(); /** * Returns the label of the period type that is one size smaller than this one, or null if * it's the smallest. * * Range periods and other such 'period collections' are not considered as separate from * the value type of the collection. So a range period will return the result of the * subperiod's `getImmediateChildPeriodLabel()` method. * * @ignore * @return string|null */ abstract public function getImmediateChildPeriodLabel(); /** * Returns the label of the period type that is one size bigger than this one, or null * if it's the biggest. * * Range periods and other such 'period collections' are not considered as separate from * the value type of the collection. So a range period will return the result of the * subperiod's `getParentPeriodLabel()` method. * * @ignore */ abstract public function getParentPeriodLabel(); /** * Returns the date range string comprising two dates * * @return string eg, `'2012-01-01,2012-01-31'`. */ public function getRangeString() { $dateStart = $this->getDateStart(); $dateEnd = $this->getDateEnd(); return $dateStart->toString("Y-m-d") . "," . $dateEnd->toString("Y-m-d"); } /** * @param string $format * * @return mixed */ protected function getTranslatedRange($format) { $dateStart = $this->getDateStart(); $dateEnd = $this->getDateEnd(); list($formatStart, $formatEnd) = $this->explodeFormat($format); $string = $dateStart->getLocalized($formatStart); $string .= $dateEnd->getLocalized($formatEnd, false); return $string; } /** * Explodes the given format into two pieces. One that can be user for start date and the other for end date * * @param $format * @return array */ protected function explodeFormat($format) { $intervalTokens = array( array('d', 'E', 'C'), array('M', 'L'), array('y') ); $offset = strlen($format); // replace string literals encapsulated by ' with same country of * $cleanedFormat = preg_replace_callback('/(\'[^\']+\')/', array($this, 'replaceWithStars'), $format); // search for first duplicate date field foreach ($intervalTokens AS $tokens) { if (preg_match_all('/[' . implode('|', $tokens) . ']+/', $cleanedFormat, $matches, PREG_OFFSET_CAPTURE) && count($matches[0]) > 1 && $offset > $matches[0][1][1] ) { $offset = $matches[0][1][1]; } } return array(substr($format, 0, $offset), substr($format, $offset)); } private function replaceWithStars($matches) { return str_repeat("*", strlen($matches[0])); } protected function getRangeFormat($short = false) { $maxDifference = 'D'; if ($this->getDateStart()->toString('y') != $this->getDateEnd()->toString('y')) { $maxDifference = 'Y'; } elseif ($this->getDateStart()->toString('m') != $this->getDateEnd()->toString('m')) { $maxDifference = 'M'; } $dateTimeFormatProvider = StaticContainer::get('Piwik\Intl\Data\Provider\DateTimeFormatProvider'); return $dateTimeFormatProvider->getRangeFormatPattern($short, $maxDifference); } /** * Returns all child periods that exist within this periods entire date range. Cascades * downwards over all period types that are smaller than this one. For example, month periods * will cascade to week and day periods and year periods will cascade to month, week and day * periods. * * The method will not return periods that are outside the range of this period. * * @return Period[] * @ignore */ public function getAllOverlappingChildPeriods() { return $this->getAllOverlappingChildPeriodsInRange($this->getDateStart(), $this->getDateEnd()); } private function getAllOverlappingChildPeriodsInRange(Date $dateStart, Date $dateEnd) { $result = array(); $childPeriodType = $this->getImmediateChildPeriodLabel(); if (empty($childPeriodType)) { return $result; } $childPeriods = Factory::build($childPeriodType, $dateStart->toString() . ',' . $dateEnd->toString()); return array_merge($childPeriods->getSubperiods(), $childPeriods->getAllOverlappingChildPeriodsInRange($dateStart, $dateEnd)); } }